diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svx/source/svdraw | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/svdraw')
83 files changed, 73751 insertions, 0 deletions
diff --git a/svx/source/svdraw/ActionDescriptionProvider.cxx b/svx/source/svdraw/ActionDescriptionProvider.cxx new file mode 100644 index 0000000000..0dc5e895c7 --- /dev/null +++ b/svx/source/svdraw/ActionDescriptionProvider.cxx @@ -0,0 +1,66 @@ +/* -*- 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/ActionDescriptionProvider.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +OUString ActionDescriptionProvider::createDescription( ActionType eActionType + , std::u16string_view rObjectName ) +{ + TranslateId pResID; + switch( eActionType ) + { + case ActionType::Insert: + pResID=STR_UndoInsertObj; + break; + case ActionType::Delete: + pResID= STR_EditDelete; + break; + case ActionType::Move: + pResID= STR_EditMove; + break; + case ActionType::Resize: + pResID= STR_EditResize; + break; + case ActionType::Rotate: + pResID= STR_EditRotate; + break; + case ActionType::Format: + pResID= STR_EditSetAttributes; + break; + case ActionType::MoveToTop: + pResID= STR_EditMovToTop; + break; + case ActionType::MoveToBottom: + pResID= STR_EditMovToBtm; + break; + case ActionType::PosSize: + pResID = STR_EditPosSize; + break; + } + if (!pResID) + return OUString(); + + OUString aStr(SvxResId(pResID)); + return aStr.replaceAll("%1", rObjectName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/MediaShellHelpers.cxx b/svx/source/svdraw/MediaShellHelpers.cxx new file mode 100644 index 0000000000..8cf760a7b0 --- /dev/null +++ b/svx/source/svdraw/MediaShellHelpers.cxx @@ -0,0 +1,108 @@ +/* -*- 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 <config_features.h> +#include <svx/MediaShellHelpers.hxx> +#include <avmedia/mediaitem.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/whiter.hxx> +#include <svx/sdr/contact/viewcontactofsdrmediaobj.hxx> +#include <svx/svdmrkv.hxx> + +namespace svx::MediaShellHelpers +{ +void GetState(const SdrMarkView* pSdrView, SfxItemSet& rSet) +{ + if (!pSdrView) + return; + + SfxWhichIter aIter(rSet); + + for (sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich()) + { + if (SID_AVMEDIA_TOOLBOX != nWhich) + continue; + +#if HAVE_FEATURE_AVMEDIA + const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); + bool bDisable = true; + + if (1 == rMarkList.GetMarkCount()) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + + if (dynamic_cast<SdrMediaObj*>(pObj)) + { + ::avmedia::MediaItem aItem(SID_AVMEDIA_TOOLBOX); + + static_cast<sdr::contact::ViewContactOfSdrMediaObj&>(pObj->GetViewContact()) + .updateMediaItem(aItem); + rSet.Put(aItem); + bDisable = false; + } + } + + if (bDisable) +#endif + rSet.DisableItem(SID_AVMEDIA_TOOLBOX); + } +} + +const ::avmedia::MediaItem* Execute(const SdrMarkView* pSdrView, SfxRequest const& rReq) +{ +#if !HAVE_FEATURE_AVMEDIA + (void)pSdrView; + (void)rReq; + return nullptr; +#else + if (!pSdrView) + return nullptr; + + if (SID_AVMEDIA_TOOLBOX != rReq.GetSlot()) + return nullptr; + + const SfxItemSet* pArgs = rReq.GetArgs(); + if (!pArgs) + return nullptr; + + const ::avmedia::MediaItem* pMediaItem = pArgs->GetItemIfSet(SID_AVMEDIA_TOOLBOX, false); + if (!pMediaItem) + return nullptr; + + const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); + + if (1 != rMarkList.GetMarkCount()) + return nullptr; + + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + + if (!dynamic_cast<SdrMediaObj*>(pObj)) + return nullptr; + + static_cast<sdr::contact::ViewContactOfSdrMediaObj&>(pObj->GetViewContact()) + .executeMediaItem(*pMediaItem); + + return pMediaItem; +#endif +} + +} // end of namespace svx::MediaShellHelpers + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/charthelper.cxx b/svx/source/svdraw/charthelper.cxx new file mode 100644 index 0000000000..2c75be5d27 --- /dev/null +++ b/svx/source/svdraw/charthelper.cxx @@ -0,0 +1,134 @@ +/* -*- 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/charthelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/util/XUpdatable2.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <sdr/primitive2d/primitivefactory2d.hxx> + +using namespace ::com::sun::star; + +void ChartHelper::updateChart( const uno::Reference< ::frame::XModel >& rXModel ) +{ + if (!rXModel.is()) + return; + + try + { + const uno::Reference< lang::XMultiServiceFactory > xChartFact(rXModel, uno::UNO_QUERY_THROW); + const uno::Reference< util::XUpdatable2 > xChartView(xChartFact->createInstance("com.sun.star.chart2.ChartView"), uno::UNO_QUERY_THROW); + + xChartView->updateHard(); + } + catch(uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } +} + +drawinglayer::primitive2d::Primitive2DContainer ChartHelper::tryToGetChartContentAsPrimitive2DSequence( + const uno::Reference< ::frame::XModel >& rXModel, + basegfx::B2DRange& rRange) +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + if (!rXModel.is()) + return aRetval; + + // don't broadcast until we're finished building, more efficient + rXModel->lockControllers(); + updateChart(rXModel); + rXModel->unlockControllers(); + + try + { + const uno::Reference< drawing::XDrawPageSupplier > xDrawPageSupplier(rXModel, uno::UNO_QUERY_THROW); + const uno::Reference< container::XIndexAccess > xShapeAccess(xDrawPageSupplier->getDrawPage(), uno::UNO_QUERY_THROW); + + if(xShapeAccess->getCount()) + { + const sal_Int32 nShapeCount(xShapeAccess->getCount()); + const uno::Sequence< beans::PropertyValue > aParams; + uno::Reference< drawing::XShape > xShape; + + for(sal_Int32 a(0); a < nShapeCount; a++) + { + xShapeAccess->getByIndex(a) >>= xShape; + + if(xShape.is()) + { + PrimitiveFactory2D::createPrimitivesFromXShape( + xShape, + aParams, + aRetval); + } + } + } + } + catch(uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + if(!aRetval.empty()) + { + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + + rRange = aRetval.getB2DRange(aViewInformation2D); + } + + return aRetval; +} + +void ChartHelper::AdaptDefaultsForChart( + const uno::Reference < embed::XEmbeddedObject > & xEmbObj) +{ + if( !xEmbObj.is()) + return; + + uno::Reference< chart2::XChartDocument > xChartDoc( xEmbObj->getComponent(), uno::UNO_QUERY ); + OSL_ENSURE( xChartDoc.is(), "Trying to set chart property to non-chart OLE" ); + if( !xChartDoc.is()) + return; + + try + { + if (uno::Reference< beans::XPropertySet > xPageProp = xChartDoc->getPageBackground()) + { + // set background to transparent (none) + xPageProp->setPropertyValue("FillStyle", uno::Any(drawing::FillStyle_NONE)); + // set no border + xPageProp->setPropertyValue("LineStyle", uno::Any(drawing::LineStyle_NONE)); + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("svx.svdraw", ""); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/clonelist.cxx b/svx/source/svdraw/clonelist.cxx new file mode 100644 index 0000000000..ffb1ccc676 --- /dev/null +++ b/svx/source/svdraw/clonelist.cxx @@ -0,0 +1,107 @@ +/* -*- 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 . + */ + + +// #i13033# +// New mechanism to hold a list of all original and cloned objects for later +// re-creating the connections for contained connectors +#include <clonelist.hxx> +#include <svx/svdoedge.hxx> +#include <svx/scene3d.hxx> + +void CloneList::AddPair(const SdrObject* pOriginal, SdrObject* pClone) +{ + maOriginalList.push_back(pOriginal); + maCloneList.push_back(pClone); + + // look for subobjects, too. + bool bOriginalIsGroup(pOriginal->IsGroupObject()); + bool bCloneIsGroup(pClone->IsGroupObject()); + + if(bOriginalIsGroup && DynCastE3dObject(pOriginal) != nullptr && DynCastE3dScene(pOriginal) == nullptr ) + bOriginalIsGroup = false; + + if(bCloneIsGroup && DynCastE3dObject(pClone) != nullptr && DynCastE3dScene(pClone) == nullptr) + bCloneIsGroup = false; + + if(!(bOriginalIsGroup && bCloneIsGroup)) + return; + + const SdrObjList* pOriginalList = pOriginal->GetSubList(); + SdrObjList* pCloneList = pClone->GetSubList(); + + if(pOriginalList && pCloneList + && pOriginalList->GetObjCount() == pCloneList->GetObjCount()) + { + for(size_t a = 0; a < pOriginalList->GetObjCount(); ++a) + { + // recursive call + AddPair(pOriginalList->GetObj(a), pCloneList->GetObj(a)); + } + } +} + +const SdrObject* CloneList::GetOriginal(sal_uInt32 nIndex) const +{ + return maOriginalList[nIndex]; +} + +SdrObject* CloneList::GetClone(sal_uInt32 nIndex) const +{ + return maCloneList[nIndex]; +} + +void CloneList::CopyConnections() const +{ + sal_uInt32 cloneCount = maCloneList.size(); + + for(size_t a = 0; a < maOriginalList.size(); a++) + { + const SdrEdgeObj* pOriginalEdge = dynamic_cast<const SdrEdgeObj*>( GetOriginal(a) ); + SdrEdgeObj* pCloneEdge = dynamic_cast<SdrEdgeObj*>( GetClone(a) ); + + if(pOriginalEdge && pCloneEdge) + { + for (bool bTail1 : { true, false }) + { + SdrObject* pOriginalNode = pOriginalEdge->GetConnectedNode(bTail1); + if (pOriginalNode) + { + std::vector<const SdrObject*>::const_iterator it = std::find(maOriginalList.begin(), + maOriginalList.end(), + pOriginalNode); + + if(it != maOriginalList.end()) + { + sal_uInt32 nPos = it - maOriginalList.begin(); + SdrObject *cObj = nullptr; + + if (nPos < cloneCount) + cObj = GetClone(nPos); + + if(pOriginalNode != cObj) + pCloneEdge->ConnectToNode(bTail1, cObj); + } + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/constructhelper.cxx b/svx/source/svdraw/constructhelper.cxx new file mode 100644 index 0000000000..d7c7f20a5c --- /dev/null +++ b/svx/source/svdraw/constructhelper.cxx @@ -0,0 +1,191 @@ +/* -*- 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 <basegfx/polygon/b2dpolygontools.hxx> +#include <svl/itemset.hxx> +#include <svx/constructhelper.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdmodel.hxx> +#include <svx/svdobj.hxx> +#include <svx/svxids.hrc> +#include <svx/xdef.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnwtit.hxx> + +//using namespace ::com::sun::star; + +::basegfx::B2DPolyPolygon ConstructHelper::GetLineEndPoly(TranslateId pResId, + const SdrModel& rModel) +{ + ::basegfx::B2DPolyPolygon aRetval; + XLineEndListRef pLineEndList(rModel.GetLineEndList()); + + if (pLineEndList.is()) + { + OUString aArrowName(SvxResId(pResId)); + tools::Long nCount = pLineEndList->Count(); + tools::Long nIndex; + for (nIndex = 0; nIndex < nCount; nIndex++) + { + const XLineEndEntry* pEntry = pLineEndList->GetLineEnd(nIndex); + if (pEntry->GetName() == aArrowName) + { + aRetval = pEntry->GetLineEnd(); + break; + } + } + } + + return aRetval; +} + +void ConstructHelper::SetLineEnds(SfxItemSet& rAttr, const SdrObject& rObj, sal_uInt16 nSlotId, + tools::Long nWidth) +{ + SdrModel& rModel(rObj.getSdrModelFromSdrObject()); + + if (!(nSlotId == SID_LINE_ARROW_START || nSlotId == SID_LINE_ARROW_END + || nSlotId == SID_LINE_ARROWS || nSlotId == SID_LINE_ARROW_CIRCLE + || nSlotId == SID_LINE_CIRCLE_ARROW || nSlotId == SID_LINE_ARROW_SQUARE + || nSlotId == SID_LINE_SQUARE_ARROW || nSlotId == SID_DRAW_MEASURELINE)) + return; + + // set attributes of line start and ends + + // arrowhead + ::basegfx::B2DPolyPolygon aArrow(GetLineEndPoly(RID_SVXSTR_ARROW, rModel)); + if (!aArrow.count()) + { + ::basegfx::B2DPolygon aNewArrow; + aNewArrow.append(::basegfx::B2DPoint(10.0, 0.0)); + aNewArrow.append(::basegfx::B2DPoint(0.0, 30.0)); + aNewArrow.append(::basegfx::B2DPoint(20.0, 30.0)); + aNewArrow.setClosed(true); + aArrow.append(aNewArrow); + } + + // Circles + ::basegfx::B2DPolyPolygon aCircle(GetLineEndPoly(RID_SVXSTR_CIRCLE, rModel)); + if (!aCircle.count()) + { + ::basegfx::B2DPolygon aNewCircle = ::basegfx::utils::createPolygonFromEllipse( + ::basegfx::B2DPoint(0.0, 0.0), 250.0, 250.0); + aNewCircle.setClosed(true); + aCircle.append(aNewCircle); + } + + // Square + ::basegfx::B2DPolyPolygon aSquare(GetLineEndPoly(RID_SVXSTR_SQUARE, rModel)); + if (!aSquare.count()) + { + ::basegfx::B2DPolygon aNewSquare; + aNewSquare.append(::basegfx::B2DPoint(0.0, 0.0)); + aNewSquare.append(::basegfx::B2DPoint(10.0, 0.0)); + aNewSquare.append(::basegfx::B2DPoint(10.0, 10.0)); + aNewSquare.append(::basegfx::B2DPoint(0.0, 10.0)); + aNewSquare.setClosed(true); + aSquare.append(aNewSquare); + } + + SfxItemSet aSet(rModel.GetItemPool()); + + // determine line width and calculate with it the line end width + if (aSet.GetItemState(XATTR_LINEWIDTH) != SfxItemState::DONTCARE) + { + tools::Long nValue = aSet.Get(XATTR_LINEWIDTH).GetValue(); + if (nValue > 0) + nWidth = nValue * 3; + } + + switch (nSlotId) + { + case SID_LINE_ARROWS: + case SID_DRAW_MEASURELINE: + { + // connector with arrow ends + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineStartWidthItem(nWidth)); + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_START: + case SID_LINE_ARROW_CIRCLE: + case SID_LINE_ARROW_SQUARE: + { + // connector with arrow start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_END: + case SID_LINE_CIRCLE_ARROW: + case SID_LINE_SQUARE_ARROW: + { + // connector with arrow end + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_ARROW), aArrow)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + } + + // and again, for the still missing ends + switch (nSlotId) + { + case SID_LINE_ARROW_CIRCLE: + { + // circle end + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_CIRCLE), aCircle)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + + case SID_LINE_CIRCLE_ARROW: + { + // circle start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_CIRCLE), aCircle)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + + case SID_LINE_ARROW_SQUARE: + { + // square end + rAttr.Put(XLineEndItem(SvxResId(RID_SVXSTR_SQUARE), aSquare)); + rAttr.Put(XLineEndWidthItem(nWidth)); + } + break; + + case SID_LINE_SQUARE_ARROW: + { + // square start + rAttr.Put(XLineStartItem(SvxResId(RID_SVXSTR_SQUARE), aSquare)); + rAttr.Put(XLineStartWidthItem(nWidth)); + } + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/gradtrns.cxx b/svx/source/svdraw/gradtrns.cxx new file mode 100644 index 0000000000..82a4ea0a29 --- /dev/null +++ b/svx/source/svdraw/gradtrns.cxx @@ -0,0 +1,531 @@ +/* -*- 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 "gradtrns.hxx" +#include <svx/svdobj.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/helpers.hxx> +#include <vcl/canvastools.hxx> + + +void GradTransformer::GradToVec(GradTransGradient const & rG, GradTransVector& rV, const SdrObject* pObj) +{ + // handle start color + rV.aCol1 = Color(rG.aGradient.GetColorStops().front().getStopColor()); + if(100 != rG.aGradient.GetStartIntens()) + { + const double fFact(static_cast<double>(rG.aGradient.GetStartIntens()) / 100.0); + rV.aCol1 = Color(rV.aCol1.getBColor() * fFact); + } + + // handle end color + rV.aCol2 = Color(rG.aGradient.GetColorStops().back().getStopColor()); + if(100 != rG.aGradient.GetEndIntens()) + { + const double fFact(static_cast<double>(rG.aGradient.GetEndIntens()) / 100.0); + rV.aCol2 = Color(rV.aCol2.getBColor() * fFact); + } + + // calc the basic positions + const tools::Rectangle aObjectSnapRectangle(pObj->GetSnapRect()); + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aObjectSnapRectangle); + const basegfx::B2DPoint aCenter(aRange.getCenter()); + basegfx::B2DPoint aStartPos, aEndPos; + + switch(rG.aGradient.GetGradientStyle()) + { + case css::awt::GradientStyle_LINEAR : + { + aStartPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMinY()); + aEndPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY()); + + if(rG.aGradient.GetBorder()) + { + basegfx::B2DVector aFullVec(aStartPos - aEndPos); + const double fLen = (aFullVec.getLength() * (100.0 - static_cast<double>(rG.aGradient.GetBorder()))) / 100.0; + aFullVec.normalize(); + aStartPos = aEndPos + (aFullVec * fLen); + } + + if(rG.aGradient.GetAngle()) + { + const double fAngle = toRadians(rG.aGradient.GetAngle()); + const basegfx::B2DHomMatrix aTransformation(basegfx::utils::createRotateAroundPoint(aCenter, -fAngle)); + + aStartPos *= aTransformation; + aEndPos *= aTransformation; + } + break; + } + case css::awt::GradientStyle_AXIAL : + { + aStartPos = aCenter; + aEndPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY()); + + if(rG.aGradient.GetBorder()) + { + basegfx::B2DVector aFullVec(aEndPos - aStartPos); + const double fLen = (aFullVec.getLength() * (100.0 - static_cast<double>(rG.aGradient.GetBorder()))) / 100.0; + aFullVec.normalize(); + aEndPos = aStartPos + (aFullVec * fLen); + } + + if(rG.aGradient.GetAngle()) + { + const double fAngle = toRadians(rG.aGradient.GetAngle()); + const basegfx::B2DHomMatrix aTransformation(basegfx::utils::createRotateAroundPoint(aCenter, -fAngle)); + + aStartPos *= aTransformation; + aEndPos *= aTransformation; + } + break; + } + case css::awt::GradientStyle_RADIAL : + case css::awt::GradientStyle_SQUARE : + { + aStartPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMaximum().getY()); + aEndPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMinY()); + + if(rG.aGradient.GetBorder()) + { + basegfx::B2DVector aFullVec(aStartPos - aEndPos); + const double fLen = (aFullVec.getLength() * (100.0 - static_cast<double>(rG.aGradient.GetBorder()))) / 100.0; + aFullVec.normalize(); + aStartPos = aEndPos + (aFullVec * fLen); + } + + if(rG.aGradient.GetAngle()) + { + const double fAngle = toRadians(rG.aGradient.GetAngle()); + const basegfx::B2DHomMatrix aTransformation(basegfx::utils::createRotateAroundPoint(aEndPos, -fAngle)); + + aStartPos *= aTransformation; + aEndPos *= aTransformation; + } + + if(rG.aGradient.GetXOffset() || rG.aGradient.GetYOffset()) + { + basegfx::B2DPoint aOffset( + (aRange.getWidth() * rG.aGradient.GetXOffset()) / 100.0, + (aRange.getHeight() * rG.aGradient.GetYOffset()) / 100.0); + + aStartPos += aOffset; + aEndPos += aOffset; + } + + break; + } + case css::awt::GradientStyle_ELLIPTICAL : + case css::awt::GradientStyle_RECT : + { + aStartPos = basegfx::B2DPoint(aRange.getMinX(), aCenter.getY()); + aEndPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMinY()); + + if(rG.aGradient.GetBorder()) + { + basegfx::B2DVector aFullVec(aStartPos - aEndPos); + const double fLen = (aFullVec.getLength() * (100.0 - static_cast<double>(rG.aGradient.GetBorder()))) / 100.0; + aFullVec.normalize(); + aStartPos = aEndPos + (aFullVec * fLen); + } + + if(rG.aGradient.GetAngle()) + { + const double fAngle = toRadians(rG.aGradient.GetAngle()); + const basegfx::B2DHomMatrix aTransformation(basegfx::utils::createRotateAroundPoint(aEndPos, -fAngle)); + + aStartPos *= aTransformation; + aEndPos *= aTransformation; + } + + if(rG.aGradient.GetXOffset() || rG.aGradient.GetYOffset()) + { + basegfx::B2DPoint aOffset( + (aRange.getWidth() * rG.aGradient.GetXOffset()) / 100.0, + (aRange.getHeight() * rG.aGradient.GetYOffset()) / 100.0); + + aStartPos += aOffset; + aEndPos += aOffset; + } + + break; + } + default: + break; + } + + // set values for vector positions now + rV.maPositionA = aStartPos; + rV.maPositionB = aEndPos; +} + + +void GradTransformer::VecToGrad(GradTransVector const & rV, GradTransGradient& rG, GradTransGradient const & rGOld, const SdrObject* pObj, + bool bMoveSingle, bool bMoveFirst) +{ + // fill old gradient to new gradient to have a base + rG = rGOld; + + // handle color changes + if(rV.aCol1 != Color(rGOld.aGradient.GetColorStops().front().getStopColor())) + { + basegfx::BColorStops aNewColorStops(rG.aGradient.GetColorStops()); + aNewColorStops.replaceStartColor(rV.aCol1.getBColor()); + rG.aGradient.SetColorStops(aNewColorStops); + rG.aGradient.SetStartIntens(100); + } + if(rV.aCol2 != Color(rGOld.aGradient.GetColorStops().back().getStopColor())) + { + basegfx::BColorStops aNewColorStops(rG.aGradient.GetColorStops()); + aNewColorStops.replaceEndColor(rV.aCol2.getBColor()); + rG.aGradient.SetColorStops(aNewColorStops); + rG.aGradient.SetEndIntens(100); + } + + // calc the basic positions + const tools::Rectangle aObjectSnapRectangle(pObj->GetSnapRect()); + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aObjectSnapRectangle); + const basegfx::B2DPoint aCenter(aRange.getCenter()); + basegfx::B2DPoint aStartPos(rV.maPositionA); + basegfx::B2DPoint aEndPos(rV.maPositionB); + + switch(rG.aGradient.GetGradientStyle()) + { + case css::awt::GradientStyle_LINEAR : + { + if(!bMoveSingle || !bMoveFirst) + { + basegfx::B2DVector aFullVec(aEndPos - aStartPos); + + if(bMoveSingle) + { + aFullVec = aEndPos - aCenter; + } + + aFullVec.normalize(); + + double fNewFullAngle(basegfx::rad2deg(atan2(aFullVec.getY(), aFullVec.getX()))); + fNewFullAngle *= -10.0; + fNewFullAngle += 900.0; + + // clip + while(fNewFullAngle < 0.0) + { + fNewFullAngle += 3600.0; + } + + while(fNewFullAngle >= 3600.0) + { + fNewFullAngle -= 3600.0; + } + + // to int and set + Degree10 nNewAngle( FRound(fNewFullAngle)); + + if(nNewAngle != rGOld.aGradient.GetAngle()) + { + rG.aGradient.SetAngle(nNewAngle); + } + } + + if(!bMoveSingle || bMoveFirst) + { + const basegfx::B2DVector aFullVec(aEndPos - aStartPos); + const basegfx::B2DPoint aBottomLeft(aRange.getMinX(), aRange.getMaximum().getY()); + const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY()); + const basegfx::B2DVector aOldVec(aBottomLeft - aTopLeft); + const double fFullLen(aFullVec.getLength()); + const double fOldLen(aOldVec.getLength()); + const double fNewBorder((fFullLen * 100.0) / fOldLen); + sal_Int32 nNewBorder(100 - FRound(fNewBorder)); + + // clip + if(nNewBorder < 0) + { + nNewBorder = 0; + } + + if(nNewBorder > 100) + { + nNewBorder = 100; + } + + // set + if(nNewBorder != rG.aGradient.GetBorder()) + { + rG.aGradient.SetBorder(static_cast<sal_uInt16>(nNewBorder)); + } + } + + break; + } + case css::awt::GradientStyle_AXIAL : + { + if(!bMoveSingle || !bMoveFirst) + { + basegfx::B2DVector aFullVec(aEndPos - aCenter); + const basegfx::B2DVector aOldVec(basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY()) - aCenter); + const double fFullLen(aFullVec.getLength()); + const double fOldLen(aOldVec.getLength()); + const double fNewBorder((fFullLen * 100.0) / fOldLen); + sal_Int32 nNewBorder = 100 - FRound(fNewBorder); + + // clip + if(nNewBorder < 0) + { + nNewBorder = 0; + } + + if(nNewBorder > 100) + { + nNewBorder = 100; + } + + // set + if(nNewBorder != rG.aGradient.GetBorder()) + { + rG.aGradient.SetBorder(static_cast<sal_uInt16>(nNewBorder)); + } + + aFullVec.normalize(); + double fNewFullAngle(basegfx::rad2deg(atan2(aFullVec.getY(), aFullVec.getX()))); + fNewFullAngle *= -10.0; + fNewFullAngle += 900.0; + + // clip + while(fNewFullAngle < 0.0) + { + fNewFullAngle += 3600.0; + } + + while(fNewFullAngle >= 3600.0) + { + fNewFullAngle -= 3600.0; + } + + // to int and set + const Degree10 nNewAngle(FRound(fNewFullAngle)); + + if(nNewAngle != rGOld.aGradient.GetAngle()) + { + rG.aGradient.SetAngle(nNewAngle); + } + } + + break; + } + case css::awt::GradientStyle_RADIAL : + case css::awt::GradientStyle_SQUARE : + { + if(!bMoveSingle || !bMoveFirst) + { + const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY()); + const basegfx::B2DPoint aOffset(aEndPos - aTopLeft); + sal_Int32 nNewXOffset(FRound((aOffset.getX() * 100.0) / aRange.getWidth())); + sal_Int32 nNewYOffset(FRound((aOffset.getY() * 100.0) / aRange.getHeight())); + + // clip + if(nNewXOffset < 0) + { + nNewXOffset = 0; + } + + if(nNewXOffset > 100) + { + nNewXOffset = 100; + } + + if(nNewYOffset < 0) + { + nNewYOffset = 0; + } + + if(nNewYOffset > 100) + { + nNewYOffset = 100; + } + + rG.aGradient.SetXOffset(static_cast<sal_uInt16>(nNewXOffset)); + rG.aGradient.SetYOffset(static_cast<sal_uInt16>(nNewYOffset)); + + aStartPos -= aOffset; + aEndPos -= aOffset; + } + + if(!bMoveSingle || bMoveFirst) + { + basegfx::B2DVector aFullVec(aStartPos - aEndPos); + const basegfx::B2DPoint aBottomLeft(aRange.getMinX(), aRange.getMaximum().getY()); + const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY()); + const basegfx::B2DVector aOldVec(aBottomLeft - aTopLeft); + const double fFullLen(aFullVec.getLength()); + const double fOldLen(aOldVec.getLength()); + const double fNewBorder((fFullLen * 100.0) / fOldLen); + sal_Int32 nNewBorder(100 - FRound(fNewBorder)); + + // clip + if(nNewBorder < 0) + { + nNewBorder = 0; + } + + if(nNewBorder > 100) + { + nNewBorder = 100; + } + + // set + if(nNewBorder != rG.aGradient.GetBorder()) + { + rG.aGradient.SetBorder(static_cast<sal_uInt16>(nNewBorder)); + } + + // angle is not definitely necessary for these modes, but it makes + // controlling more fun for the user + aFullVec.normalize(); + double fNewFullAngle(basegfx::rad2deg(atan2(aFullVec.getY(), aFullVec.getX()))); + fNewFullAngle *= -10.0; + fNewFullAngle += 900.0; + + // clip + while(fNewFullAngle < 0.0) + { + fNewFullAngle += 3600.0; + } + + while(fNewFullAngle >= 3600.0) + { + fNewFullAngle -= 3600.0; + } + + // to int and set + const Degree10 nNewAngle(FRound(fNewFullAngle)); + + if(nNewAngle != rGOld.aGradient.GetAngle()) + { + rG.aGradient.SetAngle(nNewAngle); + } + } + + break; + } + case css::awt::GradientStyle_ELLIPTICAL : + case css::awt::GradientStyle_RECT : + { + if(!bMoveSingle || !bMoveFirst) + { + const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY()); + const basegfx::B2DPoint aOffset(aEndPos - aTopLeft); + sal_Int32 nNewXOffset(FRound((aOffset.getX() * 100.0) / aRange.getWidth())); + sal_Int32 nNewYOffset(FRound((aOffset.getY() * 100.0) / aRange.getHeight())); + + // clip + if(nNewXOffset < 0) + { + nNewXOffset = 0; + } + + if(nNewXOffset > 100) + { + nNewXOffset = 100; + } + + if(nNewYOffset < 0) + { + nNewYOffset = 0; + } + + if(nNewYOffset > 100) + { + nNewYOffset = 100; + } + + rG.aGradient.SetXOffset(static_cast<sal_uInt16>(nNewXOffset)); + rG.aGradient.SetYOffset(static_cast<sal_uInt16>(nNewYOffset)); + + aStartPos -= aOffset; + aEndPos -= aOffset; + } + + if(!bMoveSingle || bMoveFirst) + { + basegfx::B2DVector aFullVec(aStartPos - aEndPos); + const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY()); + const basegfx::B2DPoint aCenterLeft(aRange.getMinX(), aCenter.getY()); + const basegfx::B2DVector aOldVec(aCenterLeft - aTopLeft); + const double fFullLen(aFullVec.getLength()); + const double fOldLen(aOldVec.getLength()); + const double fNewBorder((fFullLen * 100.0) / fOldLen); + sal_Int32 nNewBorder(100 - FRound(fNewBorder)); + + // clip + if(nNewBorder < 0) + { + nNewBorder = 0; + } + + if(nNewBorder > 100) + { + nNewBorder = 100; + } + + // set + if(nNewBorder != rG.aGradient.GetBorder()) + { + rG.aGradient.SetBorder(static_cast<sal_uInt16>(nNewBorder)); + } + + // angle is not definitely necessary for these modes, but it makes + // controlling more fun for the user + aFullVec.normalize(); + double fNewFullAngle(basegfx::rad2deg(atan2(aFullVec.getY(), aFullVec.getX()))); + fNewFullAngle *= -10.0; + fNewFullAngle += 900.0; + + // clip + while(fNewFullAngle < 0.0) + { + fNewFullAngle += 3600.0; + } + + while(fNewFullAngle >= 3600.0) + { + fNewFullAngle -= 3600.0; + } + + // to int and set + const Degree10 nNewAngle(FRound(fNewFullAngle)); + + if(nNewAngle != rGOld.aGradient.GetAngle()) + { + rG.aGradient.SetAngle(nNewAngle); + } + } + + break; + } + default: + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/gradtrns.hxx b/svx/source/svdraw/gradtrns.hxx new file mode 100644 index 0000000000..f53e236b73 --- /dev/null +++ b/svx/source/svdraw/gradtrns.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_SVDRAW_GRADTRNS_HXX +#define INCLUDED_SVX_SOURCE_SVDRAW_GRADTRNS_HXX + +#include <tools/color.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/bgradient.hxx> + +class SdrObject; + +class GradTransVector +{ +public: + basegfx::B2DPoint maPositionA; + basegfx::B2DPoint maPositionB; + Color aCol1; + Color aCol2; +}; + +class GradTransGradient +{ +public: + basegfx::BGradient aGradient; +}; + +struct GradTransformer +{ + GradTransformer() = delete; + + static void GradToVec(GradTransGradient const & rG, GradTransVector& rV, + const SdrObject* pObj); + static void VecToGrad(GradTransVector const & rV, GradTransGradient& rG, + GradTransGradient const & rGOld, const SdrObject* pObj, bool bMoveSingle, bool bMoveFirst); +}; + +#endif // INCLUDED_SVX_SOURCE_SVDRAW_GRADTRNS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/polypolygoneditor.cxx b/svx/source/svdraw/polypolygoneditor.cxx new file mode 100644 index 0000000000..4a9c888454 --- /dev/null +++ b/svx/source/svdraw/polypolygoneditor.cxx @@ -0,0 +1,183 @@ +/* -*- 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 <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> + +#include <svx/polypolygoneditor.hxx> +#include <utility> + +namespace sdr { + +PolyPolygonEditor::PolyPolygonEditor( basegfx::B2DPolyPolygon aPolyPolygon) +: maPolyPolygon(std::move( aPolyPolygon )) +{ +} + +bool PolyPolygonEditor::DeletePoints( const o3tl::sorted_vector< sal_uInt16 >& rAbsPoints ) +{ + bool bPolyPolyChanged = false; + + auto aIter( rAbsPoints.rbegin() ); + for( ; aIter != rAbsPoints.rend(); ++aIter ) + { + sal_uInt32 nPoly, nPnt; + if( GetRelativePolyPoint(maPolyPolygon,(*aIter), nPoly, nPnt) ) + { + // remove point + basegfx::B2DPolygon aCandidate(maPolyPolygon.getB2DPolygon(nPoly)); + + aCandidate.remove(nPnt); + + + if( aCandidate.count() < 2 ) + { + maPolyPolygon.remove(nPoly); + } + else + { + maPolyPolygon.setB2DPolygon(nPoly, aCandidate); + } + + bPolyPolyChanged = true; + } + } + + return bPolyPolyChanged; +} + +bool PolyPolygonEditor::SetSegmentsKind(SdrPathSegmentKind eKind, const o3tl::sorted_vector< sal_uInt16 >& rAbsPoints ) +{ + bool bPolyPolyChanged = false; + + auto aIter( rAbsPoints.rbegin() ); + for( ; aIter != rAbsPoints.rend(); ++aIter ) + { + sal_uInt32 nPolyNum, nPntNum; + + if(PolyPolygonEditor::GetRelativePolyPoint(maPolyPolygon, (*aIter), nPolyNum, nPntNum)) + { + // do change at aNewPolyPolygon. Take a look at edge. + basegfx::B2DPolygon aCandidate(maPolyPolygon.getB2DPolygon(nPolyNum)); + const sal_uInt32 nCount(aCandidate.count()); + + if(nCount && (nPntNum + 1 < nCount || aCandidate.isClosed())) + { + bool bCandidateChanged(false); + + // it's a valid edge, check control point usage + const sal_uInt32 nNextIndex((nPntNum + 1) % nCount); + const bool bContolUsed(aCandidate.areControlPointsUsed() + && (aCandidate.isNextControlPointUsed(nPntNum) || aCandidate.isPrevControlPointUsed(nNextIndex))); + + if(bContolUsed) + { + if(SdrPathSegmentKind::Toggle == eKind || SdrPathSegmentKind::Line == eKind) + { + // remove control + aCandidate.resetNextControlPoint(nPntNum); + aCandidate.resetPrevControlPoint(nNextIndex); + bCandidateChanged = true; + } + } + else + { + if(SdrPathSegmentKind::Toggle == eKind || SdrPathSegmentKind::Curve == eKind) + { + // add control + const basegfx::B2DPoint aStart(aCandidate.getB2DPoint(nPntNum)); + const basegfx::B2DPoint aEnd(aCandidate.getB2DPoint(nNextIndex)); + + aCandidate.setNextControlPoint(nPntNum, interpolate(aStart, aEnd, (1.0 / 3.0))); + aCandidate.setPrevControlPoint(nNextIndex, interpolate(aStart, aEnd, (2.0 / 3.0))); + bCandidateChanged = true; + } + } + + if(bCandidateChanged) + { + maPolyPolygon.setB2DPolygon(nPolyNum, aCandidate); + bPolyPolyChanged = true; + } + } + } + } + + return bPolyPolyChanged; +} + +bool PolyPolygonEditor::SetPointsSmooth( basegfx::B2VectorContinuity eFlags, const o3tl::sorted_vector< sal_uInt16 >& rAbsPoints) +{ + bool bPolyPolygonChanged(false); + + auto aIter( rAbsPoints.rbegin() ); + for( ; aIter != rAbsPoints.rend(); ++aIter ) + { + sal_uInt32 nPolyNum, nPntNum; + + if(PolyPolygonEditor::GetRelativePolyPoint(maPolyPolygon, (*aIter), nPolyNum, nPntNum)) + { + // do change at aNewPolyPolygon... + basegfx::B2DPolygon aCandidate(maPolyPolygon.getB2DPolygon(nPolyNum)); + + // set continuity in point, make sure there is a curve + bool bPolygonChanged = basegfx::utils::expandToCurveInPoint(aCandidate, nPntNum); + bPolygonChanged |= basegfx::utils::setContinuityInPoint(aCandidate, nPntNum, eFlags); + + if(bPolygonChanged) + { + maPolyPolygon.setB2DPolygon(nPolyNum, aCandidate); + bPolyPolygonChanged = true; + } + } + } + + return bPolyPolygonChanged; +} + +bool PolyPolygonEditor::GetRelativePolyPoint( const basegfx::B2DPolyPolygon& rPoly, sal_uInt32 nAbsPnt, sal_uInt32& rPolyNum, sal_uInt32& rPointNum ) +{ + const sal_uInt32 nPolyCount(rPoly.count()); + sal_uInt32 nPolyNum(0); + + while(nPolyNum < nPolyCount) + { + const sal_uInt32 nPointCount(rPoly.getB2DPolygon(nPolyNum).count()); + + if(nAbsPnt < nPointCount) + { + rPolyNum = nPolyNum; + rPointNum = nAbsPnt; + + return true; + } + else + { + nPolyNum++; + nAbsPnt -= nPointCount; + } + } + + return false; +} + +} // end of namespace sdr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/presetooxhandleadjustmentrelations.cxx b/svx/source/svdraw/presetooxhandleadjustmentrelations.cxx new file mode 100644 index 0000000000..528c8b35cd --- /dev/null +++ b/svx/source/svdraw/presetooxhandleadjustmentrelations.cxx @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <o3tl/string_view.hxx> +#include <rtl/ustring.hxx> +#include <unordered_map> +#include "presetooxhandleadjustmentrelations.hxx" + +namespace +{ +typedef std::unordered_map<OUString, OUString> HandleAdjRelHashMap; + +struct HandleAdjRel +{ + // Shape name without leading "ooxml-", underscore, zero based handle index + // e.g. The third handle in shape of type "ooxml-circularArrow" will be + // identified by key "circularArrow_2" + const char* sShape_Handle; + + // 4 tokens with separator "|" + // first: RefX or RefR, na if not exists + // second: adj, or adj1 or adj2, etc. as in preset, na if not exists + // third: RefY or RefAngle, na if not exists + // forth: adj, or adj1 or adj2, etc. as in preset, na if not exists + // e.g. The third handle in shape <circularArrow> has in the preset + // the tag <ahPolar gdRefR="adj5" minR="0" maxR="25000"> . + // The resulting value in the map here is "RefR|adj5|na|na" + const char* sAdjReferences; +}; + +// The array initializer has been extracted from +// oox/source/drawingml/customshapes/presetShapeDefinitions.xml +// by using an XSLT file. That file is attached to tdf#126512. +constexpr HandleAdjRel aHandleAdjRelArray[] + = { { "accentBorderCallout1_0", "RefX|adj2|RefY|adj1" }, + { "accentBorderCallout1_1", "RefX|adj4|RefY|adj3" }, + { "accentBorderCallout2_0", "RefX|adj2|RefY|adj1" }, + { "accentBorderCallout2_1", "RefX|adj4|RefY|adj3" }, + { "accentBorderCallout2_2", "RefX|adj6|RefY|adj5" }, + { "accentBorderCallout3_0", "RefX|adj2|RefY|adj1" }, + { "accentBorderCallout3_1", "RefX|adj4|RefY|adj3" }, + { "accentBorderCallout3_2", "RefX|adj6|RefY|adj5" }, + { "accentBorderCallout3_3", "RefX|adj8|RefY|adj7" }, + { "accentCallout1_0", "RefX|adj2|RefY|adj1" }, + { "accentCallout1_1", "RefX|adj4|RefY|adj3" }, + { "accentCallout2_0", "RefX|adj2|RefY|adj1" }, + { "accentCallout2_1", "RefX|adj4|RefY|adj3" }, + { "accentCallout2_2", "RefX|adj6|RefY|adj5" }, + { "accentCallout3_0", "RefX|adj2|RefY|adj1" }, + { "accentCallout3_1", "RefX|adj4|RefY|adj3" }, + { "accentCallout3_2", "RefX|adj6|RefY|adj5" }, + { "accentCallout3_3", "RefX|adj8|RefY|adj7" }, + { "arc_0", "na|na|RefAngle|adj1" }, + { "arc_1", "na|na|RefAngle|adj2" }, + { "bentArrow_0", "RefX|adj1|na|na" }, + { "bentArrow_1", "na|na|RefY|adj2" }, + { "bentArrow_2", "RefX|adj3|na|na" }, + { "bentArrow_3", "RefX|adj4|na|na" }, + { "bentConnector3_0", "RefX|adj1|na|na" }, + { "bentConnector4_0", "RefX|adj1|na|na" }, + { "bentConnector4_1", "na|na|RefY|adj2" }, + { "bentConnector5_0", "RefX|adj1|na|na" }, + { "bentConnector5_1", "na|na|RefY|adj2" }, + { "bentConnector5_2", "RefX|adj3|na|na" }, + { "bentUpArrow_0", "na|na|RefY|adj1" }, + { "bentUpArrow_1", "RefX|adj2|na|na" }, + { "bentUpArrow_2", "na|na|RefY|adj3" }, + { "bevel_0", "RefX|adj|na|na" }, + { "blockArc_0", "na|na|RefAngle|adj1" }, + { "blockArc_1", "RefR|adj3|RefAngle|adj2" }, + { "borderCallout1_0", "RefX|adj2|RefY|adj1" }, + { "borderCallout1_1", "RefX|adj4|RefY|adj3" }, + { "borderCallout2_0", "RefX|adj2|RefY|adj1" }, + { "borderCallout2_1", "RefX|adj4|RefY|adj3" }, + { "borderCallout2_2", "RefX|adj6|RefY|adj5" }, + { "borderCallout3_0", "RefX|adj2|RefY|adj1" }, + { "borderCallout3_1", "RefX|adj4|RefY|adj3" }, + { "borderCallout3_2", "RefX|adj6|RefY|adj5" }, + { "borderCallout3_3", "RefX|adj8|RefY|adj7" }, + { "bracePair_0", "na|na|RefY|adj" }, + { "bracketPair_0", "na|na|RefY|adj" }, + { "callout1_0", "RefX|adj2|RefY|adj1" }, + { "callout1_1", "RefX|adj4|RefY|adj3" }, + { "callout2_0", "RefX|adj2|RefY|adj1" }, + { "callout2_1", "RefX|adj4|RefY|adj3" }, + { "callout2_2", "RefX|adj6|RefY|adj5" }, + { "callout3_0", "RefX|adj2|RefY|adj1" }, + { "callout3_1", "RefX|adj4|RefY|adj3" }, + { "callout3_2", "RefX|adj6|RefY|adj5" }, + { "callout3_3", "RefX|adj8|RefY|adj7" }, + { "can_0", "na|na|RefY|adj" }, + { "chevron_0", "RefX|adj|na|na" }, + { "chord_0", "na|na|RefAngle|adj1" }, + { "chord_1", "na|na|RefAngle|adj2" }, + { "circularArrow_0", "na|na|RefAngle|adj2" }, + { "circularArrow_1", "na|na|RefAngle|adj4" }, + { "circularArrow_2", "RefR|adj1|RefAngle|adj3" }, + { "circularArrow_3", "RefR|adj5|na|na" }, + { "cloudCallout_0", "RefX|adj1|RefY|adj2" }, + { "corner_0", "na|na|RefY|adj1" }, + { "corner_1", "RefX|adj2|na|na" }, + { "cube_0", "na|na|RefY|adj" }, + { "curvedConnector3_0", "RefX|adj1|na|na" }, + { "curvedConnector4_0", "RefX|adj1|na|na" }, + { "curvedConnector4_1", "na|na|RefY|adj2" }, + { "curvedConnector5_0", "RefX|adj1|na|na" }, + { "curvedConnector5_1", "na|na|RefY|adj2" }, + { "curvedConnector5_2", "RefX|adj3|na|na" }, + { "curvedDownArrow_0", "RefX|adj1|na|na" }, + { "curvedDownArrow_1", "RefX|adj2|na|na" }, + { "curvedDownArrow_2", "na|na|RefY|adj3" }, + { "curvedLeftArrow_0", "na|na|RefY|adj1" }, + { "curvedLeftArrow_1", "na|na|RefY|adj2" }, + { "curvedLeftArrow_2", "RefX|adj3|na|na" }, + { "curvedRightArrow_0", "na|na|RefY|adj1" }, + { "curvedRightArrow_1", "na|na|RefY|adj2" }, + { "curvedRightArrow_2", "RefX|adj3|na|na" }, + { "curvedUpArrow_0", "RefX|adj1|na|na" }, + { "curvedUpArrow_1", "RefX|adj2|na|na" }, + { "curvedUpArrow_2", "na|na|RefY|adj3" }, + { "diagStripe_0", "na|na|RefY|adj" }, + { "donut_0", "RefR|adj|na|na" }, + { "doubleWave_0", "na|na|RefY|adj1" }, + { "doubleWave_1", "RefX|adj2|na|na" }, + { "downArrow_0", "RefX|adj1|na|na" }, + { "downArrow_1", "na|na|RefY|adj2" }, + { "downArrowCallout_0", "RefX|adj1|na|na" }, + { "downArrowCallout_1", "RefX|adj2|na|na" }, + { "downArrowCallout_2", "na|na|RefY|adj3" }, + { "downArrowCallout_3", "na|na|RefY|adj4" }, + { "ellipseRibbon_0", "na|na|RefY|adj1" }, + { "ellipseRibbon_1", "RefX|adj2|na|na" }, + { "ellipseRibbon_2", "na|na|RefY|adj3" }, + { "ellipseRibbon2_0", "na|na|RefY|adj1" }, + { "ellipseRibbon2_1", "RefX|adj2|na|na" }, + { "ellipseRibbon2_2", "na|na|RefY|adj3" }, + { "foldedCorner_0", "RefX|adj|na|na" }, + { "frame_0", "RefX|adj1|na|na" }, + { "gear6_0", "na|na|RefY|adj1" }, + { "gear6_1", "RefX|adj2|na|na" }, + { "gear9_0", "na|na|RefY|adj1" }, + { "gear9_1", "RefX|adj2|na|na" }, + { "halfFrame_0", "na|na|RefY|adj1" }, + { "halfFrame_1", "RefX|adj2|na|na" }, + { "hexagon_0", "RefX|adj|na|na" }, + { "homePlate_0", "RefX|adj|na|na" }, + { "horizontalScroll_0", "RefX|adj|na|na" }, + { "leftArrow_0", "na|na|RefY|adj1" }, + { "leftArrow_1", "RefX|adj2|na|na" }, + { "leftArrowCallout_0", "na|na|RefY|adj1" }, + { "leftArrowCallout_1", "na|na|RefY|adj2" }, + { "leftArrowCallout_2", "RefX|adj3|na|na" }, + { "leftArrowCallout_3", "RefX|adj4|na|na" }, + { "leftBrace_0", "na|na|RefY|adj1" }, + { "leftBrace_1", "na|na|RefY|adj2" }, + { "leftBracket_0", "na|na|RefY|adj" }, + { "leftCircularArrow_0", "na|na|RefAngle|adj2" }, + { "leftCircularArrow_1", "na|na|RefAngle|adj4" }, + { "leftCircularArrow_2", "RefR|adj1|RefAngle|adj3" }, + { "leftCircularArrow_3", "RefR|adj5|na|na" }, + { "leftRightArrow_0", "na|na|RefY|adj1" }, + { "leftRightArrow_1", "RefX|adj2|na|na" }, + { "leftRightArrowCallout_0", "na|na|RefY|adj1" }, + { "leftRightArrowCallout_1", "na|na|RefY|adj2" }, + { "leftRightArrowCallout_2", "RefX|adj3|na|na" }, + { "leftRightArrowCallout_3", "RefX|adj4|na|na" }, + { "leftRightCircularArrow_0", "na|na|RefAngle|adj2" }, + { "leftRightCircularArrow_1", "na|na|RefAngle|adj4" }, + { "leftRightCircularArrow_2", "RefR|adj1|RefAngle|adj3" }, + { "leftRightCircularArrow_3", "RefR|adj5|na|na" }, + { "leftRightRibbon_0", "na|na|RefY|adj1" }, + { "leftRightRibbon_1", "RefX|adj2|na|na" }, + { "leftRightRibbon_2", "na|na|RefY|adj3" }, + { "leftRightUpArrow_0", "RefX|adj1|na|na" }, + { "leftRightUpArrow_1", "RefX|adj2|na|na" }, + { "leftRightUpArrow_2", "na|na|RefY|adj3" }, + { "leftUpArrow_0", "na|na|RefY|adj1" }, + { "leftUpArrow_1", "RefX|adj2|na|na" }, + { "leftUpArrow_2", "na|na|RefY|adj3" }, + { "mathDivide_0", "na|na|RefY|adj1" }, + { "mathDivide_1", "na|na|RefY|adj2" }, + { "mathDivide_2", "RefX|adj3|na|na" }, + { "mathEqual_0", "na|na|RefY|adj1" }, + { "mathEqual_1", "na|na|RefY|adj2" }, + { "mathMinus_0", "na|na|RefY|adj1" }, + { "mathMultiply_0", "na|na|RefY|adj1" }, + { "mathNotEqual_0", "na|na|RefY|adj1" }, + { "mathNotEqual_1", "na|na|RefAngle|adj2" }, + { "mathNotEqual_2", "na|na|RefY|adj3" }, + { "mathPlus_0", "na|na|RefY|adj1" }, + { "moon_0", "RefX|adj|na|na" }, + { "nonIsoscelesTrapezoid_0", "RefX|adj1|na|na" }, + { "nonIsoscelesTrapezoid_1", "RefX|adj2|na|na" }, + { "noSmoking_0", "RefR|adj|na|na" }, + { "notchedRightArrow_0", "na|na|RefY|adj1" }, + { "notchedRightArrow_1", "RefX|adj2|na|na" }, + { "octagon_0", "RefX|adj|na|na" }, + { "parallelogram_0", "RefX|adj|na|na" }, + { "pie_0", "na|na|RefAngle|adj1" }, + { "pie_1", "na|na|RefAngle|adj2" }, + { "plaque_0", "RefX|adj|na|na" }, + { "plus_0", "RefX|adj|na|na" }, + { "quadArrow_0", "RefX|adj1|na|na" }, + { "quadArrow_1", "RefX|adj2|na|na" }, + { "quadArrow_2", "na|na|RefY|adj3" }, + { "quadArrowCallout_0", "RefX|adj1|na|na" }, + { "quadArrowCallout_1", "RefX|adj2|na|na" }, + { "quadArrowCallout_2", "na|na|RefY|adj3" }, + { "quadArrowCallout_3", "na|na|RefY|adj4" }, + { "ribbon_0", "na|na|RefY|adj1" }, + { "ribbon_1", "RefX|adj2|na|na" }, + { "ribbon2_0", "na|na|RefY|adj1" }, + { "ribbon2_1", "RefX|adj2|na|na" }, + { "rightArrow_0", "na|na|RefY|adj1" }, + { "rightArrow_1", "RefX|adj2|na|na" }, + { "rightArrowCallout_0", "na|na|RefY|adj1" }, + { "rightArrowCallout_1", "na|na|RefY|adj2" }, + { "rightArrowCallout_2", "RefX|adj3|na|na" }, + { "rightArrowCallout_3", "RefX|adj4|na|na" }, + { "rightBrace_0", "na|na|RefY|adj1" }, + { "rightBrace_1", "na|na|RefY|adj2" }, + { "rightBracket_0", "na|na|RefY|adj" }, + { "round1Rect_0", "RefX|adj|na|na" }, + { "round2DiagRect_0", "RefX|adj1|na|na" }, + { "round2DiagRect_1", "RefX|adj2|na|na" }, + { "round2SameRect_0", "RefX|adj1|na|na" }, + { "round2SameRect_1", "RefX|adj2|na|na" }, + { "roundRect_0", "RefX|adj|na|na" }, + { "smileyFace_0", "na|na|RefY|adj" }, + { "snip1Rect_0", "RefX|adj|na|na" }, + { "snip2DiagRect_0", "RefX|adj1|na|na" }, + { "snip2DiagRect_1", "RefX|adj2|na|na" }, + { "snip2SameRect_0", "RefX|adj1|na|na" }, + { "snip2SameRect_1", "RefX|adj2|na|na" }, + { "snipRoundRect_0", "RefX|adj1|na|na" }, + { "snipRoundRect_1", "RefX|adj2|na|na" }, + { "star10_0", "na|na|RefY|adj" }, + { "star12_0", "na|na|RefY|adj" }, + { "star16_0", "na|na|RefY|adj" }, + { "star24_0", "na|na|RefY|adj" }, + { "star32_0", "na|na|RefY|adj" }, + { "star4_0", "na|na|RefY|adj" }, + { "star5_0", "na|na|RefY|adj" }, + { "star6_0", "na|na|RefY|adj" }, + { "star7_0", "na|na|RefY|adj" }, + { "star8_0", "na|na|RefY|adj" }, + { "stripedRightArrow_0", "na|na|RefY|adj1" }, + { "stripedRightArrow_1", "RefX|adj2|na|na" }, + { "sun_0", "RefX|adj|na|na" }, + { "swooshArrow_0", "na|na|RefY|adj1" }, + { "swooshArrow_1", "RefX|adj2|na|na" }, + { "teardrop_0", "RefX|adj|na|na" }, + { "trapezoid_0", "RefX|adj|na|na" }, + { "triangle_0", "RefX|adj|na|na" }, + { "upArrowCallout_0", "RefX|adj1|na|na" }, + { "upArrowCallout_1", "RefX|adj2|na|na" }, + { "upArrowCallout_2", "na|na|RefY|adj3" }, + { "upArrowCallout_3", "na|na|RefY|adj4" }, + { "upDownArrow_0", "RefX|adj1|na|na" }, + { "upDownArrow_1", "na|na|RefY|adj2" }, + { "upArrow_0", "RefX|adj1|na|na" }, + { "upArrow_1", "na|na|RefY|adj2" }, + { "upDownArrowCallout_0", "RefX|adj1|na|na" }, + { "upDownArrowCallout_1", "RefX|adj2|na|na" }, + { "upDownArrowCallout_2", "na|na|RefY|adj3" }, + { "upDownArrowCallout_3", "na|na|RefY|adj4" }, + { "uturnArrow_0", "RefX|adj1|na|na" }, + { "uturnArrow_1", "RefX|adj2|na|na" }, + { "uturnArrow_2", "na|na|RefY|adj3" }, + { "uturnArrow_3", "RefX|adj4|na|na" }, + { "uturnArrow_4", "na|na|RefY|adj5" }, + { "verticalScroll_0", "na|na|RefY|adj" }, + { "wave_0", "na|na|RefY|adj1" }, + { "wave_1", "RefX|adj2|na|na" }, + { "wedgeEllipseCallout_0", "RefX|adj1|RefY|adj2" }, + { "wedgeRectCallout_0", "RefX|adj1|RefY|adj2" }, + { "wedgeRoundRectCallout_0", "RefX|adj1|RefY|adj2" } }; +} + +static sal_Int32 lcl_getAdjIndexFromToken(const sal_Int32 nTokenPos, std::u16string_view rMapValue) +{ + std::u16string_view sAdjRef = o3tl::getToken(rMapValue, nTokenPos, '|'); + std::u16string_view sNumber; // number part from "adj1", "adj2" etc. + if (o3tl::starts_with(sAdjRef, u"adj", &sNumber)) + { + if (sNumber.empty() || sNumber == u"1") + return 0; + else + return o3tl::toInt32(sNumber) - 1; + } + else + return -1; +} + +void PresetOOXHandleAdj::GetOOXHandleAdjRelation( + std::u16string_view sFullOOXShapeName, const sal_Int32 nHandleIndex, OUString& rFirstRefType, + sal_Int32& rFirstAdjValueIndex, OUString& rSecondRefType, sal_Int32& rSecondAdjValueIndex) +{ + static const HandleAdjRelHashMap s_HashMap = []() { + HandleAdjRelHashMap aH; + aH.reserve(std::size(aHandleAdjRelArray)); + for (const auto& item : aHandleAdjRelArray) + aH.emplace(OUString::createFromAscii(item.sShape_Handle), + OUString::createFromAscii(item.sAdjReferences)); + return aH; + }(); + + std::u16string_view sKey; + OUString sValue; + rFirstRefType = "na"; + rFirstAdjValueIndex = -1; + rSecondRefType = "na"; + rSecondAdjValueIndex = -1; + if (o3tl::starts_with(sFullOOXShapeName, u"ooxml-", &sKey)) + { + HandleAdjRelHashMap::const_iterator aHashIter( + s_HashMap.find(OUString::Concat(sKey) + "_" + OUString::number(nHandleIndex))); + if (aHashIter != s_HashMap.end()) + sValue = (*aHashIter).second; + else + return; + } + else + return; + + rFirstRefType = sValue.getToken(0, '|'); + rFirstAdjValueIndex = lcl_getAdjIndexFromToken(1, sValue); + rSecondRefType = sValue.getToken(2, '|'); + rSecondAdjValueIndex = lcl_getAdjIndexFromToken(3, sValue); + return; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svx/source/svdraw/presetooxhandleadjustmentrelations.hxx b/svx/source/svdraw/presetooxhandleadjustmentrelations.hxx new file mode 100644 index 0000000000..b9404e5987 --- /dev/null +++ b/svx/source/svdraw/presetooxhandleadjustmentrelations.hxx @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#ifndef INCLUDED_SVX_SOURCE_SVDRAW_PRESETOOXHANDLEADJUSTMENTRELATIONS_HXX +#define INCLUDED_SVX_SOURCE_SVDRAW_PRESETOOXHANDLEADJUSTMENTRELATIONS_HXX + +#include <sal/config.h> + +#include <string_view> + +#include <rtl/ustring.hxx> + +namespace PresetOOXHandleAdj +{ +/* This method is used in SdrObjCustomShape::MergeDefaultAttributes() */ +void GetOOXHandleAdjRelation( + std::u16string_view sFullOOXShapeName, /* e.g. "ooxml-circularArrow" */ + const sal_Int32 nHandleIndex, /* index in sequence from property "Handles" */ + OUString& rFirstRefType, /* Propertyname, same as by pptx import, e.g. "RefX" */ + sal_Int32& rFirstAdjValueIndex, /* index in sequence from property "AdjustmentValues" */ + OUString& rSecondRefType, /* Propertyname, same as by pptx import, e.g. "RefY" */ + sal_Int32& rSecondAdjValueIndex /* index in sequence from property "AdjustmentValues" */ +); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svx/source/svdraw/sdrhittesthelper.cxx b/svx/source/svdraw/sdrhittesthelper.cxx new file mode 100644 index 0000000000..9dc3b9118f --- /dev/null +++ b/svx/source/svdraw/sdrhittesthelper.cxx @@ -0,0 +1,175 @@ +/* -*- 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/sdrhittesthelper.hxx> +#include <svx/obj3d.hxx> +#include <svx/helperhittest3d.hxx> +#include <svx/svdpage.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/displayinfo.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <drawinglayer/processor2d/hittestprocessor2d.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdr/contact/viewcontact.hxx> + + +// #i101872# new Object HitTest as View-tooling + +SdrObject* SdrObjectPrimitiveHit( + const SdrObject& rObject, + const Point& rPnt, + const basegfx::B2DVector& rHitTolerance, + const SdrPageView& rSdrPageView, + const SdrLayerIDSet* pVisiLayer, + bool bTextOnly, + drawinglayer::primitive2d::Primitive2DContainer* pHitContainer) +{ + SdrObject* pResult = nullptr; + + if(rObject.GetSubList() && rObject.GetSubList()->GetObjCount()) + { + // group or scene with content. Single 3D objects also have a + // true == rObject.GetSubList(), but no content + pResult = SdrObjListPrimitiveHit(*rObject.GetSubList(), rPnt, rHitTolerance, rSdrPageView, pVisiLayer, bTextOnly); + } + else + { + if( rObject.IsVisible() && (!pVisiLayer || pVisiLayer->IsSet(rObject.GetLayer()))) + { + // single object, 3d object, empty scene or empty group. Check if + // it's a single 3D object + const E3dCompoundObject* pE3dCompoundObject = dynamic_cast< const E3dCompoundObject* >(&rObject); + + if(pE3dCompoundObject) + { + const basegfx::B2DPoint aHitPosition(rPnt.X(), rPnt.Y()); + + if(checkHitSingle3DObject(aHitPosition, *pE3dCompoundObject)) + { + pResult = const_cast< E3dCompoundObject* >(pE3dCompoundObject); + } + } + else + { + // not a single 3D object; Check in first PageWindow using primitives (only SC + // with split views uses multiple PageWindows nowadays) + if(rSdrPageView.PageWindowCount()) + { + const basegfx::B2DPoint aHitPosition(rPnt.X(), rPnt.Y()); + const sdr::contact::ViewObjectContact& rVOC = rObject.GetViewContact().GetViewObjectContact( + rSdrPageView.GetPageWindow(0)->GetObjectContact()); + + if(ViewObjectContactPrimitiveHit(rVOC, aHitPosition, rHitTolerance, bTextOnly, pHitContainer)) + { + pResult = const_cast< SdrObject* >(&rObject); + } + } + } + } + } + + return pResult; +} + + +SdrObject* SdrObjListPrimitiveHit( + const SdrObjList& rList, + const Point& rPnt, + const basegfx::B2DVector& rHitTolerance, + const SdrPageView& rSdrPageView, + const SdrLayerIDSet* pVisiLayer, + bool bTextOnly) +{ + size_t nObjNum(rList.GetObjCount()); + SdrObject* pRetval = nullptr; + + while(!pRetval && nObjNum > 0) + { + nObjNum--; + SdrObject* pObj = rList.GetObj(nObjNum); + + pRetval = SdrObjectPrimitiveHit(*pObj, rPnt, rHitTolerance, rSdrPageView, pVisiLayer, bTextOnly); + } + + return pRetval; +} + + +bool ViewObjectContactPrimitiveHit( + const sdr::contact::ViewObjectContact& rVOC, + const basegfx::B2DPoint& rHitPosition, + const basegfx::B2DVector& rLogicHitTolerance, + bool bTextOnly, + drawinglayer::primitive2d::Primitive2DContainer* pHitContainer) +{ + basegfx::B2DRange aObjectRange(rVOC.getObjectRange()); + + if(!aObjectRange.isEmpty()) + { + // first do a rough B2DRange based HitTest; do not forget to + // include the HitTolerance if given + if(rLogicHitTolerance.getX() > 0 || rLogicHitTolerance.getY() > 0) + { + aObjectRange.grow(rLogicHitTolerance); + } + + if(aObjectRange.isInside(rHitPosition)) + { + // get primitive sequence + sdr::contact::DisplayInfo aDisplayInfo; + // have to make a copy of this container here, because it might be changed underneath us + const drawinglayer::primitive2d::Primitive2DContainer aSequence(rVOC.getPrimitive2DSequence(aDisplayInfo)); + + if(!aSequence.empty()) + { + // create a HitTest processor + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D = rVOC.GetObjectContact().getViewInformation2D(); + drawinglayer::processor2d::HitTestProcessor2D aHitTestProcessor2D( + rViewInformation2D, + rHitPosition, + rLogicHitTolerance, + bTextOnly); + + // ask for HitStack + aHitTestProcessor2D.collectHitStack(pHitContainer != nullptr); + + // feed it with the primitives + aHitTestProcessor2D.process(aSequence); + + // deliver result + if (aHitTestProcessor2D.getHit()) + { + if (pHitContainer) + { + // fetch HitStack primitives if requested + *pHitContainer = aHitTestProcessor2D.getHitStack(); + } + + return true; + } + } + } + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/sdrmasterpagedescriptor.cxx b/svx/source/svdraw/sdrmasterpagedescriptor.cxx new file mode 100644 index 0000000000..1d08439189 --- /dev/null +++ b/svx/source/svdraw/sdrmasterpagedescriptor.cxx @@ -0,0 +1,104 @@ +/* -*- 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/sdrmasterpagedescriptor.hxx> +#include <sdr/contact/viewcontactofmasterpagedescriptor.hxx> +#include <svx/svdpage.hxx> +#include <svx/xdef.hxx> +#include <svx/xfillit0.hxx> +#include <svl/itemset.hxx> + +using namespace com::sun::star; + +namespace sdr +{ + MasterPageDescriptor::MasterPageDescriptor(SdrPage& aOwnerPage, SdrPage& aUsedPage) + : maOwnerPage(aOwnerPage), + maUsedPage(aUsedPage) + { + // all layers visible + maVisibleLayers.SetAll(); + + // register at used page + maUsedPage.AddPageUser(*this); + } + + MasterPageDescriptor::~MasterPageDescriptor() + { + // de-register at used page + maUsedPage.RemovePageUser(*this); + + mpViewContact.reset(); + } + + // ViewContact part + sdr::contact::ViewContact& MasterPageDescriptor::GetViewContact() const + { + if(!mpViewContact) + { + mpViewContact.reset( + new sdr::contact::ViewContactOfMasterPageDescriptor(*const_cast< MasterPageDescriptor* >(this)) ); + } + + return *mpViewContact; + } + + // this method is called from the destructor of the referenced page. + // do all necessary action to forget the page. It is not necessary to call + // RemovePageUser(), that is done from the destructor. + void MasterPageDescriptor::PageInDestruction(const SdrPage& /*rPage*/) + { + maOwnerPage.TRG_ClearMasterPage(); + } + + void MasterPageDescriptor::SetVisibleLayers(const SdrLayerIDSet& rNew) + { + if(rNew != maVisibleLayers) + { + maVisibleLayers = rNew; + GetViewContact().ActionChanged(); + } + } + + + const SdrPageProperties* MasterPageDescriptor::getCorrectSdrPageProperties() const + { + const SdrPage* pCorrectPage = &GetOwnerPage(); + const SdrPageProperties* pCorrectProperties = &pCorrectPage->getSdrPageProperties(); + + if(drawing::FillStyle_NONE == pCorrectProperties->GetItemSet().Get(XATTR_FILLSTYLE).GetValue()) + { + pCorrectPage = &GetUsedPage(); + pCorrectProperties = &pCorrectPage->getSdrPageProperties(); + } + + if(pCorrectPage->IsMasterPage() && !pCorrectProperties->GetStyleSheet()) + { + // #i110846# Suppress SdrPage FillStyle for MasterPages without StyleSheets, + // else the PoolDefault (XFILL_COLOR and Blue8) will be used. Normally, all + // MasterPages should have a StyleSheet exactly for this reason, but historically + // e.g. the Notes MasterPage has no StyleSheet set (and there maybe others). + pCorrectProperties = nullptr; + } + + return pCorrectProperties; + } +} // end of namespace sdr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/sdrpagewindow.cxx b/svx/source/svdraw/sdrpagewindow.cxx new file mode 100644 index 0000000000..fa8e5f4d7f --- /dev/null +++ b/svx/source/svdraw/sdrpagewindow.cxx @@ -0,0 +1,533 @@ +/* -*- 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/sdrpagewindow.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdr/contact/objectcontactofpageview.hxx> +#include <svx/sdr/contact/displayinfo.hxx> +#include <svx/fmview.hxx> +#include <sfx2/lokhelper.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <tools/debug.hxx> +#include <vcl/window.hxx> + +using namespace ::com::sun::star; + +struct SdrPageWindow::Impl +{ + // #110094# ObjectContact section + mutable sdr::contact::ObjectContact* mpObjectContact; + + // the SdrPageView this window belongs to + SdrPageView& mrPageView; + + // the PaintWindow to paint on. Here is access to OutDev etc. + // #i72752# change to pointer to allow patcing it in DrawLayer() if necessary + SdrPaintWindow* mpPaintWindow; + SdrPaintWindow* mpOriginalPaintWindow; + + // UNO stuff for xControls + uno::Reference<awt::XControlContainer> mxControlContainer; + + Impl( SdrPageView& rPageView, SdrPaintWindow& rPaintWindow ) : + mpObjectContact(nullptr), + mrPageView(rPageView), + mpPaintWindow(&rPaintWindow), + mpOriginalPaintWindow(nullptr) + { + } +}; + + +uno::Reference<awt::XControlContainer> const & SdrPageWindow::GetControlContainer( bool _bCreateIfNecessary ) const +{ + if (!mpImpl->mxControlContainer.is() && _bCreateIfNecessary) + { + SdrView& rView = GetPageView().GetView(); + + const SdrPaintWindow& rPaintWindow( GetOriginalPaintWindow() ? *GetOriginalPaintWindow() : GetPaintWindow() ); + if ( rPaintWindow.OutputToWindow() && !rView.IsPrintPreview() ) + { + vcl::Window* pWindow = rPaintWindow.GetOutputDevice().GetOwnerWindow(); + const_cast< SdrPageWindow* >( this )->mpImpl->mxControlContainer = VCLUnoHelper::CreateControlContainer( pWindow ); + + // #100394# xC->setVisible triggers window->Show() and this has + // problems when the view is not completely constructed which may + // happen when loading. This leads to accessibility broadcasts which + // throw asserts due to the not finished view. All this chain can be avoided + // since xC->setVisible is here called only for the side effect in + // UnoControlContainer::setVisible(...) which calls createPeer(...). + // This will now be called directly from here. + + uno::Reference< awt::XControl > xControl(mpImpl->mxControlContainer, uno::UNO_QUERY); + if(xControl.is()) + { + uno::Reference< uno::XInterface > xContext = xControl->getContext(); + if(!xContext.is()) + { + xControl->createPeer( uno::Reference<awt::XToolkit>(), uno::Reference<awt::XWindowPeer>() ); + } + } + } + else + { + // Printer and VirtualDevice, or rather: no OutDev + uno::Reference< lang::XMultiServiceFactory > xFactory( ::comphelper::getProcessServiceFactory() ); + const_cast< SdrPageWindow* >( this )->mpImpl->mxControlContainer.set(xFactory->createInstance("com.sun.star.awt.UnoControlContainer"), uno::UNO_QUERY); + uno::Reference< awt::XControlModel > xModel(xFactory->createInstance("com.sun.star.awt.UnoControlContainerModel"), uno::UNO_QUERY); + uno::Reference< awt::XControl > xControl(mpImpl->mxControlContainer, uno::UNO_QUERY); + if (xControl.is()) + xControl->setModel(xModel); + + OutputDevice& rOutDev = rPaintWindow.GetOutputDevice(); + Point aPosPix = rOutDev.GetMapMode().GetOrigin(); + Size aSizePix = rOutDev.GetOutputSizePixel(); + + uno::Reference< awt::XWindow > xContComp(mpImpl->mxControlContainer, uno::UNO_QUERY); + if( xContComp.is() ) + xContComp->setPosSize(aPosPix.X(), aPosPix.Y(), aSizePix.Width(), aSizePix.Height(), awt::PosSize::POSSIZE); + } + + FmFormView* pViewAsFormView = dynamic_cast< FmFormView* >( &rView ); + if ( pViewAsFormView ) + pViewAsFormView->InsertControlContainer(mpImpl->mxControlContainer); + } + return mpImpl->mxControlContainer; +} + +SdrPageWindow::SdrPageWindow(SdrPageView& rPageView, SdrPaintWindow& rPaintWindow) : + mpImpl(new Impl(rPageView, rPaintWindow)) +{ +} + +SdrPageWindow::~SdrPageWindow() +{ + // #i26631# + ResetObjectContact(); + + if (!mpImpl->mxControlContainer.is()) + return; + + auto & rView = static_cast<SdrPaintView &>(GetPageView().GetView()); + + // notify derived views + FmFormView* pViewAsFormView = dynamic_cast< FmFormView* >( &rView ); + if ( pViewAsFormView ) + pViewAsFormView->RemoveControlContainer(mpImpl->mxControlContainer); + + // dispose the control container + uno::Reference< lang::XComponent > xComponent(mpImpl->mxControlContainer, uno::UNO_QUERY); + xComponent->dispose(); +} + +SdrPageView& SdrPageWindow::GetPageView() const +{ + return mpImpl->mrPageView; +} + +SdrPaintWindow& SdrPageWindow::GetPaintWindow() const +{ + return *mpImpl->mpPaintWindow; +} + +const SdrPaintWindow* SdrPageWindow::GetOriginalPaintWindow() const +{ + return mpImpl->mpOriginalPaintWindow; +} + +// OVERLAY MANAGER +rtl::Reference< sdr::overlay::OverlayManager > const & SdrPageWindow::GetOverlayManager() const +{ + return GetPaintWindow().GetOverlayManager(); +} + +SdrPaintWindow* SdrPageWindow::patchPaintWindow(SdrPaintWindow& rPaintWindow) +{ + if (!mpImpl) + return nullptr; + + if (!mpImpl->mpOriginalPaintWindow) + { + // first patch + mpImpl->mpOriginalPaintWindow = mpImpl->mpPaintWindow; + mpImpl->mpPaintWindow = &rPaintWindow; + mpImpl->mpOriginalPaintWindow->setPatched(&rPaintWindow); + return mpImpl->mpOriginalPaintWindow; + } + else + { + // second or more patch + auto pPreviousPaintWindow = mpImpl->mpPaintWindow; + mpImpl->mpPaintWindow = &rPaintWindow; + mpImpl->mpOriginalPaintWindow->setPatched(&rPaintWindow); + return pPreviousPaintWindow; + } +} + +void SdrPageWindow::unpatchPaintWindow(SdrPaintWindow* pPreviousPaintWindow) +{ + if (pPreviousPaintWindow == mpImpl->mpOriginalPaintWindow) + { + // first patch + mpImpl->mpPaintWindow = mpImpl->mpOriginalPaintWindow; + mpImpl->mpOriginalPaintWindow->setPatched(nullptr); + mpImpl->mpOriginalPaintWindow = nullptr; + } + else + { + // second or more patch + mpImpl->mpPaintWindow = pPreviousPaintWindow; + mpImpl->mpOriginalPaintWindow->setPatched(pPreviousPaintWindow); + } +} + +void SdrPageWindow::PrePaint() +{ + // give OC the chance to do ProcessDisplay preparations + if(HasObjectContact()) + { + GetObjectContact().PrepareProcessDisplay(); + } +} + +void SdrPageWindow::PrepareRedraw(const vcl::Region& rReg) +{ + // give OC the chance to do ProcessDisplay preparations + if(HasObjectContact()) + { + GetObjectContact().PrepareProcessDisplay(); + } + + // if necessary, remember changed RedrawArea at PaintWindow for usage with + // overlay and PreRenderDevice stuff + GetPaintWindow().SetRedrawRegion(rReg); +} + + +// clip test +#ifdef CLIPPER_TEST +#include <svx/svdopath.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <tools/helpers.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> + +// for ::std::sort +#include <algorithm> + +namespace +{ + void impPaintStrokePolygon(const basegfx::B2DPolygon& rCandidate, OutputDevice& rOutDev, Color aColor) + { + basegfx::B2DPolygon aCandidate(rCandidate); + + if(aCandidate.areControlPointsUsed()) + { + aCandidate = basegfx::utils::adaptiveSubdivideByAngle(rCandidate); + } + + if(aCandidate.count()) + { + const sal_uInt32 nLoopCount(aCandidate.isClosed() ? aCandidate.count() : aCandidate.count() - 1); + rOutDev.SetFillColor(); + rOutDev.SetLineColor(aColor); + + for(sal_uInt32 a(0); a < nLoopCount; a++) + { + const basegfx::B2DPoint aBStart(aCandidate.getB2DPoint(a)); + const basegfx::B2DPoint aBEnd(aCandidate.getB2DPoint((a + 1) % aCandidate.count())); + const Point aStart(FRound(aBStart.getX()), FRound(aBStart.getY())); + const Point aEnd(FRound(aBEnd.getX()), FRound(aBEnd.getY())); + rOutDev.DrawLine(aStart, aEnd); + } + } + } + + void impTryTest(const SdrPageView& rPageView, OutputDevice& rOutDev) + { + if(rPageView.GetPage() && rPageView.GetPage()->GetObjCount() >= 2) + { + SdrPage* pPage = rPageView.GetPage(); + SdrObject* pObjA = pPage->GetObj(0); + + if(dynamic_cast<const SdrPathObj*>( pObjA)) + { + basegfx::B2DPolyPolygon aPolyA(pObjA->GetPathPoly()); + aPolyA = basegfx::utils::correctOrientations(aPolyA); + + basegfx::B2DPolyPolygon aPolyB; + + for (const rtl::Reference<SdrObject>& pObjB : *rPageView.GetPage()) + { + if(dynamic_cast<const SdrPathObj*>( pObjB.get())) + { + basegfx::B2DPolyPolygon aCandidate(pObjB->GetPathPoly()); + aCandidate = basegfx::utils::correctOrientations(aCandidate); + aPolyB.append(aCandidate); + } + } + + if(aPolyA.count() && aPolyA.isClosed() && aPolyB.count()) + { + // poly A is the clipregion, clip poly b against it. Algo depends on + // poly b being closed. + basegfx::B2DPolyPolygon aResult(basegfx::utils::clipPolyPolygonOnPolyPolygon(aPolyB, aPolyA)); + + for(auto const& rPolygon : aResult) + { + int nR = comphelper::rng::uniform_int_distribution(0, 254); + int nG = comphelper::rng::uniform_int_distribution(0, 254); + int nB = comphelper::rng::uniform_int_distribution(0, 254); + Color aColor(nR, nG, nB); + impPaintStrokePolygon(rPolygon, rOutDev, aColor); + } + } + } + } + } +} // end of anonymous namespace +#endif // CLIPPER_TEST + + +void SdrPageWindow::RedrawAll( sdr::contact::ViewObjectContactRedirector* pRedirector ) +{ + // set Redirector + GetObjectContact().SetViewObjectContactRedirector(pRedirector); + + // set PaintingPageView + const SdrView& rView = mpImpl->mrPageView.GetView(); + SdrModel& rModel = rView.GetModel(); + + // get to be processed layers + const bool bPrinter(GetPaintWindow().OutputToPrinter()); + SdrLayerIDSet aProcessLayers = bPrinter ? mpImpl->mrPageView.GetPrintableLayers() : mpImpl->mrPageView.GetVisibleLayers(); + + // create PaintInfoRec; use Rectangle only temporarily + const vcl::Region& rRegion = GetPaintWindow().GetRedrawRegion(); + + // create processing data + sdr::contact::DisplayInfo aDisplayInfo; + + // Draw all layers. do NOT draw form layer from CompleteRedraw, this is done separately + // as a single layer paint + const SdrLayerAdmin& rLayerAdmin = rModel.GetLayerAdmin(); + const SdrLayerID nControlLayerId = rLayerAdmin.GetLayerID(rLayerAdmin.GetControlLayerName()); + aProcessLayers.Clear(nControlLayerId); + + // still something to paint? + if(!aProcessLayers.IsEmpty()) + { + aDisplayInfo.SetProcessLayers(aProcessLayers); + + // Set region as redraw area + aDisplayInfo.SetRedrawArea(rRegion); + + // paint page + GetObjectContact().ProcessDisplay(aDisplayInfo); + } + + // reset redirector + GetObjectContact().SetViewObjectContactRedirector(nullptr); + + // LineClip test +#ifdef CLIPPER_TEST + if(true) + { + impTryTest(GetPageView(), GetPaintWindow().GetOutputDevice()); + } +#endif // CLIPPER_TEST +} + +void SdrPageWindow::RedrawLayer(const SdrLayerID* pId, + sdr::contact::ViewObjectContactRedirector* pRedirector, + basegfx::B2IRectangle const*const pPageFrame) +{ + // set redirector + GetObjectContact().SetViewObjectContactRedirector(pRedirector); + + // set PaintingPageView + const SdrView& rView = mpImpl->mrPageView.GetView(); + SdrModel& rModel = rView.GetModel(); + + // get the layers to process + const bool bPrinter(GetPaintWindow().OutputToPrinter()); + SdrLayerIDSet aProcessLayers = bPrinter ? mpImpl->mrPageView.GetPrintableLayers() : mpImpl->mrPageView.GetVisibleLayers(); + + // is the given layer visible at all? + if(aProcessLayers.IsSet(*pId)) + { + // find out if we are painting the ControlLayer + const SdrLayerAdmin& rLayerAdmin = rModel.GetLayerAdmin(); + const SdrLayerID nControlLayerId = rLayerAdmin.GetLayerID(rLayerAdmin.GetControlLayerName()); + const bool bControlLayerProcessingActive(nControlLayerId == *pId); + + // create PaintInfoRec, use Rectangle only temporarily + const vcl::Region& rRegion = GetPaintWindow().GetRedrawRegion(); + + // create processing data + sdr::contact::DisplayInfo aDisplayInfo; + + // is it the control layer? If Yes, set flag + aDisplayInfo.SetControlLayerProcessingActive(bControlLayerProcessingActive); + + // Draw just the one given layer + aProcessLayers.ClearAll(); + aProcessLayers.Set(*pId); + + aDisplayInfo.SetProcessLayers(aProcessLayers); + + // Set region as redraw area + aDisplayInfo.SetRedrawArea(rRegion); + + // Writer or calc, coming from original RedrawOneLayer. + // #i72889# no page painting or MasterPage painting for layer painting + const bool bOldPageDecorationAllowed(GetPageView().GetView().IsPageDecorationAllowed()); + const bool bOldMasterPageVisualizationAllowed(GetPageView().GetView().IsMasterPageVisualizationAllowed()); + GetPageView().GetView().SetPageDecorationAllowed(false); + GetPageView().GetView().SetMasterPageVisualizationAllowed(false); + + if (pPageFrame) // Writer page frame for anchor based clipping + { + aDisplayInfo.SetWriterPageFrame(*pPageFrame); + } + + // paint page + GetObjectContact().ProcessDisplay(aDisplayInfo); + + // reset temporarily changed flags + GetPageView().GetView().SetPageDecorationAllowed(bOldPageDecorationAllowed); + GetPageView().GetView().SetMasterPageVisualizationAllowed(bOldMasterPageVisualizationAllowed); + } + + // reset redirector + GetObjectContact().SetViewObjectContactRedirector(nullptr); +} + +// Invalidate call, used from ObjectContact(OfPageView) in InvalidatePartOfView(...) +void SdrPageWindow::InvalidatePageWindow(const basegfx::B2DRange& rRange) +{ + if (GetPageView().IsVisible() && GetPaintWindow().OutputToWindow()) + { + OutputDevice& rWindow(GetPaintWindow().GetOutputDevice()); + basegfx::B2DRange aDiscreteRange(rRange); + aDiscreteRange.transform(rWindow.GetViewTransformation()); + + if (SvtOptionsDrawinglayer::IsAntiAliasing()) + { + // invalidate one discrete unit more under the assumption that AA + // needs one pixel more + aDiscreteRange.grow(1.0); + } + + // If the shapes use negative X coordinates, make them positive before sending + // the invalidation rectangle. + bool bNegativeX = mpImpl->mrPageView.GetView().IsNegativeX(); + + const tools::Rectangle aVCLDiscreteRectangle( + static_cast<tools::Long>(bNegativeX ? std::max(0.0, ceil(-aDiscreteRange.getMaxX())) : floor(aDiscreteRange.getMinX())), + static_cast<tools::Long>(floor(aDiscreteRange.getMinY())), + static_cast<tools::Long>(bNegativeX ? std::max(0.0, floor(-aDiscreteRange.getMinX())) : ceil(aDiscreteRange.getMaxX())), + static_cast<tools::Long>(ceil(aDiscreteRange.getMaxY()))); + + const bool bWasMapModeEnabled(rWindow.IsMapModeEnabled()); + rWindow.EnableMapMode(false); + GetPageView().GetView().InvalidateOneWin(rWindow, aVCLDiscreteRectangle); + rWindow.EnableMapMode(bWasMapModeEnabled); + } + else if (comphelper::LibreOfficeKit::isActive()) + { + // we don't really have to have a paint window with LOK; OTOH we know + // that the drawinglayer units are 100ths of mm, so they are easy to + // convert to twips + + // If the shapes use negative X coordinates, make them positive before sending + // the invalidation rectangle. + bool bNegativeX = mpImpl->mrPageView.GetView().IsNegativeX(); + const tools::Rectangle aRect100thMM( + static_cast<tools::Long>(bNegativeX ? std::max(0.0, ceil(-rRange.getMaxX())) : floor(rRange.getMinX())), + static_cast<tools::Long>(floor(rRange.getMinY())), + static_cast<tools::Long>(bNegativeX ? std::max(0.0, floor(-rRange.getMinX())) : ceil(rRange.getMaxX())), + static_cast<tools::Long>(ceil(rRange.getMaxY()))); + + const tools::Rectangle aRectTwips = o3tl::convert(aRect100thMM, o3tl::Length::mm100, o3tl::Length::twip); + + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + SfxLokHelper::notifyInvalidation(pViewShell, &aRectTwips); + } +} + +// ObjectContact section +const sdr::contact::ObjectContact& SdrPageWindow::GetObjectContact() const +{ + if (!mpImpl->mpObjectContact) + { + mpImpl->mpObjectContact = GetPageView().GetView().createViewSpecificObjectContact( + const_cast<SdrPageWindow&>(*this), + "svx::svdraw::SdrPageWindow mpObjectContact"); + } + + return *mpImpl->mpObjectContact; +} + +sdr::contact::ObjectContact& SdrPageWindow::GetObjectContact() +{ + if (!mpImpl->mpObjectContact) + { + mpImpl->mpObjectContact = GetPageView().GetView().createViewSpecificObjectContact( + *this, + "svx::svdraw::SdrPageWindow mpObjectContact" ); + } + + return *mpImpl->mpObjectContact; +} + +bool SdrPageWindow::HasObjectContact() const +{ + return mpImpl->mpObjectContact != nullptr; +} + +// #i26631# +void SdrPageWindow::ResetObjectContact() +{ + if (mpImpl->mpObjectContact) + { + delete mpImpl->mpObjectContact; + mpImpl->mpObjectContact = nullptr; + } +} + +void SdrPageWindow::SetDesignMode( bool _bDesignMode ) const +{ + const sdr::contact::ObjectContactOfPageView* pOC = dynamic_cast< const sdr::contact::ObjectContactOfPageView* >( &GetObjectContact() ); + DBG_ASSERT( pOC, "SdrPageWindow::SetDesignMode: invalid object contact!" ); + if ( pOC ) + pOC->SetUNOControlsDesignMode( _bDesignMode ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/sdrpaintwindow.cxx b/svx/source/svdraw/sdrpaintwindow.cxx new file mode 100644 index 0000000000..ebed55326a --- /dev/null +++ b/svx/source/svdraw/sdrpaintwindow.cxx @@ -0,0 +1,337 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <osl/diagnose.h> +#include <svx/sdrpaintwindow.hxx> +#include <sdr/overlay/overlaymanagerbuffered.hxx> +#include <svx/svdpntv.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <set> +#include <vector> + +namespace { + +//rhbz#1007697 do this in two loops, one to collect the candidates +//and another to update them because updating a candidate can +//trigger the candidate to be deleted, so asking for its +//sibling after that is going to fail hard +class CandidateMgr +{ + std::vector<VclPtr<vcl::Window> > m_aCandidates; + std::set<VclPtr<vcl::Window> > m_aDeletedCandidates; + DECL_LINK(WindowEventListener, VclWindowEvent&, void); +public: + void PaintTransparentChildren(vcl::Window const & rWindow, tools::Rectangle const& rPixelRect); + ~CandidateMgr(); +}; + +} + +IMPL_LINK(CandidateMgr, WindowEventListener, VclWindowEvent&, rEvent, void) +{ + vcl::Window* pWindow = rEvent.GetWindow(); + if (rEvent.GetId() == VclEventId::ObjectDying) + { + m_aDeletedCandidates.insert(pWindow); + } +} + +CandidateMgr::~CandidateMgr() +{ + for (VclPtr<vcl::Window>& pCandidate : m_aCandidates) + { + if (m_aDeletedCandidates.find(pCandidate) != m_aDeletedCandidates.end()) + continue; + pCandidate->RemoveEventListener(LINK(this, CandidateMgr, WindowEventListener)); + } +} + +void PaintTransparentChildren(vcl::Window const & rWindow, tools::Rectangle const& rPixelRect) +{ + if (!rWindow.IsChildTransparentModeEnabled()) + return; + + CandidateMgr aManager; + aManager.PaintTransparentChildren(rWindow, rPixelRect); +} + +void CandidateMgr::PaintTransparentChildren(vcl::Window const & rWindow, tools::Rectangle const& rPixelRect) +{ + vcl::Window * pCandidate = rWindow.GetWindow( GetWindowType::FirstChild ); + while (pCandidate) + { + if (pCandidate->IsPaintTransparent()) + { + const tools::Rectangle aCandidatePosSizePixel( + pCandidate->GetPosPixel(), + pCandidate->GetSizePixel()); + + if (aCandidatePosSizePixel.Overlaps(rPixelRect)) + { + m_aCandidates.emplace_back(pCandidate); + pCandidate->AddEventListener(LINK(this, CandidateMgr, WindowEventListener)); + } + } + pCandidate = pCandidate->GetWindow( GetWindowType::Next ); + } + + for (const auto& rpCandidate : m_aCandidates) + { + pCandidate = rpCandidate.get(); + if (m_aDeletedCandidates.find(pCandidate) != m_aDeletedCandidates.end()) + continue; + //rhbz#1007697 this can cause the window itself to be + //deleted. So we are listening to see if that happens + //and if so, then skip the update + pCandidate->Invalidate(InvalidateFlags::NoTransparent|InvalidateFlags::Children); + // important: actually paint the child here! + if (m_aDeletedCandidates.find(pCandidate) != m_aDeletedCandidates.end()) + continue; + pCandidate->PaintImmediately(); + } +} + +SdrPreRenderDevice::SdrPreRenderDevice(OutputDevice& rOriginal) +: mpOutputDevice(&rOriginal), + mpPreRenderDevice(VclPtr<VirtualDevice>::Create()) +{ +} + +SdrPreRenderDevice::~SdrPreRenderDevice() +{ + mpPreRenderDevice.disposeAndClear(); +} + +void SdrPreRenderDevice::PreparePreRenderDevice() +{ + // compare size of mpPreRenderDevice with size of visible area + if(mpPreRenderDevice->GetOutputSizePixel() != mpOutputDevice->GetOutputSizePixel()) + { + mpPreRenderDevice->SetOutputSizePixel(mpOutputDevice->GetOutputSizePixel()); + } + + // Also compare the MapModes for zoom/scroll changes + if(mpPreRenderDevice->GetMapMode() != mpOutputDevice->GetMapMode()) + { + mpPreRenderDevice->SetMapMode(mpOutputDevice->GetMapMode()); + } + + // #i29186# + mpPreRenderDevice->SetDrawMode(mpOutputDevice->GetDrawMode()); + mpPreRenderDevice->SetSettings(mpOutputDevice->GetSettings()); +} + +void SdrPreRenderDevice::OutputPreRenderDevice(const vcl::Region& rExpandedRegion) +{ + // region to pixels + const vcl::Region aRegionPixel(mpOutputDevice->LogicToPixel(rExpandedRegion)); + //RegionHandle aRegionHandle(aRegionPixel.BeginEnumRects()); + //Rectangle aRegionRectanglePixel; + + // MapModes off + bool bMapModeWasEnabledDest(mpOutputDevice->IsMapModeEnabled()); + bool bMapModeWasEnabledSource(mpPreRenderDevice->IsMapModeEnabled()); + mpOutputDevice->EnableMapMode(false); + mpPreRenderDevice->EnableMapMode(false); + + RectangleVector aRectangles; + aRegionPixel.GetRegionRectangles(aRectangles); + + for(const auto& rRect : aRectangles) + { + // for each rectangle, copy the area + const Point aTopLeft(rRect.TopLeft()); + const Size aSize(rRect.GetSize()); + + mpOutputDevice->DrawOutDev( + aTopLeft, aSize, + aTopLeft, aSize, + *mpPreRenderDevice); + } + + mpOutputDevice->EnableMapMode(bMapModeWasEnabledDest); + mpPreRenderDevice->EnableMapMode(bMapModeWasEnabledSource); +} + +void SdrPaintView::InitOverlayManager(rtl::Reference<sdr::overlay::OverlayManager> xOverlayManager) +{ + Color aColA(SvtOptionsDrawinglayer::GetStripeColorA()); + Color aColB(SvtOptionsDrawinglayer::GetStripeColorB()); + + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + aColA = aColB = Application::GetSettings().GetStyleSettings().GetHighlightColor(); + aColB.Invert(); + } + + xOverlayManager->setStripeColorA(aColA); + xOverlayManager->setStripeColorB(aColB); + xOverlayManager->setStripeLengthPixel(SvtOptionsDrawinglayer::GetStripeLength()); +} + +rtl::Reference<sdr::overlay::OverlayManager> SdrPaintView::CreateOverlayManager(OutputDevice& rOutputDevice) const +{ + rtl::Reference<sdr::overlay::OverlayManager> xOverlayManager; + // is it a window? + if (OUTDEV_WINDOW == rOutputDevice.GetOutDevType()) + { + vcl::Window* pWindow = rOutputDevice.GetOwnerWindow(); + // decide which OverlayManager to use + if (IsBufferedOverlayAllowed() && !pWindow->SupportsDoubleBuffering()) + { + // buffered OverlayManager, buffers its background and refreshes from there + // for pure overlay changes (no system redraw). The 3rd parameter specifies + // whether that refresh itself will use a 2nd vdev to avoid flickering. + // Also hand over the old OverlayManager if existent; this means to take over + // the registered OverlayObjects from it + xOverlayManager = sdr::overlay::OverlayManagerBuffered::create(rOutputDevice); + } + else + { + // unbuffered OverlayManager, just invalidates places where changes + // take place + // Also hand over the old OverlayManager if existent; this means to take over + // the registered OverlayObjects from it + xOverlayManager = sdr::overlay::OverlayManager::create(rOutputDevice); + } + + OSL_ENSURE(xOverlayManager.is(), "SdrPaintWindow::SdrPaintWindow: Could not allocate an overlayManager (!)"); + + // Request a repaint so that the buffered overlay manager fills + // its buffer properly. This is a workaround for missing buffer + // updates. + if (!comphelper::LibreOfficeKit::isActive()) + { + pWindow->Invalidate(); + } + + InitOverlayManager(xOverlayManager); + } + return xOverlayManager; +} + +void SdrPaintWindow::impCreateOverlayManager() +{ + // not yet one created? + if(!mxOverlayManager.is()) + mxOverlayManager = mrPaintView.CreateOverlayManager(GetOutputDevice()); +} + +SdrPaintWindow::SdrPaintWindow(SdrPaintView& rNewPaintView, OutputDevice& rOut, vcl::Window* pWindow) +: mpOutputDevice(&rOut), + mpWindow(pWindow), + mrPaintView(rNewPaintView), + mbTemporaryTarget(false), // #i72889# + mbOutputToWindow(OUTDEV_WINDOW == mpOutputDevice->GetOutDevType()), + mpPatched(nullptr) +{ +} + +SdrPaintWindow::~SdrPaintWindow() +{ + mxOverlayManager.clear(); + + mpPreRenderDevice.reset(); +} + +rtl::Reference< sdr::overlay::OverlayManager > const & SdrPaintWindow::GetOverlayManager() const +{ + if(!mxOverlayManager.is()) + { + // Create buffered overlay manager by default. + const_cast< SdrPaintWindow* >(this)->impCreateOverlayManager(); + } + + return mxOverlayManager; +} + +tools::Rectangle SdrPaintWindow::GetVisibleArea() const +{ + Size aVisSizePixel(GetOutputDevice().GetOutputSizePixel()); + return GetOutputDevice().PixelToLogic(tools::Rectangle(Point(0,0), aVisSizePixel)); +} + +bool SdrPaintWindow::OutputToRecordingMetaFile() const +{ + GDIMetaFile* pMetaFile = mpOutputDevice->GetConnectMetaFile(); + return (pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause()); +} + +void SdrPaintWindow::PreparePreRenderDevice() +{ + const bool bPrepareBufferedOutput( + mrPaintView.IsBufferedOutputAllowed() + && !OutputToPrinter() + && !mpOutputDevice->IsVirtual() + && !OutputToRecordingMetaFile()); + + if(bPrepareBufferedOutput) + { + if(!mpPreRenderDevice) + { + mpPreRenderDevice.reset(new SdrPreRenderDevice(*mpOutputDevice)); + } + mpPreRenderDevice->PreparePreRenderDevice(); + } + else + { + mpPreRenderDevice.reset(); + } +} + +void SdrPaintWindow::OutputPreRenderDevice(const vcl::Region& rExpandedRegion) +{ + if(mpPreRenderDevice) + { + mpPreRenderDevice->OutputPreRenderDevice(rExpandedRegion); + } +} + +// #i73602# add flag if buffer shall be used +void SdrPaintWindow::DrawOverlay(const vcl::Region& rRegion) +{ + // ## force creation of OverlayManager since the first repaint needs to + // save the background to get a controlled start into overlay mechanism + impCreateOverlayManager(); + + if(mxOverlayManager.is() && !OutputToPrinter()) + { + if(mpPreRenderDevice) + { + mxOverlayManager->completeRedraw(rRegion, &mpPreRenderDevice->GetPreRenderDevice()); + } + else + { + mxOverlayManager->completeRedraw(rRegion); + } + } +} + + +void SdrPaintWindow::SetRedrawRegion(const vcl::Region& rNew) +{ + maRedrawRegion = rNew; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/sdrundomanager.cxx b/svx/source/svdraw/sdrundomanager.cxx new file mode 100644 index 0000000000..3d5fde475a --- /dev/null +++ b/svx/source/svdraw/sdrundomanager.cxx @@ -0,0 +1,184 @@ +/* -*- 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/sdrundomanager.hxx> +#include <svx/svdundo.hxx> +#include <sfx2/objsh.hxx> +#include <svl/hint.hxx> + +SdrUndoManager::SdrUndoManager() + : EditUndoManager(20 /*nMaxUndoActionCount*/) + , mpLastUndoActionBeforeTextEdit(nullptr) + , mnRedoActionCountBeforeTextEdit(0) + , mbEndTextEditTriggeredFromUndo(false) + , m_pDocSh(nullptr) +{ +} + +SdrUndoManager::~SdrUndoManager() {} + +bool SdrUndoManager::Undo() +{ + if (isTextEditActive()) + { + bool bRetval(false); + + // we are in text edit mode + if (GetUndoActionCount() && mpLastUndoActionBeforeTextEdit != GetUndoAction()) + { + // there is an undo action for text edit, trigger it + bRetval = EditUndoManager::Undo(); + } + else + { + // no more text edit undo, end text edit + mbEndTextEditTriggeredFromUndo = true; + maEndTextEditHdl.Call(this); + mbEndTextEditTriggeredFromUndo = false; + } + + return bRetval; + } + else + { + // no undo triggered up to now, trigger local one + return SfxUndoManager::Undo(); + } +} + +bool SdrUndoManager::Redo() +{ + bool bRetval(false); + bool bClearRedoStack(false); + + if (isTextEditActive()) + { + // we are in text edit mode + bRetval = EditUndoManager::Redo(); + } + + if (!bRetval) + { + // Check if the current and thus to-be undone UndoAction is a SdrUndoDiagramModelData action + const bool bCurrentIsDiagramChange( + GetRedoActionCount() + && nullptr != dynamic_cast<SdrUndoDiagramModelData*>(GetRedoAction())); + + // no redo triggered up to now, trigger local one + bRetval = SfxUndoManager::Redo(); + + // it was a SdrUndoDiagramModelData action and we have more Redo actions + if (bCurrentIsDiagramChange && GetRedoActionCount()) + { + const bool bNextIsDiagramChange( + nullptr != dynamic_cast<SdrUndoDiagramModelData*>(GetRedoAction())); + + // We have more Redo-actions and the 'next' one to be executed is *not* a + // SdrUndoDiagramModelData-action. This means that the already executed + // one had done a re-Layout/Re-create of the Diagram XShape/SdrObject + // representation based on the restored Diagram ModelData. When the next + // Redo action is something else (and thus will not itself re-create + // XShapes/SdrShapes) it may be that it is an UnGroup/Delete where a former + // as-content-of-Diagram created XShape/SdrShape is referenced, an action + // that references a XShape/SdrShape by pointer/reference. That + // pointer/reference *cannot* be valid anymore (now). + + // The problem here is that Undo/Redo actions historically reference + // XShapes/SdrShapes by pointer/reference, e.g. deleting means: remove + // from an SdrObjList and add to an Undo action. I is *not* + // address/incarnation-invariant in the sense to remember e.g. to + // remove the Nth object in the list (that would work). + + // It might be possible to solve/correct this better, but since it's + // a rare corner case just avoid the possible crash when continuing Redos + // by clearing the Redo-Stack here as a consequence + bClearRedoStack = !bNextIsDiagramChange; + } + } + + if (bClearRedoStack) + { + // clear Redo-Stack (explanation see above) + ClearRedo(); + } + + return bRetval; +} + +void SdrUndoManager::Clear() +{ + if (isTextEditActive()) + { + while (GetUndoActionCount() && mpLastUndoActionBeforeTextEdit != GetUndoAction()) + { + RemoveLastUndoAction(); + } + + // urgently needed: RemoveLastUndoAction does NOT correct the Redo stack by itself (!) + ClearRedo(); + } + else + { + // call parent + EditUndoManager::Clear(); + } +} + +void SdrUndoManager::SetEndTextEditHdl(const Link<SdrUndoManager*, void>& rLink) +{ + maEndTextEditHdl = rLink; + + if (isTextEditActive()) + { + // text edit start, remember last non-textedit action for later cleanup + mpLastUndoActionBeforeTextEdit = GetUndoActionCount() ? GetUndoAction() : nullptr; + mnRedoActionCountBeforeTextEdit = GetRedoActionCount(); + } + else + { + // text edit ends, pop all textedit actions up to the remembered non-textedit action from the start + // to set back the UndoManager to the state before text edit started. If that action is already gone + // (due to being removed from the undo stack in the meantime), all need to be removed anyways + while (GetUndoActionCount() && mpLastUndoActionBeforeTextEdit != GetUndoAction()) + { + RemoveLastUndoAction(); + } + + // urgently needed: RemoveLastUndoAction does NOT correct the Redo stack by itself (!) + ClearRedo(); + + // forget marker again + mpLastUndoActionBeforeTextEdit = nullptr; + mnRedoActionCountBeforeTextEdit = 0; + } +} + +bool SdrUndoManager::isTextEditActive() const { return maEndTextEditHdl.IsSet(); } + +void SdrUndoManager::SetDocShell(SfxObjectShell* pDocShell) { m_pDocSh = pDocShell; } + +void SdrUndoManager::EmptyActionsChanged() +{ + if (m_pDocSh) + { + m_pDocSh->Broadcast(SfxHint(SfxHintId::DocumentRepair)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/selectioncontroller.cxx b/svx/source/svdraw/selectioncontroller.cxx new file mode 100644 index 0000000000..43112953fa --- /dev/null +++ b/svx/source/svdraw/selectioncontroller.cxx @@ -0,0 +1,125 @@ +/* -*- 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/selectioncontroller.hxx> +#include <svx/svdobj.hxx> + +namespace sdr +{ + +bool SelectionController::onKeyInput(const KeyEvent& /*rKEvt*/, vcl::Window* /*pWin*/) +{ + return false; +} + +bool SelectionController::onMouseButtonDown(const MouseEvent& /*rMEvt*/, vcl::Window* /*pWin*/) +{ + return false; +} + +bool SelectionController::onMouseButtonUp(const MouseEvent& /*rMEvt*/, vcl::Window* /*pWin*/) +{ + return false; +} + +bool SelectionController::onMouseMove(const MouseEvent& /*rMEvt*/, vcl::Window* /*pWin*/) +{ + return false; +} + +void SelectionController::onSelectionHasChanged() +{ +} + +void SelectionController::onSelectAll() +{ +} + +void SelectionController::GetState( SfxItemSet& /*rSet*/ ) +{ +} + +void SelectionController::Execute( SfxRequest& /*rReq*/ ) +{ +} + +bool SelectionController::DeleteMarked() +{ + return false; +} + +bool SelectionController::GetAttributes(SfxItemSet& /*rTargetSet*/, bool /*bOnlyHardAttr*/) const +{ + return false; +} + +bool SelectionController::SetAttributes(const SfxItemSet& /*rSet*/, bool /*bReplaceAll*/) +{ + return false; +} + +bool SelectionController::GetStyleSheet( SfxStyleSheet* &/*rpStyleSheet*/ ) const +{ + return false; +} + +bool SelectionController::SetStyleSheet( SfxStyleSheet* /*pStyleSheet*/, bool /*bDontRemoveHardAttr*/ ) +{ + return false; +} + +rtl::Reference<SdrObject> SelectionController::GetMarkedSdrObjClone( SdrModel& /*rTargetModel*/ ) +{ + return nullptr; +} + +bool SelectionController::PasteObjModel( const SdrModel& /*rModel*/ ) +{ + return false; +} + +bool SelectionController::ApplyFormatPaintBrush( SfxItemSet& /*rFormatSet*/, bool /*bNoCharacterFormats*/, bool /*bNoParagraphFormats*/ ) +{ + return false; +} + +bool SelectionController::hasSelectedCells() const +{ + return false; +} + +void SelectionController::getSelectedCells(table::CellPos& /*rFirstPos*/, table::CellPos& /*rLastPos*/) +{ +} + +bool SelectionController::setCursorLogicPosition(const Point& /*rPosition*/, bool /*bPoint*/) +{ + return false; +} + + +bool SelectionController::ChangeFontSize(bool /*bGrow*/, const FontList* /*pFontList*/) +{ + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdattr.cxx b/svx/source/svdraw/svdattr.cxx new file mode 100644 index 0000000000..c518900e6f --- /dev/null +++ b/svx/source/svdraw/svdattr.cxx @@ -0,0 +1,2060 @@ +/* -*- 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 <com/sun/star/drawing/TextFitToSizeType.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/TextAnimationKind.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/drawing/TextAnimationDirection.hpp> +#include <com/sun/star/drawing/ConnectorType.hpp> +#include <com/sun/star/drawing/MeasureKind.hpp> +#include <com/sun/star/drawing/MeasureTextHorzPos.hpp> +#include <com/sun/star/drawing/MeasureTextVertPos.hpp> +#include <com/sun/star/drawing/CircleKind.hpp> + +#include <docmodel/theme/FormatScheme.hxx> + +#include <editeng/boxitem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/xmlcnitm.hxx> +#include <editeng/writingmodeitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <osl/diagnose.h> +#include <i18nutil/unicode.hxx> +#include <tools/bigint.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <svl/grabbagitem.hxx> +#include <svl/voiditem.hxx> + +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/sdgcpitm.hxx> +#include <svx/sdtfchim.hxx> +#include <svx/sdasitm.hxx> +#include <sdgcoitm.hxx> +#include <svx/sdggaitm.hxx> +#include <sdginitm.hxx> +#include <svx/sdgluitm.hxx> +#include <svx/sdgmoitm.hxx> +#include <sdgtritm.hxx> +#include <svx/sdprcitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtaditm.hxx> +#include <svx/sdtaiitm.hxx> +#include <svx/sdtaitm.hxx> +#include <svx/sdtakitm.hxx> +#include <svx/sdtayitm.hxx> +#include <svx/sdtfsitm.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpool.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svx3ditems.hxx> +#include <svx/svxids.hrc> +#include <sxallitm.hxx> +#include <sxcaitm.hxx> +#include <svx/sxcecitm.hxx> +#include <svx/sxcgitm.hxx> +#include <sxcikitm.hxx> +#include <svx/sxcllitm.hxx> +#include <svx/sxctitm.hxx> +#include <svx/sxekitm.hxx> +#include <svx/sxelditm.hxx> +#include <svx/sxenditm.hxx> +#include <sxfiitm.hxx> +#include <sxlayitm.hxx> +#include <sxlogitm.hxx> +#include <svx/sxmbritm.hxx> +#include <sxmfsitm.hxx> +#include <sxmkitm.hxx> +#include <sxmoitm.hxx> +#include <sxmovitm.hxx> +#include <sxmsitm.hxx> +#include <sxmtaitm.hxx> +#include <svx/sxmtfitm.hxx> +#include <svx/sxmtpitm.hxx> +#include <svx/sxmtritm.hxx> +#include <svx/sxmuitm.hxx> +#include <svx/xcolit.hxx> +#include <svx/RectangleAlignmentItem.hxx> +#include <sxoneitm.hxx> +#include <sxopitm.hxx> +#include <sxreaitm.hxx> +#include <sxreoitm.hxx> +#include <sxroaitm.hxx> +#include <sxrooitm.hxx> +#include <sxsaitm.hxx> +#include <sxsalitm.hxx> +#include <sxsiitm.hxx> +#include <sxsoitm.hxx> +#include <sxtraitm.hxx> +#include <editeng/frmdiritem.hxx> +#include <libxml/xmlwriter.h> + +using namespace ::com::sun::star; + +SdrItemPool::SdrItemPool( + SfxItemPool* _pMaster) +: XOutdevItemPool(_pMaster) +{ + // prepare some constants + const Color aNullCol(COL_BLACK); + const sal_Int32 nDefEdgeDist(500); // Defaulting hard for Draw (100TH_MM) currently. MapMode will have to be taken into account in the future. + + // init the non-persistent items + for(sal_uInt16 i(SDRATTR_NOTPERSIST_FIRST); i <= SDRATTR_NOTPERSIST_LAST; i++) + { + mpLocalItemInfos[i - SDRATTR_START]._bNeedsPoolRegistration = false; + } + + // these slots need _bNeedsPoolRegistration == true, see + // text @svl/source/items/itempool.cxx + mpLocalItemInfos[SDRATTR_XMLATTRIBUTES -SDRATTR_START]._bNeedsPoolRegistration = true; + + // init own PoolDefaults + std::vector<SfxPoolItem*>& rPoolDefaults = *mpLocalPoolDefaults; + rPoolDefaults[SDRATTR_SHADOW -SDRATTR_START]=new SdrOnOffItem(SDRATTR_SHADOW, false); + rPoolDefaults[SDRATTR_SHADOWCOLOR -SDRATTR_START]=new XColorItem(SDRATTR_SHADOWCOLOR, aNullCol); + rPoolDefaults[SDRATTR_SHADOWXDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_SHADOWXDIST, 0); + rPoolDefaults[SDRATTR_SHADOWYDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_SHADOWYDIST, 0); + rPoolDefaults[SDRATTR_SHADOWSIZEX -SDRATTR_START]=new SdrMetricItem(SDRATTR_SHADOWSIZEX, 100000); + rPoolDefaults[SDRATTR_SHADOWSIZEY -SDRATTR_START]=new SdrMetricItem(SDRATTR_SHADOWSIZEY, 100000); + rPoolDefaults[SDRATTR_SHADOWTRANSPARENCE-SDRATTR_START]=new SdrPercentItem(SDRATTR_SHADOWTRANSPARENCE, 0); + rPoolDefaults[SDRATTR_SHADOWBLUR -SDRATTR_START]=new SdrMetricItem(SDRATTR_SHADOWBLUR, 0); + rPoolDefaults[SDRATTR_SHADOWALIGNMENT -SDRATTR_START]=new SvxRectangleAlignmentItem(SDRATTR_SHADOWALIGNMENT, model::RectangleAlignment::Unset); + rPoolDefaults[SDRATTR_SHADOW3D -SDRATTR_START]=new SfxVoidItem(SDRATTR_SHADOW3D ); + rPoolDefaults[SDRATTR_SHADOWPERSP -SDRATTR_START]=new SfxVoidItem(SDRATTR_SHADOWPERSP ); + rPoolDefaults[SDRATTR_CAPTIONTYPE -SDRATTR_START]=new SdrCaptionTypeItem ; + rPoolDefaults[SDRATTR_CAPTIONFIXEDANGLE-SDRATTR_START]=new SdrOnOffItem(SDRATTR_CAPTIONFIXEDANGLE, true); + rPoolDefaults[SDRATTR_CAPTIONANGLE -SDRATTR_START]=new SdrCaptionAngleItem ; + rPoolDefaults[SDRATTR_CAPTIONGAP -SDRATTR_START]=new SdrCaptionGapItem ; + rPoolDefaults[SDRATTR_CAPTIONESCDIR -SDRATTR_START]=new SdrCaptionEscDirItem ; + rPoolDefaults[SDRATTR_CAPTIONESCISREL -SDRATTR_START]=new SdrCaptionEscIsRelItem ; + rPoolDefaults[SDRATTR_CAPTIONESCREL -SDRATTR_START]=new SdrCaptionEscRelItem ; + rPoolDefaults[SDRATTR_CAPTIONESCABS -SDRATTR_START]=new SdrCaptionEscAbsItem ; + rPoolDefaults[SDRATTR_CAPTIONLINELEN -SDRATTR_START]=new SdrCaptionLineLenItem ; + rPoolDefaults[SDRATTR_CAPTIONFITLINELEN-SDRATTR_START]=new SdrCaptionFitLineLenItem; + rPoolDefaults[SDRATTR_CORNER_RADIUS -SDRATTR_START]=new SdrMetricItem(SDRATTR_CORNER_RADIUS, 0); + rPoolDefaults[SDRATTR_TEXT_MINFRAMEHEIGHT -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_MINFRAMEHEIGHT, 0); + rPoolDefaults[SDRATTR_TEXT_AUTOGROWHEIGHT -SDRATTR_START]=new SdrOnOffItem(SDRATTR_TEXT_AUTOGROWHEIGHT, true); + rPoolDefaults[SDRATTR_TEXT_FITTOSIZE -SDRATTR_START]=new SdrTextFitToSizeTypeItem; + rPoolDefaults[SDRATTR_TEXT_LEFTDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_LEFTDIST, 0); + rPoolDefaults[SDRATTR_TEXT_RIGHTDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_RIGHTDIST, 0); + rPoolDefaults[SDRATTR_TEXT_UPPERDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_UPPERDIST, 0); + rPoolDefaults[SDRATTR_TEXT_LOWERDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_LOWERDIST, 0); + rPoolDefaults[SDRATTR_TEXT_VERTADJUST -SDRATTR_START]=new SdrTextVertAdjustItem; + rPoolDefaults[SDRATTR_TEXT_MAXFRAMEHEIGHT -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_MAXFRAMEHEIGHT, 0); + rPoolDefaults[SDRATTR_TEXT_MINFRAMEWIDTH -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_MINFRAMEWIDTH, 0); + rPoolDefaults[SDRATTR_TEXT_MAXFRAMEWIDTH -SDRATTR_START]=new SdrMetricItem(SDRATTR_TEXT_MAXFRAMEWIDTH, 0); + rPoolDefaults[SDRATTR_TEXT_AUTOGROWWIDTH -SDRATTR_START]=new SdrOnOffItem(SDRATTR_TEXT_AUTOGROWWIDTH, false); + rPoolDefaults[SDRATTR_TEXT_HORZADJUST -SDRATTR_START]=new SdrTextHorzAdjustItem; + rPoolDefaults[SDRATTR_TEXT_ANIKIND -SDRATTR_START]=new SdrTextAniKindItem; + rPoolDefaults[SDRATTR_TEXT_ANIDIRECTION -SDRATTR_START]=new SdrTextAniDirectionItem; + rPoolDefaults[SDRATTR_TEXT_ANISTARTINSIDE -SDRATTR_START]=new SdrTextAniStartInsideItem; + rPoolDefaults[SDRATTR_TEXT_ANISTOPINSIDE -SDRATTR_START]=new SdrTextAniStopInsideItem; + rPoolDefaults[SDRATTR_TEXT_ANICOUNT -SDRATTR_START]=new SdrTextAniCountItem; + rPoolDefaults[SDRATTR_TEXT_ANIDELAY -SDRATTR_START]=new SdrTextAniDelayItem; + rPoolDefaults[SDRATTR_TEXT_ANIAMOUNT -SDRATTR_START]=new SdrTextAniAmountItem; + rPoolDefaults[SDRATTR_TEXT_CONTOURFRAME -SDRATTR_START]=new SdrOnOffItem(SDRATTR_TEXT_CONTOURFRAME, false); + rPoolDefaults[SDRATTR_XMLATTRIBUTES -SDRATTR_START]=new SvXMLAttrContainerItem( SDRATTR_XMLATTRIBUTES ); + rPoolDefaults[SDRATTR_TEXT_CHAINNEXTNAME -SDRATTR_START]=new SfxStringItem(SDRATTR_TEXT_CHAINNEXTNAME, ""); + rPoolDefaults[SDRATTR_TEXT_USEFIXEDCELLHEIGHT -SDRATTR_START]=new SdrTextFixedCellHeightItem; + rPoolDefaults[SDRATTR_TEXT_WORDWRAP -SDRATTR_START]=new SdrOnOffItem(SDRATTR_TEXT_WORDWRAP, true); + rPoolDefaults[SDRATTR_TEXT_CLIPVERTOVERFLOW-SDRATTR_START]=new SdrOnOffItem(SDRATTR_TEXT_CLIPVERTOVERFLOW, false); + rPoolDefaults[SDRATTR_EDGEKIND -SDRATTR_START]=new SdrEdgeKindItem; + rPoolDefaults[SDRATTR_EDGENODE1HORZDIST-SDRATTR_START]=new SdrEdgeNode1HorzDistItem(nDefEdgeDist); + rPoolDefaults[SDRATTR_EDGENODE1VERTDIST-SDRATTR_START]=new SdrEdgeNode1VertDistItem(nDefEdgeDist); + rPoolDefaults[SDRATTR_EDGENODE2HORZDIST-SDRATTR_START]=new SdrEdgeNode2HorzDistItem(nDefEdgeDist); + rPoolDefaults[SDRATTR_EDGENODE2VERTDIST-SDRATTR_START]=new SdrEdgeNode2VertDistItem(nDefEdgeDist); + rPoolDefaults[SDRATTR_EDGENODE1GLUEDIST-SDRATTR_START]=new SdrEdgeNode1GlueDistItem; + rPoolDefaults[SDRATTR_EDGENODE2GLUEDIST-SDRATTR_START]=new SdrEdgeNode2GlueDistItem; + rPoolDefaults[SDRATTR_EDGELINEDELTACOUNT-SDRATTR_START]=new SdrEdgeLineDeltaCountItem; + rPoolDefaults[SDRATTR_EDGELINE1DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE1DELTA, 0); + rPoolDefaults[SDRATTR_EDGELINE2DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE2DELTA, 0); + rPoolDefaults[SDRATTR_EDGELINE3DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE3DELTA, 0); + rPoolDefaults[SDRATTR_EDGEOOXMLCURVE -SDRATTR_START]=new SfxBoolItem(SDRATTR_EDGEOOXMLCURVE, false); + rPoolDefaults[SDRATTR_MEASUREKIND -SDRATTR_START]=new SdrMeasureKindItem; + rPoolDefaults[SDRATTR_MEASURETEXTHPOS -SDRATTR_START]=new SdrMeasureTextHPosItem; + rPoolDefaults[SDRATTR_MEASURETEXTVPOS -SDRATTR_START]=new SdrMeasureTextVPosItem; + rPoolDefaults[SDRATTR_MEASURELINEDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_MEASURELINEDIST, 800); + rPoolDefaults[SDRATTR_MEASUREHELPLINEOVERHANG -SDRATTR_START]=new SdrMetricItem(SDRATTR_MEASUREHELPLINEOVERHANG, 200); + rPoolDefaults[SDRATTR_MEASUREHELPLINEDIST -SDRATTR_START]=new SdrMetricItem(SDRATTR_MEASUREHELPLINEDIST, 100); + rPoolDefaults[SDRATTR_MEASUREHELPLINE1LEN -SDRATTR_START]=new SdrMetricItem(SDRATTR_MEASUREHELPLINE1LEN, 0); + rPoolDefaults[SDRATTR_MEASUREHELPLINE2LEN -SDRATTR_START]=new SdrMetricItem(SDRATTR_MEASUREHELPLINE2LEN, 0); + rPoolDefaults[SDRATTR_MEASUREBELOWREFEDGE -SDRATTR_START]=new SdrMeasureBelowRefEdgeItem; + rPoolDefaults[SDRATTR_MEASURETEXTROTA90 -SDRATTR_START]=new SdrMeasureTextRota90Item; + rPoolDefaults[SDRATTR_MEASURETEXTUPSIDEDOWN -SDRATTR_START]=new SdrMeasureTextUpsideDownItem; + rPoolDefaults[SDRATTR_MEASUREOVERHANG -SDRATTR_START]=new SdrMeasureOverhangItem(600); + rPoolDefaults[SDRATTR_MEASUREUNIT -SDRATTR_START]=new SdrMeasureUnitItem; + rPoolDefaults[SDRATTR_MEASURESCALE -SDRATTR_START]=new SdrMeasureScaleItem; + rPoolDefaults[SDRATTR_MEASURESHOWUNIT -SDRATTR_START]=new SdrYesNoItem(SDRATTR_MEASURESHOWUNIT, false); + rPoolDefaults[SDRATTR_MEASUREFORMATSTRING -SDRATTR_START]=new SdrMeasureFormatStringItem(); + rPoolDefaults[SDRATTR_MEASURETEXTAUTOANGLE -SDRATTR_START]=new SdrMeasureTextAutoAngleItem(); + rPoolDefaults[SDRATTR_MEASURETEXTAUTOANGLEVIEW-SDRATTR_START]=new SdrMeasureTextAutoAngleViewItem(); + rPoolDefaults[SDRATTR_MEASURETEXTISFIXEDANGLE -SDRATTR_START]=new SdrMeasureTextIsFixedAngleItem(); + rPoolDefaults[SDRATTR_MEASURETEXTFIXEDANGLE -SDRATTR_START]=new SdrMeasureTextFixedAngleItem(); + rPoolDefaults[SDRATTR_MEASUREDECIMALPLACES -SDRATTR_START]=new SdrMeasureDecimalPlacesItem(); + rPoolDefaults[SDRATTR_CIRCKIND -SDRATTR_START]=new SdrCircKindItem; + rPoolDefaults[SDRATTR_CIRCSTARTANGLE-SDRATTR_START]=new SdrAngleItem(SDRATTR_CIRCSTARTANGLE, 0_deg100); + rPoolDefaults[SDRATTR_CIRCENDANGLE -SDRATTR_START]=new SdrAngleItem(SDRATTR_CIRCENDANGLE, 36000_deg100); + rPoolDefaults[SDRATTR_OBJMOVEPROTECT -SDRATTR_START]=new SdrYesNoItem(SDRATTR_OBJMOVEPROTECT, false); + rPoolDefaults[SDRATTR_OBJSIZEPROTECT -SDRATTR_START]=new SdrYesNoItem(SDRATTR_OBJSIZEPROTECT, false); + rPoolDefaults[SDRATTR_OBJPRINTABLE -SDRATTR_START]=new SdrObjPrintableItem; + rPoolDefaults[SDRATTR_OBJVISIBLE -SDRATTR_START]=new SdrObjVisibleItem; + rPoolDefaults[SDRATTR_LAYERID -SDRATTR_START]=new SdrLayerIdItem(SdrLayerID(0)); + rPoolDefaults[SDRATTR_LAYERNAME -SDRATTR_START]=new SdrLayerNameItem; + rPoolDefaults[SDRATTR_OBJECTNAME -SDRATTR_START]=new SfxStringItem(SDRATTR_OBJECTNAME); + rPoolDefaults[SDRATTR_ALLPOSITIONX -SDRATTR_START]=new SdrAllPositionXItem; + rPoolDefaults[SDRATTR_ALLPOSITIONY -SDRATTR_START]=new SdrAllPositionYItem; + rPoolDefaults[SDRATTR_ALLSIZEWIDTH -SDRATTR_START]=new SdrAllSizeWidthItem; + rPoolDefaults[SDRATTR_ALLSIZEHEIGHT -SDRATTR_START]=new SdrAllSizeHeightItem; + rPoolDefaults[SDRATTR_ONEPOSITIONX -SDRATTR_START]=new SdrOnePositionXItem; + rPoolDefaults[SDRATTR_ONEPOSITIONY -SDRATTR_START]=new SdrOnePositionYItem; + rPoolDefaults[SDRATTR_ONESIZEWIDTH -SDRATTR_START]=new SdrOneSizeWidthItem; + rPoolDefaults[SDRATTR_ONESIZEHEIGHT -SDRATTR_START]=new SdrOneSizeHeightItem; + rPoolDefaults[SDRATTR_LOGICSIZEWIDTH -SDRATTR_START]=new SdrLogicSizeWidthItem; + rPoolDefaults[SDRATTR_LOGICSIZEHEIGHT-SDRATTR_START]=new SdrLogicSizeHeightItem; + rPoolDefaults[SDRATTR_ROTATEANGLE -SDRATTR_START]=new SdrAngleItem(SDRATTR_ROTATEANGLE, 0_deg100); + rPoolDefaults[SDRATTR_SHEARANGLE -SDRATTR_START]=new SdrShearAngleItem; + rPoolDefaults[SDRATTR_MOVEX -SDRATTR_START]=new SdrMoveXItem; + rPoolDefaults[SDRATTR_MOVEY -SDRATTR_START]=new SdrMoveYItem; + rPoolDefaults[SDRATTR_RESIZEXONE -SDRATTR_START]=new SdrResizeXOneItem; + rPoolDefaults[SDRATTR_RESIZEYONE -SDRATTR_START]=new SdrResizeYOneItem; + rPoolDefaults[SDRATTR_ROTATEONE -SDRATTR_START]=new SdrRotateOneItem; + rPoolDefaults[SDRATTR_HORZSHEARONE -SDRATTR_START]=new SdrHorzShearOneItem; + rPoolDefaults[SDRATTR_VERTSHEARONE -SDRATTR_START]=new SdrVertShearOneItem; + rPoolDefaults[SDRATTR_RESIZEXALL -SDRATTR_START]=new SdrResizeXAllItem; + rPoolDefaults[SDRATTR_RESIZEYALL -SDRATTR_START]=new SdrResizeYAllItem; + rPoolDefaults[SDRATTR_ROTATEALL -SDRATTR_START]=new SdrRotateAllItem; + rPoolDefaults[SDRATTR_HORZSHEARALL -SDRATTR_START]=new SdrHorzShearAllItem; + rPoolDefaults[SDRATTR_VERTSHEARALL -SDRATTR_START]=new SdrVertShearAllItem; + rPoolDefaults[SDRATTR_TRANSFORMREF1X -SDRATTR_START]=new SdrTransformRef1XItem; + rPoolDefaults[SDRATTR_TRANSFORMREF1Y -SDRATTR_START]=new SdrTransformRef1YItem; + rPoolDefaults[SDRATTR_TRANSFORMREF2X -SDRATTR_START]=new SdrTransformRef2XItem; + rPoolDefaults[SDRATTR_TRANSFORMREF2Y -SDRATTR_START]=new SdrTransformRef2YItem; + rPoolDefaults[SDRATTR_TEXTDIRECTION -SDRATTR_START]=new SvxWritingModeItem(css::text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION); + rPoolDefaults[ SDRATTR_GRAFRED - SDRATTR_START] = new SdrGrafRedItem; + rPoolDefaults[ SDRATTR_GRAFGREEN - SDRATTR_START] = new SdrGrafGreenItem; + rPoolDefaults[ SDRATTR_GRAFBLUE - SDRATTR_START] = new SdrGrafBlueItem; + rPoolDefaults[ SDRATTR_GRAFLUMINANCE - SDRATTR_START] = new SdrGrafLuminanceItem; + rPoolDefaults[ SDRATTR_GRAFCONTRAST - SDRATTR_START] = new SdrGrafContrastItem; + rPoolDefaults[ SDRATTR_GRAFGAMMA - SDRATTR_START] = new SdrGrafGamma100Item; + rPoolDefaults[ SDRATTR_GRAFTRANSPARENCE - SDRATTR_START] = new SdrGrafTransparenceItem; + rPoolDefaults[ SDRATTR_GRAFINVERT - SDRATTR_START] = new SdrGrafInvertItem; + rPoolDefaults[ SDRATTR_GRAFMODE - SDRATTR_START] = new SdrGrafModeItem; + rPoolDefaults[ SDRATTR_GRAFCROP - SDRATTR_START] = new SdrGrafCropItem; + rPoolDefaults[ SDRATTR_3DOBJ_PERCENT_DIAGONAL - SDRATTR_START ] = new SfxUInt16Item(SDRATTR_3DOBJ_PERCENT_DIAGONAL, 10); + rPoolDefaults[ SDRATTR_3DOBJ_BACKSCALE - SDRATTR_START ] = new SfxUInt16Item(SDRATTR_3DOBJ_BACKSCALE, 100); + rPoolDefaults[ SDRATTR_3DOBJ_DEPTH - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DOBJ_DEPTH, 1000); + rPoolDefaults[ SDRATTR_3DOBJ_HORZ_SEGS - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DOBJ_HORZ_SEGS, 24); + rPoolDefaults[ SDRATTR_3DOBJ_VERT_SEGS - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DOBJ_VERT_SEGS, 24); + rPoolDefaults[ SDRATTR_3DOBJ_END_ANGLE - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DOBJ_END_ANGLE, 3600); + rPoolDefaults[ SDRATTR_3DOBJ_DOUBLE_SIDED - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DOBJ_DOUBLE_SIDED, false); + rPoolDefaults[ SDRATTR_3DOBJ_NORMALS_KIND - SDRATTR_START ] = new Svx3DNormalsKindItem; + rPoolDefaults[ SDRATTR_3DOBJ_NORMALS_INVERT - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DOBJ_NORMALS_INVERT, false); + rPoolDefaults[ SDRATTR_3DOBJ_TEXTURE_PROJ_X - SDRATTR_START ] = new Svx3DTextureProjectionXItem; + rPoolDefaults[ SDRATTR_3DOBJ_TEXTURE_PROJ_Y - SDRATTR_START ] = new Svx3DTextureProjectionYItem; + rPoolDefaults[ SDRATTR_3DOBJ_SHADOW_3D - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DOBJ_SHADOW_3D, false); + rPoolDefaults[ SDRATTR_3DOBJ_MAT_COLOR - SDRATTR_START ] = new SvxColorItem(Color(0x0000b8ff), SDRATTR_3DOBJ_MAT_COLOR); + rPoolDefaults[ SDRATTR_3DOBJ_MAT_EMISSION - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DOBJ_MAT_EMISSION); + rPoolDefaults[ SDRATTR_3DOBJ_MAT_SPECULAR - SDRATTR_START ] = new SvxColorItem(Color(0x00ffffff), SDRATTR_3DOBJ_MAT_SPECULAR); + rPoolDefaults[ SDRATTR_3DOBJ_MAT_SPECULAR_INTENSITY - SDRATTR_START ] = new SfxUInt16Item(SDRATTR_3DOBJ_MAT_SPECULAR_INTENSITY, 15); + rPoolDefaults[ SDRATTR_3DOBJ_TEXTURE_KIND - SDRATTR_START ] = new Svx3DTextureKindItem; + rPoolDefaults[ SDRATTR_3DOBJ_TEXTURE_MODE - SDRATTR_START ] = new Svx3DTextureModeItem; + rPoolDefaults[ SDRATTR_3DOBJ_TEXTURE_FILTER - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DOBJ_TEXTURE_FILTER, false); + rPoolDefaults[ SDRATTR_3DOBJ_SMOOTH_NORMALS - SDRATTR_START ] = new Svx3DSmoothNormalsItem; + rPoolDefaults[ SDRATTR_3DOBJ_SMOOTH_LIDS - SDRATTR_START ] = new Svx3DSmoothLidsItem; + rPoolDefaults[ SDRATTR_3DOBJ_CHARACTER_MODE - SDRATTR_START ] = new Svx3DCharacterModeItem; + rPoolDefaults[ SDRATTR_3DOBJ_CLOSE_FRONT - SDRATTR_START ] = new Svx3DCloseFrontItem; + rPoolDefaults[ SDRATTR_3DOBJ_CLOSE_BACK - SDRATTR_START ] = new Svx3DCloseBackItem; + rPoolDefaults[ SDRATTR_3DOBJ_REDUCED_LINE_GEOMETRY - SDRATTR_START ] = new Svx3DReducedLineGeometryItem; + rPoolDefaults[ SDRATTR_3DSCENE_PERSPECTIVE - SDRATTR_START ] = new Svx3DPerspectiveItem; + rPoolDefaults[ SDRATTR_3DSCENE_DISTANCE - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DSCENE_DISTANCE, 100); + rPoolDefaults[ SDRATTR_3DSCENE_FOCAL_LENGTH - SDRATTR_START ] = new SfxUInt32Item(SDRATTR_3DSCENE_FOCAL_LENGTH, 100); + rPoolDefaults[ SDRATTR_3DSCENE_TWO_SIDED_LIGHTING - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_TWO_SIDED_LIGHTING, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_1 - SDRATTR_START ] = new SvxColorItem(Color(ColorTransparency, 0xffcccccc), SDRATTR_3DSCENE_LIGHTCOLOR_1); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_2 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_2); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_3 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_3); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_4 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_4); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_5 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_5); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_6 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_6); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_7 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_7); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTCOLOR_8 - SDRATTR_START ] = new SvxColorItem(Color(0x00000000), SDRATTR_3DSCENE_LIGHTCOLOR_8); + rPoolDefaults[ SDRATTR_3DSCENE_AMBIENTCOLOR - SDRATTR_START ] = new SvxColorItem(Color(0x00666666), SDRATTR_3DSCENE_AMBIENTCOLOR); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_1 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_1, true); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_2 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_2, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_3 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_3, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_4 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_4, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_5 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_5, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_6 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_6, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_7 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_7, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTON_8 - SDRATTR_START ] = new SfxBoolItem(SDRATTR_3DSCENE_LIGHTON_8, false); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_1 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_1, basegfx::B3DVector(0.57735026918963, 0.57735026918963, 0.57735026918963)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_2 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_2, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_3 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_3, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_4 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_4, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_5 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_5, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_6 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_6, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_7 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_7, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_LIGHTDIRECTION_8 - SDRATTR_START ] = new SvxB3DVectorItem(SDRATTR_3DSCENE_LIGHTDIRECTION_8, basegfx::B3DVector(0.0,0.0,1.0)); + rPoolDefaults[ SDRATTR_3DSCENE_SHADOW_SLANT - SDRATTR_START ] = new SfxUInt16Item(SDRATTR_3DSCENE_SHADOW_SLANT, 0); + rPoolDefaults[ SDRATTR_3DSCENE_SHADE_MODE - SDRATTR_START ] = new Svx3DShadeModeItem; + rPoolDefaults[ SDRATTR_CUSTOMSHAPE_ENGINE - SDRATTR_START ] = new SfxStringItem(SDRATTR_CUSTOMSHAPE_ENGINE, ""); + rPoolDefaults[ SDRATTR_CUSTOMSHAPE_DATA - SDRATTR_START ] = new SfxStringItem(SDRATTR_CUSTOMSHAPE_DATA, ""); + rPoolDefaults[ SDRATTR_CUSTOMSHAPE_GEOMETRY - SDRATTR_START ] = new SdrCustomShapeGeometryItem; + + SvxBoxItem* pboxItem = new SvxBoxItem( SDRATTR_TABLE_BORDER ); + pboxItem->SetAllDistances( 100 ); + rPoolDefaults[ SDRATTR_TABLE_BORDER - SDRATTR_START ] = pboxItem; + + SvxBoxInfoItem* pBoxInfoItem = new SvxBoxInfoItem( SDRATTR_TABLE_BORDER_INNER ); + + pBoxInfoItem->SetTable( true ); + pBoxInfoItem->SetDist( true); // always show margin field + pBoxInfoItem->SetValid( SvxBoxInfoItemValidFlags::DISABLE ); // some lines may have DontCare state only in tables + + rPoolDefaults[ SDRATTR_TABLE_BORDER_INNER - SDRATTR_START ] = pBoxInfoItem; + rPoolDefaults[ SDRATTR_TABLE_BORDER_TLBR - SDRATTR_START ] = new SvxLineItem( SDRATTR_TABLE_BORDER_TLBR ); + rPoolDefaults[ SDRATTR_TABLE_BORDER_BLTR - SDRATTR_START ] = new SvxLineItem( SDRATTR_TABLE_BORDER_BLTR ); + rPoolDefaults[ SDRATTR_TABLE_TEXT_ROTATION - SDRATTR_START ] = new SvxTextRotateItem(0_deg10, SDRATTR_TABLE_TEXT_ROTATION); + rPoolDefaults[ SDRATTR_TABLE_CELL_GRABBAG - SDRATTR_START ] = new SfxGrabBagItem(SDRATTR_TABLE_CELL_GRABBAG); + + rPoolDefaults[ SDRATTR_GLOW_RADIUS - SDRATTR_START ] = new SdrMetricItem(SDRATTR_GLOW_RADIUS, 0); + rPoolDefaults[ SDRATTR_GLOW_COLOR - SDRATTR_START ] = new XColorItem(SDRATTR_GLOW_COLOR, aNullCol); + rPoolDefaults[ SDRATTR_GLOW_TRANSPARENCY - SDRATTR_START ] = new SdrPercentItem(SDRATTR_GLOW_TRANSPARENCY, 0); + + rPoolDefaults[SDRATTR_SOFTEDGE_RADIUS - SDRATTR_START] = new SdrMetricItem(SDRATTR_SOFTEDGE_RADIUS, 0); + + rPoolDefaults[SDRATTR_TEXTCOLUMNS_NUMBER - SDRATTR_START] = new SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, 1); + rPoolDefaults[SDRATTR_TEXTCOLUMNS_SPACING - SDRATTR_START] = new SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, 0); + + rPoolDefaults[SDRATTR_WRITINGMODE2 - SDRATTR_START] = new SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, SDRATTR_WRITINGMODE2); + + // set own ItemInfos + mpLocalItemInfos[SDRATTR_SHADOW-SDRATTR_START]._nSID=SID_ATTR_FILL_SHADOW; + mpLocalItemInfos[SDRATTR_SHADOWCOLOR-SDRATTR_START]._nSID=SID_ATTR_SHADOW_COLOR; + mpLocalItemInfos[SDRATTR_SHADOWTRANSPARENCE-SDRATTR_START]._nSID=SID_ATTR_SHADOW_TRANSPARENCE; + mpLocalItemInfos[SDRATTR_SHADOWBLUR-SDRATTR_START]._nSID=SID_ATTR_SHADOW_BLUR; + mpLocalItemInfos[SDRATTR_SHADOWXDIST-SDRATTR_START]._nSID=SID_ATTR_SHADOW_XDISTANCE; + mpLocalItemInfos[SDRATTR_SHADOWYDIST-SDRATTR_START]._nSID=SID_ATTR_SHADOW_YDISTANCE; + mpLocalItemInfos[SDRATTR_TEXT_FITTOSIZE-SDRATTR_START]._nSID=SID_ATTR_TEXT_FITTOSIZE; + mpLocalItemInfos[SDRATTR_GRAFCROP-SDRATTR_START]._nSID=SID_ATTR_GRAF_CROP; + + mpLocalItemInfos[SDRATTR_TABLE_BORDER - SDRATTR_START ]._nSID = SID_ATTR_BORDER_OUTER; + mpLocalItemInfos[SDRATTR_TABLE_BORDER_INNER - SDRATTR_START ]._nSID = SID_ATTR_BORDER_INNER; + mpLocalItemInfos[SDRATTR_TABLE_BORDER_TLBR - SDRATTR_START ]._nSID = SID_ATTR_BORDER_DIAG_TLBR; + mpLocalItemInfos[SDRATTR_TABLE_BORDER_BLTR - SDRATTR_START ]._nSID = SID_ATTR_BORDER_DIAG_BLTR; + + mpLocalItemInfos[SDRATTR_GLOW_RADIUS - SDRATTR_START]._nSID = SID_ATTR_GLOW_RADIUS; + mpLocalItemInfos[SDRATTR_GLOW_COLOR - SDRATTR_START]._nSID = SID_ATTR_GLOW_COLOR; + mpLocalItemInfos[SDRATTR_GLOW_TRANSPARENCY - SDRATTR_START]._nSID = SID_ATTR_GLOW_TRANSPARENCY; + + mpLocalItemInfos[SDRATTR_SOFTEDGE_RADIUS - SDRATTR_START]._nSID = SID_ATTR_SOFTEDGE_RADIUS; + + mpLocalItemInfos[SDRATTR_TEXTCOLUMNS_NUMBER - SDRATTR_START]._nSID = 0 /*TODO*/; + mpLocalItemInfos[SDRATTR_TEXTCOLUMNS_SPACING - SDRATTR_START]._nSID = 0 /*TODO*/; + + mpLocalItemInfos[SDRATTR_WRITINGMODE2 - SDRATTR_START]._nSID = 0 /*TODO*/; + + // it's my own creation level, set Defaults and ItemInfos + SetDefaults(mpLocalPoolDefaults); + SetItemInfos(mpLocalItemInfos.get()); +} + +// copy ctor, so that static defaults are cloned +// (Parameter 2 = sal_True) +SdrItemPool::SdrItemPool(const SdrItemPool& rPool) +: XOutdevItemPool(rPool) +{ +} + +rtl::Reference<SfxItemPool> SdrItemPool::Clone() const +{ + return new SdrItemPool(*this); +} + +SdrItemPool::~SdrItemPool() +{ + // split pools before destroying + SetSecondaryPool(nullptr); +} + +bool SdrItemPool::GetPresentation( + const SfxPoolItem& rItem, + MapUnit ePresentationMetric, OUString& rText, + const IntlWrapper& rIntlWrapper) const +{ + if (!IsInvalidItem(&rItem)) { + sal_uInt16 nWhich=rItem.Which(); + if (nWhich>=SDRATTR_SHADOW_FIRST && nWhich<=SDRATTR_END) { + rItem.GetPresentation(SfxItemPresentation::Nameless, + GetMetric(nWhich),ePresentationMetric,rText, + rIntlWrapper); + rText = GetItemName(nWhich) + " " + rText; + + return true; + } + } + return XOutdevItemPool::GetPresentation(rItem,ePresentationMetric,rText,rIntlWrapper); +} + +OUString SdrItemPool::GetItemName(sal_uInt16 nWhich) +{ + TranslateId pResId = SIP_UNKNOWN_ATTR; + + switch (nWhich) + { + case XATTR_LINESTYLE : pResId = SIP_XA_LINESTYLE;break; + case XATTR_LINEDASH : pResId = SIP_XA_LINEDASH;break; + case XATTR_LINEWIDTH : pResId = SIP_XA_LINEWIDTH;break; + case XATTR_LINECOLOR : pResId = SIP_XA_LINECOLOR;break; + case XATTR_LINESTART : pResId = SIP_XA_LINESTART;break; + case XATTR_LINEEND : pResId = SIP_XA_LINEEND;break; + case XATTR_LINESTARTWIDTH : pResId = SIP_XA_LINESTARTWIDTH;break; + case XATTR_LINEENDWIDTH : pResId = SIP_XA_LINEENDWIDTH;break; + case XATTR_LINESTARTCENTER : pResId = SIP_XA_LINESTARTCENTER;break; + case XATTR_LINEENDCENTER : pResId = SIP_XA_LINEENDCENTER;break; + case XATTR_LINETRANSPARENCE : pResId = SIP_XA_LINETRANSPARENCE;break; + case XATTR_LINEJOINT : pResId = SIP_XA_LINEJOINT;break; + case XATTRSET_LINE : pResId = SIP_XATTRSET_LINE;break; + + case XATTR_FILLSTYLE : pResId = SIP_XA_FILLSTYLE;break; + case XATTR_FILLCOLOR : pResId = SIP_XA_FILLCOLOR;break; + case XATTR_FILLGRADIENT : pResId = SIP_XA_FILLGRADIENT;break; + case XATTR_FILLHATCH : pResId = SIP_XA_FILLHATCH;break; + case XATTR_FILLBITMAP : pResId = SIP_XA_FILLBITMAP;break; + case XATTR_FILLTRANSPARENCE : pResId = SIP_XA_FILLTRANSPARENCE;break; + case XATTR_GRADIENTSTEPCOUNT : pResId = SIP_XA_GRADIENTSTEPCOUNT;break; + case XATTR_FILLBMP_TILE : pResId = SIP_XA_FILLBMP_TILE;break; + case XATTR_FILLBMP_POS : pResId = SIP_XA_FILLBMP_POS;break; + case XATTR_FILLBMP_SIZEX : pResId = SIP_XA_FILLBMP_SIZEX;break; + case XATTR_FILLBMP_SIZEY : pResId = SIP_XA_FILLBMP_SIZEY;break; + case XATTR_FILLFLOATTRANSPARENCE: pResId = SIP_XA_FILLFLOATTRANSPARENCE;break; + case XATTR_SECONDARYFILLCOLOR : pResId = SIP_XA_SECONDARYFILLCOLOR;break; + case XATTR_FILLBMP_SIZELOG : pResId = SIP_XA_FILLBMP_SIZELOG;break; + case XATTR_FILLBMP_TILEOFFSETX : pResId = SIP_XA_FILLBMP_TILEOFFSETX;break; + case XATTR_FILLBMP_TILEOFFSETY : pResId = SIP_XA_FILLBMP_TILEOFFSETY;break; + case XATTR_FILLBMP_STRETCH : pResId = SIP_XA_FILLBMP_STRETCH;break; + case XATTR_FILLBMP_POSOFFSETX : pResId = SIP_XA_FILLBMP_POSOFFSETX;break; + case XATTR_FILLBMP_POSOFFSETY : pResId = SIP_XA_FILLBMP_POSOFFSETY;break; + case XATTR_FILLBACKGROUND : pResId = SIP_XA_FILLBACKGROUND;break; + case XATTR_FILLUSESLIDEBACKGROUND: pResId = SIP_XA_FILLUSESLIDEBACKGROUND;break; + + case XATTRSET_FILL : pResId = SIP_XATTRSET_FILL;break; + + case XATTR_FORMTXTSTYLE : pResId = SIP_XA_FORMTXTSTYLE;break; + case XATTR_FORMTXTADJUST : pResId = SIP_XA_FORMTXTADJUST;break; + case XATTR_FORMTXTDISTANCE : pResId = SIP_XA_FORMTXTDISTANCE;break; + case XATTR_FORMTXTSTART : pResId = SIP_XA_FORMTXTSTART;break; + case XATTR_FORMTXTMIRROR : pResId = SIP_XA_FORMTXTMIRROR;break; + case XATTR_FORMTXTOUTLINE : pResId = SIP_XA_FORMTXTOUTLINE;break; + case XATTR_FORMTXTSHADOW : pResId = SIP_XA_FORMTXTSHADOW;break; + case XATTR_FORMTXTSHDWCOLOR : pResId = SIP_XA_FORMTXTSHDWCOLOR;break; + case XATTR_FORMTXTSHDWXVAL : pResId = SIP_XA_FORMTXTSHDWXVAL;break; + case XATTR_FORMTXTSHDWYVAL : pResId = SIP_XA_FORMTXTSHDWYVAL;break; + case XATTR_FORMTXTHIDEFORM : pResId = SIP_XA_FORMTXTHIDEFORM;break; + case XATTR_FORMTXTSHDWTRANSP: pResId = SIP_XA_FORMTXTSHDWTRANSP;break; + + case SDRATTR_SHADOW : pResId = SIP_SA_SHADOW;break; + case SDRATTR_SHADOWCOLOR : pResId = SIP_SA_SHADOWCOLOR;break; + case SDRATTR_SHADOWXDIST : pResId = SIP_SA_SHADOWXDIST;break; + case SDRATTR_SHADOWYDIST : pResId = SIP_SA_SHADOWYDIST;break; + case SDRATTR_SHADOWTRANSPARENCE: pResId = SIP_SA_SHADOWTRANSPARENCE;break; + case SDRATTR_SHADOWBLUR : pResId = SIP_SA_SHADOWBLUR;break; + case SDRATTR_SHADOW3D : pResId = SIP_SA_SHADOW3D;break; + case SDRATTR_SHADOWPERSP : pResId = SIP_SA_SHADOWPERSP;break; + + case SDRATTR_GLOW_RADIUS : pResId = SIP_SA_GLOW_RADIUS;break; + case SDRATTR_GLOW_COLOR : pResId = SIP_SA_GLOW_COLOR;break; + case SDRATTR_GLOW_TRANSPARENCY : pResId = SIP_SA_GLOW_TRANSPARENCY;break; + + case SDRATTR_SOFTEDGE_RADIUS : pResId = SIP_SA_SOFTEDGE_RADIUS; break; + + case SDRATTR_CAPTIONTYPE : pResId = SIP_SA_CAPTIONTYPE;break; + case SDRATTR_CAPTIONFIXEDANGLE: pResId = SIP_SA_CAPTIONFIXEDANGLE;break; + case SDRATTR_CAPTIONANGLE : pResId = SIP_SA_CAPTIONANGLE;break; + case SDRATTR_CAPTIONGAP : pResId = SIP_SA_CAPTIONGAP;break; + case SDRATTR_CAPTIONESCDIR : pResId = SIP_SA_CAPTIONESCDIR;break; + case SDRATTR_CAPTIONESCISREL : pResId = SIP_SA_CAPTIONESCISREL;break; + case SDRATTR_CAPTIONESCREL : pResId = SIP_SA_CAPTIONESCREL;break; + case SDRATTR_CAPTIONESCABS : pResId = SIP_SA_CAPTIONESCABS;break; + case SDRATTR_CAPTIONLINELEN : pResId = SIP_SA_CAPTIONLINELEN;break; + case SDRATTR_CAPTIONFITLINELEN: pResId = SIP_SA_CAPTIONFITLINELEN;break; + + case SDRATTR_CORNER_RADIUS : pResId = SIP_SA_CORNER_RADIUS;break; + case SDRATTR_TEXT_MINFRAMEHEIGHT : pResId = SIP_SA_TEXT_MINFRAMEHEIGHT;break; + case SDRATTR_TEXT_AUTOGROWHEIGHT : pResId = SIP_SA_TEXT_AUTOGROWHEIGHT;break; + case SDRATTR_TEXT_FITTOSIZE : pResId = SIP_SA_TEXT_FITTOSIZE;break; + case SDRATTR_TEXT_LEFTDIST : pResId = SIP_SA_TEXT_LEFTDIST;break; + case SDRATTR_TEXT_RIGHTDIST : pResId = SIP_SA_TEXT_RIGHTDIST;break; + case SDRATTR_TEXT_UPPERDIST : pResId = SIP_SA_TEXT_UPPERDIST;break; + case SDRATTR_TEXT_LOWERDIST : pResId = SIP_SA_TEXT_LOWERDIST;break; + case SDRATTR_TEXT_VERTADJUST : pResId = SIP_SA_TEXT_VERTADJUST;break; + case SDRATTR_TEXT_MAXFRAMEHEIGHT : pResId = SIP_SA_TEXT_MAXFRAMEHEIGHT;break; + case SDRATTR_TEXT_MINFRAMEWIDTH : pResId = SIP_SA_TEXT_MINFRAMEWIDTH;break; + case SDRATTR_TEXT_MAXFRAMEWIDTH : pResId = SIP_SA_TEXT_MAXFRAMEWIDTH;break; + case SDRATTR_TEXT_AUTOGROWWIDTH : pResId = SIP_SA_TEXT_AUTOGROWWIDTH;break; + case SDRATTR_TEXT_HORZADJUST : pResId = SIP_SA_TEXT_HORZADJUST;break; + case SDRATTR_TEXT_ANIKIND : pResId = SIP_SA_TEXT_ANIKIND;break; + case SDRATTR_TEXT_ANIDIRECTION : pResId = SIP_SA_TEXT_ANIDIRECTION;break; + case SDRATTR_TEXT_ANISTARTINSIDE : pResId = SIP_SA_TEXT_ANISTARTINSIDE;break; + case SDRATTR_TEXT_ANISTOPINSIDE : pResId = SIP_SA_TEXT_ANISTOPINSIDE;break; + case SDRATTR_TEXT_ANICOUNT : pResId = SIP_SA_TEXT_ANICOUNT;break; + case SDRATTR_TEXT_ANIDELAY : pResId = SIP_SA_TEXT_ANIDELAY;break; + case SDRATTR_TEXT_ANIAMOUNT : pResId = SIP_SA_TEXT_ANIAMOUNT;break; + case SDRATTR_TEXT_CONTOURFRAME : pResId = SIP_SA_TEXT_CONTOURFRAME;break; + case SDRATTR_XMLATTRIBUTES : pResId = SIP_SA_XMLATTRIBUTES;break; + case SDRATTR_TEXT_USEFIXEDCELLHEIGHT: pResId = SIP_SA_TEXT_USEFIXEDCELLHEIGHT;break; + case SDRATTR_TEXT_WORDWRAP : pResId = SIP_SA_WORDWRAP;break; + case SDRATTR_TEXT_CHAINNEXTNAME : pResId = SIP_SA_CHAINNEXTNAME;break; + + case SDRATTR_EDGEKIND : pResId = SIP_SA_EDGEKIND;break; + case SDRATTR_EDGENODE1HORZDIST : pResId = SIP_SA_EDGENODE1HORZDIST;break; + case SDRATTR_EDGENODE1VERTDIST : pResId = SIP_SA_EDGENODE1VERTDIST;break; + case SDRATTR_EDGENODE2HORZDIST : pResId = SIP_SA_EDGENODE2HORZDIST;break; + case SDRATTR_EDGENODE2VERTDIST : pResId = SIP_SA_EDGENODE2VERTDIST;break; + case SDRATTR_EDGENODE1GLUEDIST : pResId = SIP_SA_EDGENODE1GLUEDIST;break; + case SDRATTR_EDGENODE2GLUEDIST : pResId = SIP_SA_EDGENODE2GLUEDIST;break; + case SDRATTR_EDGELINEDELTACOUNT : pResId = SIP_SA_EDGELINEDELTACOUNT;break; + case SDRATTR_EDGELINE1DELTA : pResId = SIP_SA_EDGELINE1DELTA;break; + case SDRATTR_EDGELINE2DELTA : pResId = SIP_SA_EDGELINE2DELTA;break; + case SDRATTR_EDGELINE3DELTA : pResId = SIP_SA_EDGELINE3DELTA;break; + + case SDRATTR_MEASUREKIND : pResId = SIP_SA_MEASUREKIND;break; + case SDRATTR_MEASURETEXTHPOS : pResId = SIP_SA_MEASURETEXTHPOS;break; + case SDRATTR_MEASURETEXTVPOS : pResId = SIP_SA_MEASURETEXTVPOS;break; + case SDRATTR_MEASURELINEDIST : pResId = SIP_SA_MEASURELINEDIST;break; + case SDRATTR_MEASUREHELPLINEOVERHANG : pResId = SIP_SA_MEASUREHELPLINEOVERHANG;break; + case SDRATTR_MEASUREHELPLINEDIST : pResId = SIP_SA_MEASUREHELPLINEDIST;break; + case SDRATTR_MEASUREHELPLINE1LEN : pResId = SIP_SA_MEASUREHELPLINE1LEN;break; + case SDRATTR_MEASUREHELPLINE2LEN : pResId = SIP_SA_MEASUREHELPLINE2LEN;break; + case SDRATTR_MEASUREBELOWREFEDGE : pResId = SIP_SA_MEASUREBELOWREFEDGE;break; + case SDRATTR_MEASURETEXTROTA90 : pResId = SIP_SA_MEASURETEXTROTA90;break; + case SDRATTR_MEASURETEXTUPSIDEDOWN : pResId = SIP_SA_MEASURETEXTUPSIDEDOWN;break; + case SDRATTR_MEASUREOVERHANG : pResId = SIP_SA_MEASUREOVERHANG;break; + case SDRATTR_MEASUREUNIT : pResId = SIP_SA_MEASUREUNIT;break; + case SDRATTR_MEASURESCALE : pResId = SIP_SA_MEASURESCALE;break; + case SDRATTR_MEASURESHOWUNIT : pResId = SIP_SA_MEASURESHOWUNIT;break; + case SDRATTR_MEASUREFORMATSTRING : pResId = SIP_SA_MEASUREFORMATSTRING;break; + case SDRATTR_MEASURETEXTAUTOANGLE : pResId = SIP_SA_MEASURETEXTAUTOANGLE;break; + case SDRATTR_MEASURETEXTAUTOANGLEVIEW: pResId = SIP_SA_MEASURETEXTAUTOANGLEVIEW;break; + case SDRATTR_MEASURETEXTISFIXEDANGLE : pResId = SIP_SA_MEASURETEXTISFIXEDANGLE;break; + case SDRATTR_MEASURETEXTFIXEDANGLE : pResId = SIP_SA_MEASURETEXTFIXEDANGLE;break; + case SDRATTR_MEASUREDECIMALPLACES : pResId = SIP_SA_MEASUREDECIMALPLACES;break; + + case SDRATTR_CIRCKIND : pResId = SIP_SA_CIRCKIND;break; + case SDRATTR_CIRCSTARTANGLE: pResId = SIP_SA_CIRCSTARTANGLE;break; + case SDRATTR_CIRCENDANGLE : pResId = SIP_SA_CIRCENDANGLE;break; + + case SDRATTR_OBJMOVEPROTECT : pResId = SIP_SA_OBJMOVEPROTECT;break; + case SDRATTR_OBJSIZEPROTECT : pResId = SIP_SA_OBJSIZEPROTECT;break; + case SDRATTR_OBJPRINTABLE : pResId = SIP_SA_OBJPRINTABLE;break; + case SDRATTR_OBJVISIBLE : pResId = SIP_SA_OBJVISIBLE;break; + case SDRATTR_LAYERID : pResId = SIP_SA_LAYERID;break; + case SDRATTR_LAYERNAME : pResId = SIP_SA_LAYERNAME;break; + case SDRATTR_OBJECTNAME : pResId = SIP_SA_OBJECTNAME;break; + case SDRATTR_ALLPOSITIONX : pResId = SIP_SA_ALLPOSITIONX;break; + case SDRATTR_ALLPOSITIONY : pResId = SIP_SA_ALLPOSITIONY;break; + case SDRATTR_ALLSIZEWIDTH : pResId = SIP_SA_ALLSIZEWIDTH;break; + case SDRATTR_ALLSIZEHEIGHT : pResId = SIP_SA_ALLSIZEHEIGHT;break; + case SDRATTR_ONEPOSITIONX : pResId = SIP_SA_ONEPOSITIONX;break; + case SDRATTR_ONEPOSITIONY : pResId = SIP_SA_ONEPOSITIONY;break; + case SDRATTR_ONESIZEWIDTH : pResId = SIP_SA_ONESIZEWIDTH;break; + case SDRATTR_ONESIZEHEIGHT : pResId = SIP_SA_ONESIZEHEIGHT;break; + case SDRATTR_LOGICSIZEWIDTH : pResId = SIP_SA_LOGICSIZEWIDTH;break; + case SDRATTR_LOGICSIZEHEIGHT: pResId = SIP_SA_LOGICSIZEHEIGHT;break; + case SDRATTR_ROTATEANGLE : pResId = SIP_SA_ROTATEANGLE;break; + case SDRATTR_SHEARANGLE : pResId = SIP_SA_SHEARANGLE;break; + case SDRATTR_MOVEX : pResId = SIP_SA_MOVEX;break; + case SDRATTR_MOVEY : pResId = SIP_SA_MOVEY;break; + case SDRATTR_RESIZEXONE : pResId = SIP_SA_RESIZEXONE;break; + case SDRATTR_RESIZEYONE : pResId = SIP_SA_RESIZEYONE;break; + case SDRATTR_ROTATEONE : pResId = SIP_SA_ROTATEONE;break; + case SDRATTR_HORZSHEARONE : pResId = SIP_SA_HORZSHEARONE;break; + case SDRATTR_VERTSHEARONE : pResId = SIP_SA_VERTSHEARONE;break; + case SDRATTR_RESIZEXALL : pResId = SIP_SA_RESIZEXALL;break; + case SDRATTR_RESIZEYALL : pResId = SIP_SA_RESIZEYALL;break; + case SDRATTR_ROTATEALL : pResId = SIP_SA_ROTATEALL;break; + case SDRATTR_HORZSHEARALL : pResId = SIP_SA_HORZSHEARALL;break; + case SDRATTR_VERTSHEARALL : pResId = SIP_SA_VERTSHEARALL;break; + case SDRATTR_TRANSFORMREF1X : pResId = SIP_SA_TRANSFORMREF1X;break; + case SDRATTR_TRANSFORMREF1Y : pResId = SIP_SA_TRANSFORMREF1Y;break; + case SDRATTR_TRANSFORMREF2X : pResId = SIP_SA_TRANSFORMREF2X;break; + case SDRATTR_TRANSFORMREF2Y : pResId = SIP_SA_TRANSFORMREF2Y;break; + + case SDRATTR_GRAFRED : pResId = SIP_SA_GRAFRED;break; + case SDRATTR_GRAFGREEN : pResId = SIP_SA_GRAFGREEN;break; + case SDRATTR_GRAFBLUE : pResId = SIP_SA_GRAFBLUE;break; + case SDRATTR_GRAFLUMINANCE : pResId = SIP_SA_GRAFLUMINANCE;break; + case SDRATTR_GRAFCONTRAST : pResId = SIP_SA_GRAFCONTRAST;break; + case SDRATTR_GRAFGAMMA : pResId = SIP_SA_GRAFGAMMA;break; + case SDRATTR_GRAFTRANSPARENCE : pResId = SIP_SA_GRAFTRANSPARENCE;break; + case SDRATTR_GRAFINVERT : pResId = SIP_SA_GRAFINVERT;break; + case SDRATTR_GRAFMODE : pResId = SIP_SA_GRAFMODE;break; + case SDRATTR_GRAFCROP : pResId = SIP_SA_GRAFCROP;break; + + case EE_PARA_HYPHENATE : pResId = SIP_EE_PARA_HYPHENATE;break; + case EE_PARA_BULLETSTATE: pResId = SIP_EE_PARA_BULLETSTATE;break; + case EE_PARA_OUTLLRSPACE: pResId = SIP_EE_PARA_OUTLLRSPACE;break; + case EE_PARA_OUTLLEVEL : pResId = SIP_EE_PARA_OUTLLEVEL;break; + case EE_PARA_BULLET : pResId = SIP_EE_PARA_BULLET;break; + case EE_PARA_LRSPACE : pResId = SIP_EE_PARA_LRSPACE;break; + case EE_PARA_ULSPACE : pResId = SIP_EE_PARA_ULSPACE;break; + case EE_PARA_SBL : pResId = SIP_EE_PARA_SBL;break; + case EE_PARA_JUST : pResId = SIP_EE_PARA_JUST;break; + case EE_PARA_TABS : pResId = SIP_EE_PARA_TABS;break; + + case EE_CHAR_COLOR : pResId = SIP_EE_CHAR_COLOR;break; + case EE_CHAR_FONTINFO : pResId = SIP_EE_CHAR_FONTINFO;break; + case EE_CHAR_FONTHEIGHT : pResId = SIP_EE_CHAR_FONTHEIGHT;break; + case EE_CHAR_FONTWIDTH : pResId = SIP_EE_CHAR_FONTWIDTH;break; + case EE_CHAR_WEIGHT : pResId = SIP_EE_CHAR_WEIGHT;break; + case EE_CHAR_UNDERLINE : pResId = SIP_EE_CHAR_UNDERLINE;break; + case EE_CHAR_OVERLINE : pResId = SIP_EE_CHAR_OVERLINE;break; + case EE_CHAR_STRIKEOUT : pResId = SIP_EE_CHAR_STRIKEOUT;break; + case EE_CHAR_ITALIC : pResId = SIP_EE_CHAR_ITALIC;break; + case EE_CHAR_OUTLINE : pResId = SIP_EE_CHAR_OUTLINE;break; + case EE_CHAR_SHADOW : pResId = SIP_EE_CHAR_SHADOW;break; + case EE_CHAR_ESCAPEMENT : pResId = SIP_EE_CHAR_ESCAPEMENT;break; + case EE_CHAR_PAIRKERNING: pResId = SIP_EE_CHAR_PAIRKERNING;break; + case EE_CHAR_KERNING : pResId = SIP_EE_CHAR_KERNING;break; + case EE_CHAR_WLM : pResId = SIP_EE_CHAR_WLM;break; + case EE_FEATURE_TAB : pResId = SIP_EE_FEATURE_TAB;break; + case EE_FEATURE_LINEBR : pResId = SIP_EE_FEATURE_LINEBR;break; + case EE_FEATURE_NOTCONV : pResId = SIP_EE_FEATURE_NOTCONV;break; + case EE_FEATURE_FIELD : pResId = SIP_EE_FEATURE_FIELD;break; + + case SDRATTR_TEXTCOLUMNS_NUMBER: pResId = SIP_SA_TEXTCOLUMNS_NUMBER; break; + case SDRATTR_TEXTCOLUMNS_SPACING: pResId = SIP_SA_TEXTCOLUMNS_SPACING; break; + } // switch + + return SvxResId(pResId); +} + + +// FractionItem + + +bool SdrFractionItem::operator==(const SfxPoolItem& rCmp) const +{ + return SfxPoolItem::operator==(rCmp) && + static_cast<const SdrFractionItem&>(rCmp).GetValue()==nValue; +} + +bool SdrFractionItem::GetPresentation( + SfxItemPresentation ePresentation, MapUnit /*eCoreMetric*/, + MapUnit /*ePresentationMetric*/, OUString &rText, const IntlWrapper&) const +{ + if(nValue.IsValid()) + { + sal_Int32 nDiv = nValue.GetDenominator(); + rText = OUString::number(nValue.GetNumerator()); + + if(nDiv != 1) + { + rText += "/" + OUString::number(nDiv); + } + } + else + { + rText = "?"; + } + + if(ePresentation == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + return true; + } + else if(ePresentation == SfxItemPresentation::Nameless) + return true; + + return false; +} + +SdrFractionItem* SdrFractionItem::Clone(SfxItemPool * /*pPool*/) const +{ + return new SdrFractionItem(Which(),GetValue()); +} + + +// ScaleItem + + +bool SdrScaleItem::GetPresentation( + SfxItemPresentation ePresentation, MapUnit /*eCoreMetric*/, + MapUnit /*ePresentationMetric*/, OUString &rText, const IntlWrapper&) const +{ + if(GetValue().IsValid()) + { + sal_Int32 nDiv = GetValue().GetDenominator(); + + rText = OUString::number(GetValue().GetNumerator()) + ":" + OUString::number(nDiv); + } + else + { + rText = "?"; + } + + if(ePresentation == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + + return true; +} + +SdrScaleItem* SdrScaleItem::Clone(SfxItemPool * /*pPool*/) const +{ + return new SdrScaleItem(Which(),GetValue()); +} + + +// OnOffItem + + +SdrOnOffItem* SdrOnOffItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrOnOffItem(TypedWhichId<SdrOnOffItem>(Which()),GetValue()); +} + +OUString SdrOnOffItem::GetValueTextByVal(bool bVal) const +{ + if (bVal) + return SvxResId(STR_ItemValON); + return SvxResId(STR_ItemValOFF); +} + +bool SdrOnOffItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByVal(GetValue()); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +void SdrOnOffItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrOnOffItem")); + if (Which() == SDRATTR_SHADOW) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOW")); + } + + SfxBoolItem::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SdrYesNoItem* SdrYesNoItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrYesNoItem(TypedWhichId<SdrYesNoItem>(Which()),GetValue()); +} + +OUString SdrYesNoItem::GetValueTextByVal(bool bVal) const +{ + if (bVal) + return SvxResId(STR_ItemValYES); + return SvxResId(STR_ItemValNO); +} + +bool SdrYesNoItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByVal(GetValue()); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +SdrPercentItem* SdrPercentItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrPercentItem(TypedWhichId<SdrPercentItem>(Which()),GetValue()); +} + +bool SdrPercentItem::GetPresentation( + SfxItemPresentation ePres, MapUnit /*eCoreMetric*/, + MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText = unicode::formatPercent(GetValue(), + Application::GetSettings().GetUILanguageTag()); + + if(ePres == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + + return true; +} + +void SdrPercentItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrPercentItem")); + if (Which() == SDRATTR_SHADOWTRANSPARENCE) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), + BAD_CAST("SDRATTR_SHADOWTRANSPARENCE")); + } + + SfxUInt16Item::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SdrAngleItem* SdrAngleItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrAngleItem(TypedWhichId<SdrAngleItem>(Which()),GetValue()); +} + +bool SdrAngleItem::GetPresentation( + SfxItemPresentation ePres, MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString& rText, const IntlWrapper& rIntlWrapper) const +{ + sal_Int32 nValue(GetValue()); + bool bNeg(nValue < 0); + + if(bNeg) + nValue = -nValue; + + OUStringBuffer aText = OUString::number(nValue); + + if(nValue) + { + sal_Unicode aUnicodeNull('0'); + sal_Int32 nCount(2); + + if(LocaleDataWrapper::isNumLeadingZero()) + nCount++; + + while(aText.getLength() < nCount) + aText.insert(0, aUnicodeNull); + + sal_Int32 nLen = aText.getLength(); + bool bNull1(aText[nLen-1] == aUnicodeNull); + bool bNull2(bNull1 && aText[nLen-2] == aUnicodeNull); + + if(bNull2) + { + // no decimal place(s) + sal_Int32 idx = nLen-2; + aText.remove(idx, aText.getLength()-idx); + } + else + { + sal_Unicode cDec = + rIntlWrapper.getLocaleData()->getNumDecimalSep()[0]; + aText.insert(nLen-2, cDec); + + if(bNull1) + aText.remove(nLen, aText.getLength()-nLen); + } + + if(bNeg) + aText.insert(0, '-'); + } + + aText.append(sal_Unicode(DEGREE_CHAR)); + + if(ePres == SfxItemPresentation::Complete) + { + OUString aStr = SdrItemPool::GetItemName(Which()); + aText.insert(0, aStr + " "); + } + + rText = aText.makeStringAndClear(); + return true; +} + +SdrMetricItem* SdrMetricItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrMetricItem(TypedWhichId<SdrMetricItem>(Which()),GetValue()); +} + +bool SdrMetricItem::HasMetrics() const +{ + return true; +} + +void SdrMetricItem::ScaleMetrics(tools::Long nMul, tools::Long nDiv) +{ + if (GetValue()!=0) { + SetValue(BigInt::Scale(GetValue(), nMul, nDiv)); + } +} + +bool SdrMetricItem::GetPresentation(SfxItemPresentation ePres, + MapUnit eCoreMetric, MapUnit ePresMetric, OUString& rText, const IntlWrapper&) const +{ + tools::Long nValue=GetValue(); + SdrFormatter aFmt(eCoreMetric,ePresMetric); + rText = aFmt.GetStr(nValue); + rText += " " + SdrFormatter::GetUnitStr(ePresMetric); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +void SdrMetricItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrMetricItem")); + if (Which() == SDRATTR_SHADOWXDIST) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOWXDIST")); + } + else if (Which() == SDRATTR_SHADOWYDIST) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOWYDIST")); + } + else if (Which() == SDRATTR_SHADOWSIZEX) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOWSIZEX")); + } + else if (Which() == SDRATTR_SHADOWSIZEY) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOWSIZEY")); + } + else if (Which() == SDRATTR_SHADOWBLUR) + { + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST("SDRATTR_SHADOWBLUR")); + } + + SfxInt32Item::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +// items of the legend object + + +SdrCaptionTypeItem* SdrCaptionTypeItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrCaptionTypeItem(*this); } + +sal_uInt16 SdrCaptionTypeItem::GetValueCount() const { return 4; } + +OUString SdrCaptionTypeItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALCAPTIONTYPES[] = + { + STR_ItemValCAPTIONTYPE1, + STR_ItemValCAPTIONTYPE2, + STR_ItemValCAPTIONTYPE3, + STR_ItemValCAPTIONTYPE4 + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALCAPTIONTYPES) && "wrong pos!"); + return SvxResId(ITEMVALCAPTIONTYPES[nPos]); +} + +bool SdrCaptionTypeItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + + +SdrCaptionEscDirItem* SdrCaptionEscDirItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrCaptionEscDirItem(*this); } + +sal_uInt16 SdrCaptionEscDirItem::GetValueCount() const { return 3; } + +OUString SdrCaptionEscDirItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALCAPTIONTYPES[] = + { + STR_ItemValCAPTIONESCHORI, + STR_ItemValCAPTIONESCVERT, + STR_ItemValCAPTIONESCBESTFIT + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALCAPTIONTYPES) && "wrong pos!"); + return SvxResId(ITEMVALCAPTIONTYPES[nPos]); +} + +bool SdrCaptionEscDirItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + + +// MiscItems + + +// FitToSize + +SfxPoolItem* SdrTextFitToSizeTypeItem::CreateDefault() { return new SdrTextFitToSizeTypeItem; } + +SdrTextFitToSizeTypeItem* SdrTextFitToSizeTypeItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextFitToSizeTypeItem(*this); } + +bool SdrTextFitToSizeTypeItem::operator==(const SfxPoolItem& rItem) const +{ + if (!SfxEnumItem<css::drawing::TextFitToSizeType>::operator==(rItem)) + { + return false; + } + + return m_nMaxScale == static_cast<const SdrTextFitToSizeTypeItem&>(rItem).m_nMaxScale; +} + +sal_uInt16 SdrTextFitToSizeTypeItem::GetValueCount() const { return 4; } + +OUString SdrTextFitToSizeTypeItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALFITTISIZETYPES[] = + { + STR_ItemValFITTOSIZENONE, + STR_ItemValFITTOSIZEPROP, + STR_ItemValFITTOSIZEALLLINES, + STR_ItemValFITTOSIZERESIZEAT + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALFITTISIZETYPES) && "wrong pos!"); + return SvxResId(ITEMVALFITTISIZETYPES[nPos]); +} + +bool SdrTextFitToSizeTypeItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrTextFitToSizeTypeItem::HasBoolValue() const { return true; } + +bool SdrTextFitToSizeTypeItem::GetBoolValue() const { return GetValue() != drawing::TextFitToSizeType_NONE; } + +void SdrTextFitToSizeTypeItem::SetBoolValue(bool bVal) +{ + SetValue(bVal ? drawing::TextFitToSizeType_PROPORTIONAL : drawing::TextFitToSizeType_NONE); +} + +bool SdrTextFitToSizeTypeItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + drawing::TextFitToSizeType eFS = GetValue(); + rVal <<= eFS; + + return true; +} + +bool SdrTextFitToSizeTypeItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::TextFitToSizeType eFS; + if(!(rVal >>= eFS)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eFS = static_cast<drawing::TextFitToSizeType>(nEnum); + } + + SetValue(eFS); + + return true; +} + + +SdrTextVertAdjustItem* SdrTextVertAdjustItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextVertAdjustItem(*this); } + +sal_uInt16 SdrTextVertAdjustItem::GetValueCount() const { return 5; } + +OUString SdrTextVertAdjustItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALTEXTVADJTYPES[] = + { + STR_ItemValTEXTVADJTOP, + STR_ItemValTEXTVADJCENTER, + STR_ItemValTEXTVADJBOTTOM, + STR_ItemValTEXTVADJBLOCK, + STR_ItemValTEXTVADJSTRETCH + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALTEXTVADJTYPES) && "wrong pos!"); + return SvxResId(ITEMVALTEXTVADJTYPES[nPos]); +} + +bool SdrTextVertAdjustItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrTextVertAdjustItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::TextVerticalAdjust>(GetValue()); + return true; +} + +bool SdrTextVertAdjustItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::TextVerticalAdjust eAdj; + if(!(rVal >>= eAdj)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eAdj = static_cast<drawing::TextVerticalAdjust>(nEnum); + } + + SetValue( static_cast<SdrTextVertAdjust>(eAdj) ); + + return true; +} + +void SdrTextVertAdjustItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrTextVertAdjustItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SdrTextHorzAdjustItem* SdrTextHorzAdjustItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextHorzAdjustItem(*this); } + +sal_uInt16 SdrTextHorzAdjustItem::GetValueCount() const { return 5; } + +OUString SdrTextHorzAdjustItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALTEXTHADJTYPES[] = + { + STR_ItemValTEXTHADJLEFT, + STR_ItemValTEXTHADJCENTER, + STR_ItemValTEXTHADJRIGHT, + STR_ItemValTEXTHADJBLOCK, + STR_ItemValTEXTHADJSTRETCH + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALTEXTHADJTYPES) && "wrong pos!"); + return SvxResId(ITEMVALTEXTHADJTYPES[nPos]); +} + +bool SdrTextHorzAdjustItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrTextHorzAdjustItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::TextHorizontalAdjust>(GetValue()); + return true; +} + +bool SdrTextHorzAdjustItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::TextHorizontalAdjust eAdj; + if(!(rVal >>= eAdj)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eAdj = static_cast<drawing::TextHorizontalAdjust>(nEnum); + } + + SetValue( static_cast<SdrTextHorzAdjust>(eAdj) ); + + return true; +} + + +SdrTextAniKindItem* SdrTextAniKindItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextAniKindItem(*this); } + +sal_uInt16 SdrTextAniKindItem::GetValueCount() const { return 5; } + +OUString SdrTextAniKindItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALTEXTANITYPES[] = + { + STR_ItemValTEXTANI_NONE, + STR_ItemValTEXTANI_BLINK, + STR_ItemValTEXTANI_SCROLL, + STR_ItemValTEXTANI_ALTERNATE, + STR_ItemValTEXTANI_SLIDE + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALTEXTANITYPES) && "wrong pos!"); + return SvxResId(ITEMVALTEXTANITYPES[nPos]); +} + +bool SdrTextAniKindItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrTextAniKindItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::TextAnimationKind>(GetValue()); + return true; +} + +bool SdrTextAniKindItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::TextAnimationKind eKind; + if(!(rVal >>= eKind)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + eKind = static_cast<drawing::TextAnimationKind>(nEnum); + } + + SetValue( static_cast<SdrTextAniKind>(eKind) ); + + return true; +} + + +SdrTextAniDirectionItem* SdrTextAniDirectionItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextAniDirectionItem(*this); } + +sal_uInt16 SdrTextAniDirectionItem::GetValueCount() const { return 4; } + +OUString SdrTextAniDirectionItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALTEXTANITYPES[] = + { + STR_ItemValTEXTANI_LEFT, + STR_ItemValTEXTANI_UP, + STR_ItemValTEXTANI_RIGHT, + STR_ItemValTEXTANI_DOWN + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALTEXTANITYPES) && "wrong pos!"); + return SvxResId(ITEMVALTEXTANITYPES[nPos]); +} + +bool SdrTextAniDirectionItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrTextAniDirectionItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::TextAnimationDirection>(GetValue()); + return true; +} + +bool SdrTextAniDirectionItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::TextAnimationDirection eDir; + if(!(rVal >>= eDir)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eDir = static_cast<drawing::TextAnimationDirection>(nEnum); + } + + SetValue( static_cast<SdrTextAniDirection>(eDir) ); + + return true; +} + + +SdrTextAniDelayItem* SdrTextAniDelayItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextAniDelayItem(*this); } + +bool SdrTextAniDelayItem::GetPresentation( + SfxItemPresentation ePres, MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString& rText, const IntlWrapper&) const +{ + rText = OUString::number(GetValue()) + "ms"; + + if(ePres == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + + return true; +} + + +SdrTextAniAmountItem* SdrTextAniAmountItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrTextAniAmountItem(*this); } + +bool SdrTextAniAmountItem::HasMetrics() const +{ + return GetValue()>0; +} + +void SdrTextAniAmountItem::ScaleMetrics(tools::Long nMul, tools::Long nDiv) +{ + if (GetValue()>0) { + BigInt aVal(GetValue()); + aVal*=nMul; + aVal+=nDiv/2; // to round accurately + aVal/=nDiv; + SetValue(short(aVal)); + } +} + +bool SdrTextAniAmountItem::GetPresentation( + SfxItemPresentation ePres, MapUnit eCoreMetric, MapUnit ePresMetric, + OUString& rText, const IntlWrapper&) const +{ + sal_Int32 nValue(GetValue()); + + if(!nValue) + nValue = -1; + + if(nValue < 0) + { + rText = OUString::number(-nValue) + "pixel"; + } + else + { + SdrFormatter aFmt(eCoreMetric, ePresMetric); + rText = aFmt.GetStr(nValue) + + SdrFormatter::GetUnitStr(ePresMetric); + } + + if(ePres == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + + return true; +} + + +SdrTextFixedCellHeightItem::SdrTextFixedCellHeightItem( bool bUseFixedCellHeight ) + : SfxBoolItem( SDRATTR_TEXT_USEFIXEDCELLHEIGHT, bUseFixedCellHeight ) +{ +} +bool SdrTextFixedCellHeightItem::GetPresentation( SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresentationMetric*/, + OUString &rText, const IntlWrapper& ) const +{ + rText = GetValueTextByVal( GetValue() ); + if (ePres==SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +SdrTextFixedCellHeightItem* SdrTextFixedCellHeightItem::Clone( SfxItemPool * /*pPool*/) const +{ + return new SdrTextFixedCellHeightItem( GetValue() ); +} + +bool SdrTextFixedCellHeightItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + bool bValue = GetValue(); + rVal <<= bValue; + return true; +} +bool SdrTextFixedCellHeightItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + bool bValue; + if( !( rVal >>= bValue ) ) + return false; + SetValue( bValue ); + return true; +} + +// EdgeKind + +SdrEdgeKindItem* SdrEdgeKindItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrEdgeKindItem(*this); } + +sal_uInt16 SdrEdgeKindItem::GetValueCount() const { return 4; } + +OUString SdrEdgeKindItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALEDGES[] = + { + STR_ItemValEDGE_ORTHOLINES, + STR_ItemValEDGE_THREELINES, + STR_ItemValEDGE_ONELINE, + STR_ItemValEDGE_BEZIER + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALEDGES) && "wrong pos!"); + return SvxResId(ITEMVALEDGES[nPos]); +} + +bool SdrEdgeKindItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrEdgeKindItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + drawing::ConnectorType eCT = drawing::ConnectorType_STANDARD; + + switch( GetValue() ) + { + case SdrEdgeKind::OrthoLines : eCT = drawing::ConnectorType_STANDARD; break; + case SdrEdgeKind::ThreeLines : eCT = drawing::ConnectorType_LINES; break; + case SdrEdgeKind::OneLine : eCT = drawing::ConnectorType_LINE; break; + case SdrEdgeKind::Bezier : eCT = drawing::ConnectorType_CURVE; break; + case SdrEdgeKind::Arc : eCT = drawing::ConnectorType_CURVE; break; + default: + OSL_FAIL( "SdrEdgeKindItem::QueryValue : unknown enum" ); + } + + rVal <<= eCT; + + return true; +} + +bool SdrEdgeKindItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::ConnectorType eCT; + if(!(rVal >>= eCT)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eCT = static_cast<drawing::ConnectorType>(nEnum); + } + + SdrEdgeKind eEK = SdrEdgeKind::OrthoLines; + switch( eCT ) + { + case drawing::ConnectorType_STANDARD : eEK = SdrEdgeKind::OrthoLines; break; + case drawing::ConnectorType_CURVE : eEK = SdrEdgeKind::Bezier; break; + case drawing::ConnectorType_LINE : eEK = SdrEdgeKind::OneLine; break; + case drawing::ConnectorType_LINES : eEK = SdrEdgeKind::ThreeLines; break; + default: + OSL_FAIL( "SdrEdgeKindItem::PuValue : unknown enum" ); + } + SetValue( eEK ); + + return true; +} + +bool SdrEdgeNode1HorzDistItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrEdgeNode1HorzDistItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue( nValue ); + return true; +} + +SdrEdgeNode1HorzDistItem* SdrEdgeNode1HorzDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode1HorzDistItem(*this); +} + +bool SdrEdgeNode1VertDistItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrEdgeNode1VertDistItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue( nValue ); + return true; +} + +SdrEdgeNode1VertDistItem* SdrEdgeNode1VertDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode1VertDistItem(*this); +} + +bool SdrEdgeNode2HorzDistItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrEdgeNode2HorzDistItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue( nValue ); + return true; +} + +SdrEdgeNode2HorzDistItem* SdrEdgeNode2HorzDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode2HorzDistItem(*this); +} + +bool SdrEdgeNode2VertDistItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrEdgeNode2VertDistItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue( nValue ); + return true; +} + +SdrEdgeNode2VertDistItem* SdrEdgeNode2VertDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode2VertDistItem(*this); +} + +SdrEdgeNode1GlueDistItem* SdrEdgeNode1GlueDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode1GlueDistItem(*this); +} + +SdrEdgeNode2GlueDistItem* SdrEdgeNode2GlueDistItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrEdgeNode2GlueDistItem(*this); +} + +SdrMeasureKindItem* SdrMeasureKindItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrMeasureKindItem(*this); } + +sal_uInt16 SdrMeasureKindItem::GetValueCount() const { return 2; } + +OUString SdrMeasureKindItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALMEASURETYPES[] = + { + STR_ItemValMEASURE_STD, + STR_ItemValMEASURE_RADIUS + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALMEASURETYPES) && "wrong pos!"); + return SvxResId(ITEMVALMEASURETYPES[nPos]); +} + +bool SdrMeasureKindItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrMeasureKindItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::MeasureKind>(GetValue()); + return true; +} + +bool SdrMeasureKindItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::MeasureKind eKind; + if(!(rVal >>= eKind)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eKind = static_cast<drawing::MeasureKind>(nEnum); + } + + SetValue( static_cast<SdrMeasureKind>(eKind) ); + return true; +} + + +SdrMeasureTextHPosItem* SdrMeasureTextHPosItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrMeasureTextHPosItem(*this); } + +sal_uInt16 SdrMeasureTextHPosItem::GetValueCount() const { return 4; } + +const OUString & SdrMeasureTextHPosItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static std::array<OUString, 4> aMeasureTextHPosItem + { + "automatic", + "left outside", + "inside (centered)", + "right outside" + }; + assert(nPos < aMeasureTextHPosItem.size() && "wrong pos!"); + return aMeasureTextHPosItem[nPos]; +} + +bool SdrMeasureTextHPosItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrMeasureTextHPosItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrMeasureTextHPosItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::MeasureTextHorzPos ePos; + if(!(rVal >>= ePos)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + ePos = static_cast<drawing::MeasureTextHorzPos>(nEnum); + } + + SetValue(ePos); + return true; +} + +SdrMeasureTextVPosItem* SdrMeasureTextVPosItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrMeasureTextVPosItem(*this); } + +sal_uInt16 SdrMeasureTextVPosItem::GetValueCount() const { return 5; } + +OUString SdrMeasureTextVPosItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALMEASURETEXTTYPES[] = + { + STR_ItemValMEASURE_TEXTVAUTO, + STR_ItemValMEASURE_ABOVE, + STR_ItemValMEASURETEXT_BREAKEDLINE, + STR_ItemValMEASURE_BELOW, + STR_ItemValMEASURETEXT_VERTICALCEN + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALMEASURETEXTTYPES) && "wrong pos!"); + return SvxResId(ITEMVALMEASURETEXTTYPES[nPos]); +} + +bool SdrMeasureTextVPosItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrMeasureTextVPosItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= GetValue(); + return true; +} + +bool SdrMeasureTextVPosItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::MeasureTextVertPos ePos; + if(!(rVal >>= ePos)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + ePos = static_cast<drawing::MeasureTextVertPos>(nEnum); + } + + SetValue(ePos); + return true; +} + +SdrMeasureUnitItem* SdrMeasureUnitItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrMeasureUnitItem(*this); } + +sal_uInt16 SdrMeasureUnitItem::GetValueCount() const { return 14; } + +OUString SdrMeasureUnitItem::GetValueTextByPos(sal_uInt16 nPos) +{ + if(static_cast<FieldUnit>(nPos) == FieldUnit::NONE) + return "default"; + else + return SdrFormatter::GetUnitStr(static_cast<FieldUnit>(nPos)); +} + +bool SdrMeasureUnitItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrMeasureUnitItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<sal_Int32>(GetValue()); + return true; +} + +bool SdrMeasureUnitItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + sal_Int32 nMeasure = 0; + if(!(rVal >>= nMeasure)) + return false; + + SetValue( static_cast<FieldUnit>(nMeasure) ); + return true; +} + + +SdrCircKindItem* SdrCircKindItem::Clone(SfxItemPool* /*pPool*/) const { return new SdrCircKindItem(*this); } + +sal_uInt16 SdrCircKindItem::GetValueCount() const { return 4; } + +OUString SdrCircKindItem::GetValueTextByPos(sal_uInt16 nPos) +{ + static TranslateId ITEMVALCIRCTYPES[] = + { + STR_ItemValCIRC_FULL, + STR_ItemValCIRC_SECT, + STR_ItemValCIRC_CUT, + STR_ItemValCIRC_ARC + }; + assert(nPos < SAL_N_ELEMENTS(ITEMVALCIRCTYPES) && "wrong pos!"); + return SvxResId(ITEMVALCIRCTYPES[nPos]); +} + +bool SdrCircKindItem::GetPresentation(SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, OUString& rText, const IntlWrapper&) const +{ + rText=GetValueTextByPos(sal::static_int_cast< sal_uInt16 >(GetValue())); + if (ePres==SfxItemPresentation::Complete) { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + return true; +} + +bool SdrCircKindItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<drawing::CircleKind>(GetValue()); + return true; +} + +bool SdrCircKindItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + drawing::CircleKind eKind; + if(!(rVal >>= eKind)) + { + sal_Int32 nEnum = 0; + if(!(rVal >>= nEnum)) + return false; + + eKind = static_cast<drawing::CircleKind>(nEnum); + } + + SetValue( static_cast<SdrCircKind>(eKind) ); + return true; +} + +SdrSignedPercentItem* SdrSignedPercentItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrSignedPercentItem( Which(), GetValue() ); +} + +bool SdrSignedPercentItem::GetPresentation( + SfxItemPresentation ePres, MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString& rText, const IntlWrapper&) const +{ + rText = unicode::formatPercent(GetValue(), + Application::GetSettings().GetUILanguageTag()); + + if(ePres == SfxItemPresentation::Complete) + { + rText = SdrItemPool::GetItemName(Which()) + " " + rText; + } + + return true; +} + +SdrGrafRedItem* SdrGrafRedItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafRedItem( *this ); +} + +SdrGrafGreenItem* SdrGrafGreenItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafGreenItem( *this ); +} + +SdrGrafBlueItem* SdrGrafBlueItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafBlueItem( *this ); +} + +SdrGrafLuminanceItem* SdrGrafLuminanceItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafLuminanceItem( *this ); +} + +SdrGrafContrastItem* SdrGrafContrastItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafContrastItem( *this ); +} + +SdrGrafGamma100Item* SdrGrafGamma100Item::Clone( SfxItemPool* /*pPool */) const +{ + return new SdrGrafGamma100Item( *this ); +} + +bool SdrGrafGamma100Item::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/) const +{ + rVal <<= static_cast<double>(GetValue()) / 100.0; + return true; +} + +bool SdrGrafGamma100Item::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/) +{ + double nGamma = 0; + if(!(rVal >>= nGamma)) + return false; + + SetValue( static_cast<sal_uInt32>(nGamma * 100.0 ) ); + return true; +} + +SdrGrafInvertItem* SdrGrafInvertItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafInvertItem( *this ); +} + +SdrGrafTransparenceItem* SdrGrafTransparenceItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafTransparenceItem( *this ); +} + +SdrGrafModeItem* SdrGrafModeItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrGrafModeItem( *this ); +} + +sal_uInt16 SdrGrafModeItem::GetValueCount() const +{ + return 4; +} + +OUString SdrGrafModeItem::GetValueTextByPos(sal_uInt16 nPos) +{ + OUString aStr; + + switch(nPos) + { + case 1: + { + aStr = "Greys"; + break; + } + case 2: + { + aStr = "Black/White"; + break; + } + case 3: + { + aStr = "Watermark"; + break; + } + default: + { + aStr = "Standard"; + break; + } + } + + return aStr; +} + +bool SdrGrafModeItem::GetPresentation( SfxItemPresentation ePres, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString& rText, const IntlWrapper&) const +{ + rText = GetValueTextByPos( sal::static_int_cast< sal_uInt16 >( GetValue() ) ); + + if( ePres == SfxItemPresentation::Complete ) + { + rText = SdrItemPool::GetItemName( Which() ) + " " + rText; + } + + return true; +} + +SdrGrafCropItem* SdrGrafCropItem::Clone( SfxItemPool* /*pPool*/) const +{ + return new SdrGrafCropItem( *this ); +} + +SdrTextAniStartInsideItem::~SdrTextAniStartInsideItem() +{ +} + +SdrTextAniStartInsideItem* SdrTextAniStartInsideItem::Clone(SfxItemPool* ) const +{ + return new SdrTextAniStartInsideItem(*this); +} + +SdrTextAniStopInsideItem::~SdrTextAniStopInsideItem() +{ +} + +SdrTextAniStopInsideItem* SdrTextAniStopInsideItem::Clone(SfxItemPool* ) const +{ + return new SdrTextAniStopInsideItem(*this); +} + +SdrCaptionEscIsRelItem::~SdrCaptionEscIsRelItem() +{ +} + +SdrCaptionEscIsRelItem* SdrCaptionEscIsRelItem::Clone(SfxItemPool* ) const +{ + return new SdrCaptionEscIsRelItem(*this); +} + +SdrCaptionEscRelItem::~SdrCaptionEscRelItem() +{ +} + +SdrCaptionEscRelItem* SdrCaptionEscRelItem::Clone(SfxItemPool*) const +{ + return new SdrCaptionEscRelItem(*this); +} + +SdrCaptionFitLineLenItem::~SdrCaptionFitLineLenItem() +{ +} + +SdrCaptionFitLineLenItem* SdrCaptionFitLineLenItem::Clone(SfxItemPool* ) const +{ + return new SdrCaptionFitLineLenItem(*this); +} + +SdrCaptionLineLenItem::~SdrCaptionLineLenItem() +{ +} + +SdrCaptionLineLenItem* SdrCaptionLineLenItem::Clone(SfxItemPool*) const +{ + return new SdrCaptionLineLenItem(*this); +} + +SdrMeasureBelowRefEdgeItem::~SdrMeasureBelowRefEdgeItem() +{ +} + +SdrMeasureBelowRefEdgeItem* SdrMeasureBelowRefEdgeItem::Clone(SfxItemPool* ) const +{ + return new SdrMeasureBelowRefEdgeItem(*this); +} + +SdrMeasureTextIsFixedAngleItem::~SdrMeasureTextIsFixedAngleItem() +{ +} + +SdrMeasureTextIsFixedAngleItem* SdrMeasureTextIsFixedAngleItem::Clone(SfxItemPool* ) const +{ + return new SdrMeasureTextIsFixedAngleItem(*this); +} + +SdrMeasureTextFixedAngleItem::~SdrMeasureTextFixedAngleItem() +{ +} + +SdrMeasureTextFixedAngleItem* SdrMeasureTextFixedAngleItem::Clone(SfxItemPool* ) const +{ + return new SdrMeasureTextFixedAngleItem(*this); +} + +SdrMeasureDecimalPlacesItem::~SdrMeasureDecimalPlacesItem() +{ +} + +SdrMeasureDecimalPlacesItem* SdrMeasureDecimalPlacesItem::Clone(SfxItemPool* ) const +{ + return new SdrMeasureDecimalPlacesItem(*this); +} + +SdrMeasureTextRota90Item::~SdrMeasureTextRota90Item() +{ +} + +SdrMeasureTextRota90Item* SdrMeasureTextRota90Item::Clone(SfxItemPool* ) const +{ + return new SdrMeasureTextRota90Item(*this); +} + +SdrMeasureTextUpsideDownItem::~SdrMeasureTextUpsideDownItem() +{ +} + +SdrMeasureTextUpsideDownItem* SdrMeasureTextUpsideDownItem::Clone(SfxItemPool* ) const +{ + return new SdrMeasureTextUpsideDownItem(*this); +} + +SdrLayerIdItem* SdrLayerIdItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrLayerIdItem(*this); +} + +SdrLayerNameItem* SdrLayerNameItem::Clone(SfxItemPool* /*pPool*/) const +{ + return new SdrLayerNameItem(*this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdcrtv.cxx b/svx/source/svdraw/svdcrtv.cxx new file mode 100644 index 0000000000..7bff18563b --- /dev/null +++ b/svx/source/svdraw/svdcrtv.cxx @@ -0,0 +1,904 @@ +/* -*- 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/svdcrtv.hxx> +#include <svx/xlnclit.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/scene3d.hxx> +#include <svx/view3d.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xlineit0.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdopath.hxx> +#include <svx/sdr/overlay/overlaypolypolygon.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <fmobj.hxx> +#include <svx/svdocirc.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx> +#include <vcl/ptrstyle.hxx> + +using namespace com::sun::star; + +class ImplConnectMarkerOverlay +{ + // The OverlayObjects + sdr::overlay::OverlayObjectList maObjects; + + // The remembered target object + const SdrObject& mrObject; + +public: + ImplConnectMarkerOverlay(const SdrCreateView& rView, SdrObject const & rObject); + + // The OverlayObjects are cleared using the destructor of OverlayObjectList. + // That destructor calls clear() at the list which removes all objects from the + // OverlayManager and deletes them. + + const SdrObject& GetTargetObject() const { return mrObject; } +}; + +ImplConnectMarkerOverlay::ImplConnectMarkerOverlay(const SdrCreateView& rView, SdrObject const & rObject) +: mrObject(rObject) +{ + basegfx::B2DPolyPolygon aB2DPolyPolygon(rObject.TakeXorPoly()); + + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if(xTargetOverlay.is()) + { + float fScalingFactor = xTargetOverlay->getOutputDevice().GetDPIScaleFactor(); + Size aHalfLogicSize(xTargetOverlay->getOutputDevice().PixelToLogic(Size(4 * fScalingFactor, 4 * fScalingFactor))); + + // object + std::unique_ptr<sdr::overlay::OverlayPolyPolygonStripedAndFilled> pNew(new sdr::overlay::OverlayPolyPolygonStripedAndFilled( + aB2DPolyPolygon)); + xTargetOverlay->add(*pNew); + maObjects.append(std::move(pNew)); + + // gluepoints + for(sal_uInt16 i(0); i < 4; i++) + { + SdrGluePoint aGluePoint(rObject.GetVertexGluePoint(i)); + const Point& rPosition = aGluePoint.GetAbsolutePos(rObject); + + basegfx::B2DPoint aTopLeft(rPosition.X() - aHalfLogicSize.Width(), rPosition.Y() - aHalfLogicSize.Height()); + basegfx::B2DPoint aBottomRight(rPosition.X() + aHalfLogicSize.Width(), rPosition.Y() + aHalfLogicSize.Height()); + + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(aTopLeft); + aTempPoly.append(basegfx::B2DPoint(aBottomRight.getX(), aTopLeft.getY())); + aTempPoly.append(aBottomRight); + aTempPoly.append(basegfx::B2DPoint(aTopLeft.getX(), aBottomRight.getY())); + aTempPoly.setClosed(true); + + basegfx::B2DPolyPolygon aTempPolyPoly; + aTempPolyPoly.append(aTempPoly); + + std::unique_ptr<sdr::overlay::OverlayPolyPolygonStripedAndFilled> pNew2(new sdr::overlay::OverlayPolyPolygonStripedAndFilled( + std::move(aTempPolyPoly))); + xTargetOverlay->add(*pNew2); + maObjects.append(std::move(pNew2)); + } + } + } +} + +class ImpSdrCreateViewExtraData +{ + // The OverlayObjects for XOR replacement + sdr::overlay::OverlayObjectList maObjects; + +public: + ImpSdrCreateViewExtraData(); + ~ImpSdrCreateViewExtraData(); + + void CreateAndShowOverlay(const SdrCreateView& rView, const SdrObject* pObject, const basegfx::B2DPolyPolygon& rPolyPoly); + void HideOverlay(); +}; + +ImpSdrCreateViewExtraData::ImpSdrCreateViewExtraData() +{ +} + +ImpSdrCreateViewExtraData::~ImpSdrCreateViewExtraData() +{ + HideOverlay(); +} + +void ImpSdrCreateViewExtraData::CreateAndShowOverlay(const SdrCreateView& rView, const SdrObject* pObject, const basegfx::B2DPolyPolygon& rPolyPoly) +{ + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference<sdr::overlay::OverlayManager>& xOverlayManager = pCandidate->GetOverlayManager(); + + if (xOverlayManager.is()) + { + if(pObject) + { + const sdr::contact::ViewContact& rVC = pObject->GetViewContact(); + drawinglayer::primitive2d::Primitive2DContainer aSequence; + rVC.getViewIndependentPrimitive2DContainer(aSequence); + std::unique_ptr<sdr::overlay::OverlayObject> pNew(new sdr::overlay::OverlayPrimitive2DSequenceObject(std::move(aSequence))); + + xOverlayManager->add(*pNew); + maObjects.append(std::move(pNew)); + } + + if(rPolyPoly.count()) + { + std::unique_ptr<sdr::overlay::OverlayPolyPolygonStripedAndFilled> pNew(new sdr::overlay::OverlayPolyPolygonStripedAndFilled( + rPolyPoly)); + xOverlayManager->add(*pNew); + maObjects.append(std::move(pNew)); + } + } + } +} + +void ImpSdrCreateViewExtraData::HideOverlay() +{ + // the clear() call of the list removes all objects from the + // OverlayManager and deletes them. + maObjects.clear(); +} + + +// CreateView + + +void SdrCreateView::ImpClearConnectMarker() +{ + mpCoMaOverlay.reset(); +} + +SdrCreateView::SdrCreateView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrDragView(rSdrModel, pOut) + , mpCreatePV(nullptr) + , mpCreateViewExtraData(new ImpSdrCreateViewExtraData()) + , maCurrentCreatePointer(PointerStyle::Cross) + , mnAutoCloseDistPix(5) + , mnFreeHandMinDistPix(10) + , mnCurrentInvent(SdrInventor::Default) + , mnCurrentIdent(SdrObjKind::NONE) + , mb1stPointAsCenter(false) + , mbUseIncompatiblePathCreateInterface(false) +{ +} + +SdrCreateView::~SdrCreateView() +{ + ImpClearConnectMarker(); + mpCreateViewExtraData.reset(); +} + +bool SdrCreateView::IsAction() const +{ + return SdrDragView::IsAction() || mpCurrentCreate!=nullptr; +} + +void SdrCreateView::MovAction(const Point& rPnt) +{ + SdrDragView::MovAction(rPnt); + if (mpCurrentCreate != nullptr) { + MovCreateObj(rPnt); + } +} + +void SdrCreateView::EndAction() +{ + if (mpCurrentCreate != nullptr) EndCreateObj(SdrCreateCmd::ForceEnd); + SdrDragView::EndAction(); +} + +void SdrCreateView::BckAction() +{ + if (mpCurrentCreate != nullptr) BckCreateObj(); + SdrDragView::BckAction(); +} + +void SdrCreateView::BrkAction() +{ + SdrDragView::BrkAction(); + BrkCreateObj(); +} + +void SdrCreateView::TakeActionRect(tools::Rectangle& rRect) const +{ + if (mpCurrentCreate != nullptr) + { + rRect=maDragStat.GetActionRect(); + if (rRect.IsEmpty()) + { + rRect=tools::Rectangle(maDragStat.GetPrev(),maDragStat.GetNow()); + } + } + else + { + SdrDragView::TakeActionRect(rRect); + } +} + +bool SdrCreateView::CheckEdgeMode() +{ + if (mpCurrentCreate != nullptr) + { + // is managed by EdgeObj + if (mnCurrentInvent==SdrInventor::Default && mnCurrentIdent==SdrObjKind::Edge) return false; + } + + if (!IsCreateMode() || mnCurrentInvent!=SdrInventor::Default || mnCurrentIdent!=SdrObjKind::Edge) + { + ImpClearConnectMarker(); + return false; + } + else + { + // sal_True, if MouseMove should check Connect + return !IsAction(); + } +} + +void SdrCreateView::SetConnectMarker(const SdrObjConnection& rCon) +{ + SdrObject* pTargetObject = rCon.m_pSdrObj; + + if(pTargetObject) + { + // if target object changes, throw away overlay object to make room for changes + if(mpCoMaOverlay && pTargetObject != &mpCoMaOverlay->GetTargetObject()) + { + ImpClearConnectMarker(); + } + + if(!mpCoMaOverlay) + { + mpCoMaOverlay.reset(new ImplConnectMarkerOverlay(*this, *pTargetObject)); + } + } + else + { + ImpClearConnectMarker(); + } +} + +void SdrCreateView::HideConnectMarker() +{ + ImpClearConnectMarker(); +} + +bool SdrCreateView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if(CheckEdgeMode() && pWin) + { + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + // TODO: Change default hit tolerance at IsMarkedHit() some time! + Point aPos(pWin->PixelToLogic(rMEvt.GetPosPixel())); + bool bMarkHit=PickHandle(aPos)!=nullptr || IsMarkedObjHit(aPos); + SdrObjConnection aCon; + if (!bMarkHit) SdrEdgeObj::ImpFindConnector(aPos,*pPV,aCon,nullptr,pWin); + SetConnectMarker(aCon); + } + } + return SdrDragView::MouseMove(rMEvt,pWin); +} + +bool SdrCreateView::IsTextTool() const +{ + return meEditMode==SdrViewEditMode::Create + && mnCurrentInvent==SdrInventor::Default + && (mnCurrentIdent==SdrObjKind::Text + || mnCurrentIdent==SdrObjKind::TitleText + || mnCurrentIdent==SdrObjKind::OutlineText); +} + +bool SdrCreateView::IsEdgeTool() const +{ + return meEditMode==SdrViewEditMode::Create && mnCurrentInvent==SdrInventor::Default && (mnCurrentIdent==SdrObjKind::Edge); +} + +bool SdrCreateView::IsMeasureTool() const +{ + return meEditMode==SdrViewEditMode::Create && mnCurrentInvent==SdrInventor::Default && (mnCurrentIdent==SdrObjKind::Measure); +} + +void SdrCreateView::SetCurrentObj(SdrObjKind nIdent, SdrInventor nInvent) +{ + if (mnCurrentInvent!=nInvent || mnCurrentIdent!=nIdent) + { + mnCurrentInvent=nInvent; + mnCurrentIdent=nIdent; + rtl::Reference<SdrObject> pObj = (nIdent == SdrObjKind::NONE) ? nullptr : + SdrObjFactory::MakeNewObject( + GetModel(), + nInvent, + nIdent); + + if(pObj) + { + // Using text tool, mouse cursor is usually I-Beam, + // crosshairs with tiny I-Beam appears only on MouseButtonDown. + if(IsTextTool()) + { + // Here the correct pointer needs to be used + // if the default is set to vertical writing + maCurrentCreatePointer = PointerStyle::Text; + } + else + maCurrentCreatePointer = pObj->GetCreatePointer(); + } + else + { + maCurrentCreatePointer = PointerStyle::Cross; + } + } + + CheckEdgeMode(); + ImpSetGlueVisible3(IsEdgeTool()); +} + +bool SdrCreateView::ImpBegCreateObj(SdrInventor nInvent, SdrObjKind nIdent, const Point& rPnt, OutputDevice* pOut, + sal_Int16 nMinMov, const tools::Rectangle& rLogRect, SdrObject* pPreparedFactoryObject) +{ + bool bRet=false; + UnmarkAllObj(); + BrkAction(); + + ImpClearConnectMarker(); + + mpCreatePV = GetSdrPageView(); + + if (mpCreatePV != nullptr) + { // otherwise no side registered! + OUString aLay(maActualLayer); + + if(nInvent == SdrInventor::Default && nIdent == SdrObjKind::Measure && !maMeasureLayer.isEmpty()) + { + aLay = maMeasureLayer; + } + + SdrLayerID nLayer = mpCreatePV->GetPage()->GetLayerAdmin().GetLayerID(aLay); + if (nLayer==SDRLAYER_NOTFOUND) nLayer = SdrLayerID(0); + if (!mpCreatePV->GetLockedLayers().IsSet(nLayer) && mpCreatePV->GetVisibleLayers().IsSet(nLayer)) + { + if(pPreparedFactoryObject) + { + mpCurrentCreate = pPreparedFactoryObject; + } + else + { + mpCurrentCreate = SdrObjFactory::MakeNewObject( + GetModel(), nInvent, nIdent); + } + + Point aPnt(rPnt); + if (mnCurrentInvent != SdrInventor::Default || (mnCurrentIdent != SdrObjKind::Edge && + mnCurrentIdent != SdrObjKind::FreehandLine && + mnCurrentIdent != SdrObjKind::FreehandFill )) { // no snapping for Edge and Freehand + aPnt=GetSnapPos(aPnt, mpCreatePV); + } + if (mpCurrentCreate!=nullptr) + { + if (mpDefaultStyleSheet!=nullptr) mpCurrentCreate->NbcSetStyleSheet(mpDefaultStyleSheet, false); + + // SW uses a naked SdrObject for frame construction. Normally, such an + // object should not be created. Since it is possible to use it as a helper + // object (e.g. in letting the user define an area with the interactive + // construction) at least no items should be set at that object. + if(nInvent != SdrInventor::Default || nIdent != SdrObjKind::NewFrame) + { + mpCurrentCreate->SetMergedItemSet(maDefaultAttr); + } + + if (dynamic_cast<const SdrCaptionObj *>(mpCurrentCreate.get()) != nullptr) + { + SfxItemSet aSet(GetModel().GetItemPool()); + aSet.Put(XFillColorItem(OUString(),COL_WHITE)); // in case someone turns on Solid + aSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + + mpCurrentCreate->SetMergedItemSet(aSet); + } + if (nInvent == SdrInventor::Default && (nIdent==SdrObjKind::Text || nIdent==SdrObjKind::TitleText || nIdent==SdrObjKind::OutlineText)) + { + // default for all text frames: no background, no border + SfxItemSet aSet(GetModel().GetItemPool()); + aSet.Put(XFillColorItem(OUString(),COL_WHITE)); // in case someone turns on Solid + aSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + aSet.Put(XLineColorItem(OUString(),COL_BLACK)); // in case someone turns on Solid + aSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); + + mpCurrentCreate->SetMergedItemSet(aSet); + } + if (!rLogRect.IsEmpty()) mpCurrentCreate->NbcSetLogicRect(rLogRect); + + // make sure drag start point is inside WorkArea + const tools::Rectangle& rWorkArea = GetWorkArea(); + + if(!rWorkArea.IsEmpty()) + { + if(aPnt.X() < rWorkArea.Left()) + { + aPnt.setX( rWorkArea.Left() ); + } + + if(aPnt.X() > rWorkArea.Right()) + { + aPnt.setX( rWorkArea.Right() ); + } + + if(aPnt.Y() < rWorkArea.Top()) + { + aPnt.setY( rWorkArea.Top() ); + } + + if(aPnt.Y() > rWorkArea.Bottom()) + { + aPnt.setY( rWorkArea.Bottom() ); + } + } + + maDragStat.Reset(aPnt); + maDragStat.SetView(static_cast<SdrView*>(this)); + maDragStat.SetPageView(mpCreatePV); + maDragStat.SetMinMove(ImpGetMinMovLogic(nMinMov,pOut)); + mpDragWin=pOut; + if (mpCurrentCreate->BegCreate(maDragStat)) + { + ShowCreateObj(/*pOut,sal_True*/); + bRet=true; + } + else + { + mpCurrentCreate = nullptr; + mpCreatePV = nullptr; + } + } + } + } + return bRet; +} + +bool SdrCreateView::BegCreateObj(const Point& rPnt, OutputDevice* pOut, short nMinMov) +{ + return ImpBegCreateObj(mnCurrentInvent,mnCurrentIdent,rPnt,pOut,nMinMov,tools::Rectangle(), nullptr); +} + +bool SdrCreateView::BegCreatePreparedObject(const Point& rPnt, sal_Int16 nMinMov, SdrObject* pPreparedFactoryObject) +{ + SdrInventor nInvent(mnCurrentInvent); + SdrObjKind nIdent(mnCurrentIdent); + + if(pPreparedFactoryObject) + { + nInvent = pPreparedFactoryObject->GetObjInventor(); + nIdent = pPreparedFactoryObject->GetObjIdentifier(); + } + + return ImpBegCreateObj(nInvent, nIdent, rPnt, nullptr, nMinMov, tools::Rectangle(), pPreparedFactoryObject); +} + +bool SdrCreateView::BegCreateCaptionObj(const Point& rPnt, const Size& rObjSiz, + OutputDevice* pOut, short nMinMov) +{ + return ImpBegCreateObj(SdrInventor::Default,SdrObjKind::Caption,rPnt,pOut,nMinMov, + tools::Rectangle(rPnt,Size(rObjSiz.Width()+1,rObjSiz.Height()+1)), nullptr); +} + +void SdrCreateView::MovCreateObj(const Point& rPnt) +{ + if (mpCurrentCreate==nullptr) + return; + + Point aPnt(rPnt); + if (!maDragStat.IsNoSnap()) + { + aPnt=GetSnapPos(aPnt, mpCreatePV); + } + if (IsOrtho()) + { + if (maDragStat.IsOrtho8Possible()) OrthoDistance8(maDragStat.GetPrev(),aPnt,IsBigOrtho()); + else if (maDragStat.IsOrtho4Possible()) OrthoDistance4(maDragStat.GetPrev(),aPnt,IsBigOrtho()); + } + + // If the drag point was limited and Ortho is active, do + // the small ortho correction (reduction) -> last parameter to FALSE. + bool bDidLimit(ImpLimitToWorkArea(aPnt)); + if(bDidLimit && IsOrtho()) + { + if(maDragStat.IsOrtho8Possible()) + OrthoDistance8(maDragStat.GetPrev(), aPnt, false); + else if(maDragStat.IsOrtho4Possible()) + OrthoDistance4(maDragStat.GetPrev(), aPnt, false); + } + + if (aPnt==maDragStat.GetNow()) return; + bool bIsMinMoved(maDragStat.IsMinMoved()); + if (!maDragStat.CheckMinMoved(aPnt)) + return; + + if (!bIsMinMoved) maDragStat.NextPoint(); + maDragStat.NextMove(aPnt); + mpCurrentCreate->MovCreate(maDragStat); + + // MovCreate changes the object, so use ActionChanged() on it + mpCurrentCreate->ActionChanged(); + + // replace for DrawCreateObjDiff + HideCreateObj(); + ShowCreateObj(); +} + +void SdrCreateView::SetupObjLayer(const SdrPageView* pPageView, const OUString& aActiveLayer, SdrObject* pObj) +{ + const SdrLayerAdmin& rAd = pPageView->GetPage()->GetLayerAdmin(); + SdrLayerID nLayer(0); + + // #i72535# + if(dynamic_cast<const FmFormObj*>( pObj) != nullptr) + { + // for FormControls, force to form layer + nLayer = rAd.GetLayerID(rAd.GetControlLayerName()); + } + else + { + nLayer = rAd.GetLayerID(aActiveLayer); + } + + if(SDRLAYER_NOTFOUND == nLayer) + { + nLayer = SdrLayerID(0); + } + + pObj->SetLayer(nLayer); +} + +bool SdrCreateView::EndCreateObj(SdrCreateCmd eCmd) +{ + bool bRet=false; + SdrObject* pObjCreated=mpCurrentCreate.get(); + + if (mpCurrentCreate!=nullptr) + { + sal_uInt32 nCount=maDragStat.GetPointCount(); + + if (nCount<=1 && eCmd==SdrCreateCmd::ForceEnd) + { + BrkCreateObj(); // objects with only a single point don't exist (at least today) + return false; // sal_False = event not interpreted + } + + bool bPntsEq=nCount>1; + sal_uInt32 i=1; + Point aP0=maDragStat.GetPoint(0); + while (bPntsEq && i<nCount) { bPntsEq=aP0==maDragStat.GetPoint(i); i++; } + + if (mpCurrentCreate->EndCreate(maDragStat,eCmd)) + { + HideCreateObj(); + + if (!bPntsEq) + { + // otherwise Brk, because all points are equal + rtl::Reference<SdrObject> pObj = std::move(mpCurrentCreate); + + SetupObjLayer(mpCreatePV, maActualLayer, pObj.get()); + + // recognize creation of a new 3D object inside a 3D scene + bool bSceneIntoScene(false); + + E3dScene* pObjScene = DynCastE3dScene(pObjCreated); + E3dScene* pCurrentScene = pObjScene ? DynCastE3dScene(mpCreatePV->GetCurrentGroup()) : nullptr; + if (pCurrentScene) + { + bool bDidInsert = static_cast<E3dView*>(this)->ImpCloneAll3DObjectsToDestScene( + pObjScene, pCurrentScene, Point(0, 0)); + + if(bDidInsert) + { + pObjCreated = nullptr; + bSceneIntoScene = true; + } + } + + if(!bSceneIntoScene) + { + // Here an interactively created SdrObject gets added, so + // take into account that interaction created an object in + // model coordinates. If we have e.g. a GirdOffset, this is a + // little bit tricky - we have an object in model coordinates, + // so the fetched offset is at the wrong point in principle + // since we need to 'substract' the offset here to get to + // 'real' model coordinates. But we have nothing better here, + // so go for it. + // The 2nd a little tricky thing is that this will early-create + // a ViewObjectContact for the new SdrObject, but these VOCs + // are anyways layouted for being create-on-demand. This will + // be adapted/replaced correctly later on. + // This *should* be the right place for getting all interactively + // created objects, see InsertObjectAtView below that calls + // CreateUndoNewObject. + basegfx::B2DVector aGridOffset(0.0, 0.0); + if(getPossibleGridOffsetForSdrObject(aGridOffset, pObj.get(), mpCreatePV)) + { + const Size aOffset( + basegfx::fround(-aGridOffset.getX()), + basegfx::fround(-aGridOffset.getY())); + + pObj->NbcMove(aOffset); + } + + // do the same as before + InsertObjectAtView(pObj.get(), *mpCreatePV); + } + + mpCreatePV = nullptr; + bRet=true; // sal_True = event interpreted + } + else + { + BrkCreateObj(); + } + } + else + { // more points + if (eCmd==SdrCreateCmd::ForceEnd || // nothing there -- force ending + nCount==0 || // no existing points (should never happen) + (nCount<=1 && !maDragStat.IsMinMoved())) { // MinMove not met + BrkCreateObj(); + } + else + { + // replace for DrawCreateObjDiff + HideCreateObj(); + ShowCreateObj(); + maDragStat.ResetMinMoved(); // NextPoint is at MovCreateObj() + bRet=true; + } + } + } + return bRet; +} + +void SdrCreateView::BckCreateObj() +{ + if (mpCurrentCreate==nullptr) + return; + + if (maDragStat.GetPointCount()<=2 ) + { + BrkCreateObj(); + } + else + { + HideCreateObj(); + maDragStat.PrevPoint(); + if (mpCurrentCreate->BckCreate(maDragStat)) + { + ShowCreateObj(); + } + else + { + BrkCreateObj(); + } + } +} + +void SdrCreateView::BrkCreateObj() +{ + if (mpCurrentCreate!=nullptr) + { + HideCreateObj(); + mpCurrentCreate->BrkCreate(maDragStat); + mpCurrentCreate = nullptr; + mpCreatePV = nullptr; + } +} + +void SdrCreateView::ShowCreateObj(/*OutputDevice* pOut, sal_Bool bFull*/) +{ + if(!IsCreateObj() || maDragStat.IsShown()) + return; + + if (mpCurrentCreate) + { + // for migration from XOR, replace DrawDragObj here to create + // overlay objects instead. + bool bUseSolidDragging(IsSolidDragging()); + + // #i101648# check if dragged object is a SdrObjKind::NewFrame. + // This is e.g. used in SW Frame construction as placeholder. + // Do not use SolidDragging for SdrObjKind::NewFrame kind of objects, + // they cannot have a valid optical representation. + if (bUseSolidDragging && SdrObjKind::NewFrame == mpCurrentCreate->GetObjIdentifier()) + { + bUseSolidDragging = false; + } + + // check for objects with no fill and no line + if(bUseSolidDragging) + { + const SfxItemSet& rSet = mpCurrentCreate->GetMergedItemSet(); + const drawing::FillStyle eFill(rSet.Get(XATTR_FILLSTYLE).GetValue()); + const drawing::LineStyle eLine(rSet.Get(XATTR_LINESTYLE).GetValue()); + + if(drawing::LineStyle_NONE == eLine && drawing::FillStyle_NONE == eFill) + { + bUseSolidDragging = false; + } + } + + // check for form controls + if(bUseSolidDragging) + { + if (dynamic_cast<const SdrUnoObj*>(mpCurrentCreate.get()) != nullptr) + { + bUseSolidDragging = false; + } + } + + // #i101781# force to non-solid dragging when not creating a full circle + if(bUseSolidDragging) + { + SdrCircObj* pCircObj = dynamic_cast<SdrCircObj*>(mpCurrentCreate.get()); + + if(pCircObj && SdrObjKind::CircleOrEllipse != pCircObj->GetObjIdentifier()) + { + // #i103058# Allow SolidDragging with four points + if(maDragStat.GetPointCount() < 4) + { + bUseSolidDragging = false; + } + } + } + + if(bUseSolidDragging) + { + basegfx::B2DPolyPolygon aDragPolyPolygon; + + if (dynamic_cast<const SdrRectObj*>(mpCurrentCreate.get()) != nullptr) + { + // ensure object has some size, necessary for SdrTextObj because + // there are still untested divisions by that sizes + tools::Rectangle aCurrentSnapRect(mpCurrentCreate->GetSnapRect()); + + if(aCurrentSnapRect.GetWidth() <= 1 || aCurrentSnapRect.GetHeight() <= 1) + { + tools::Rectangle aNewRect(maDragStat.GetStart(), maDragStat.GetStart() + Point(2, 2)); + mpCurrentCreate->NbcSetSnapRect(aNewRect); + } + } + + if (auto pPathObj = dynamic_cast<SdrPathObj*>(mpCurrentCreate.get())) + { + // The up-to-now created path needs to be set at the object to have something + // that can be visualized + const basegfx::B2DPolyPolygon aCurrentPolyPolygon(pPathObj->getObjectPolyPolygon(maDragStat)); + + if(aCurrentPolyPolygon.count()) + { + pPathObj->NbcSetPathPoly(aCurrentPolyPolygon); + } + + aDragPolyPolygon = pPathObj->getDragPolyPolygon(maDragStat); + } + + // use the SdrObject directly for overlay + mpCreateViewExtraData->CreateAndShowOverlay(*this, mpCurrentCreate.get(), aDragPolyPolygon); + } + else + { + const ::basegfx::B2DPolyPolygon aPoly(mpCurrentCreate->TakeCreatePoly(maDragStat)); + + mpCreateViewExtraData->CreateAndShowOverlay(*this, nullptr, aPoly); + } + + // #i101679# Force changed overlay to be shown + for(sal_uInt32 a(0); a < PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = GetPaintWindow(a); + const rtl::Reference<sdr::overlay::OverlayManager>& xOverlayManager = pCandidate->GetOverlayManager(); + + if (xOverlayManager.is()) + { + xOverlayManager->flush(); + } + } + } + + maDragStat.SetShown(true); +} + +void SdrCreateView::HideCreateObj() +{ + if(IsCreateObj() && maDragStat.IsShown()) + { + // for migration from XOR, replace DrawDragObj here to create + // overlay objects instead. + mpCreateViewExtraData->HideOverlay(); + + //DrawCreateObj(pOut,bFull); + maDragStat.SetShown(false); + } +} + + +void SdrCreateView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if (mpCurrentCreate) + { + rTargetSet.Put(mpCurrentCreate->GetMergedItemSet()); + } + else + { + SdrDragView::GetAttributes(rTargetSet, bOnlyHardAttr); + } +} + +bool SdrCreateView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + if (mpCurrentCreate) + { + mpCurrentCreate->SetMergedItemSetAndBroadcast(rSet, bReplaceAll); + + return true; + } + else + { + return SdrDragView::SetAttributes(rSet,bReplaceAll); + } +} + +SfxStyleSheet* SdrCreateView::GetStyleSheet() const +{ + if (mpCurrentCreate != nullptr) + { + return mpCurrentCreate->GetStyleSheet(); + } + else + { + return SdrDragView::GetStyleSheet(); + } +} + +void SdrCreateView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (mpCurrentCreate != nullptr) + { + mpCurrentCreate->SetStyleSheet(pStyleSheet,bDontRemoveHardAttr); + } + else + { + SdrDragView::SetStyleSheet(pStyleSheet,bDontRemoveHardAttr); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svddrag.cxx b/svx/source/svdraw/svddrag.cxx new file mode 100644 index 0000000000..0c785dc674 --- /dev/null +++ b/svx/source/svdraw/svddrag.cxx @@ -0,0 +1,129 @@ +/* -*- 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/svdview.hxx> +#include <svx/svddrag.hxx> + +SdrDragStatUserData::~SdrDragStatUserData() = default; + +SdrDragStat::~SdrDragStat() +{ +} + +void SdrDragStat::Clear() +{ + mpUserData.reset(); + mvPnts.clear(); + mvPnts.emplace_back(); +} + +void SdrDragStat::Reset() +{ + pView=nullptr; + pPageView=nullptr; + bShown=false; + nMinMov=1; + bMinMoved=false; + bHorFixed=false; + bVerFixed=false; + bWantNoSnap=false; + pHdl=nullptr; + bOrtho4=false; + bOrtho8=false; + pDragMethod=nullptr; + bEndDragChangesAttributes=false; + bEndDragChangesGeoAndAttributes=false; + mbEndDragChangesLayout=false; + bMouseIsUp=false; + Clear(); + aActionRect=tools::Rectangle(); +} + +void SdrDragStat::Reset(const Point& rPnt) +{ + Reset(); + mvPnts[0]=rPnt; + aPos0=rPnt; + aRealNow=rPnt; +} + +void SdrDragStat::NextMove(const Point& rPnt) +{ + aPos0=mvPnts.back(); + aRealNow=rPnt; + mvPnts.back()=rPnt; +} + +void SdrDragStat::NextPoint() +{ + mvPnts.emplace_back(aRealNow); +} + +void SdrDragStat::PrevPoint() +{ + if (mvPnts.size()>1) { // one has to remain at all times + mvPnts.erase(mvPnts.begin()+mvPnts.size()-2); + mvPnts.back() = aRealNow; + } +} + +bool SdrDragStat::CheckMinMoved(const Point& rPnt) +{ + if (!bMinMoved) { + tools::Long dx=rPnt.X()-GetPrev().X(); if (dx<0) dx=-dx; + tools::Long dy=rPnt.Y()-GetPrev().Y(); if (dy<0) dy=-dy; + if (dx>=tools::Long(nMinMov) || dy>=tools::Long(nMinMov)) + bMinMoved=true; + } + return bMinMoved; +} + +Fraction SdrDragStat::GetXFact() const +{ + tools::Long nMul=mvPnts.back().X()-aRef1.X(); + tools::Long nDiv=GetPrev().X()-aRef1.X(); + if (nDiv==0) nDiv=1; + if (bHorFixed) { nMul=1; nDiv=1; } + return Fraction(nMul,nDiv); +} + +Fraction SdrDragStat::GetYFact() const +{ + tools::Long nMul=mvPnts.back().Y()-aRef1.Y(); + tools::Long nDiv=GetPrev().Y()-aRef1.Y(); + if (nDiv==0) nDiv=1; + if (bVerFixed) { nMul=1; nDiv=1; } + return Fraction(nMul,nDiv); +} + +void SdrDragStat::TakeCreateRect(tools::Rectangle& rRect) const +{ + rRect=tools::Rectangle(mvPnts[0], mvPnts.back()); + if (mvPnts.size()>1) { + Point aBtmRgt(mvPnts[1]); + rRect.SetRight(aBtmRgt.X() ); + rRect.SetBottom(aBtmRgt.Y() ); + } + if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) { + rRect.AdjustTop(rRect.Top()-rRect.Bottom() ); + rRect.AdjustLeft(rRect.Left()-rRect.Right() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svddrgm1.hxx b/svx/source/svdraw/svddrgm1.hxx new file mode 100644 index 0000000000..54214b9563 --- /dev/null +++ b/svx/source/svdraw/svddrgm1.hxx @@ -0,0 +1,232 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_SVDRAW_SVDDRGM1_HXX +#define INCLUDED_SVX_SOURCE_SVDRAW_SVDDRGM1_HXX + +#include <svx/xpoly.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svddrgv.hxx> +#include <svx/svddrgmt.hxx> + +class SdrDragView; + +class SdrDragMovHdl : public SdrDragMethod +{ +protected: + // define nothing, override to do so + virtual void createSdrDragEntries() override; + +public: + explicit SdrDragMovHdl(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual void CancelSdrDrag() override; + virtual PointerStyle GetSdrDragPointer() const override; +}; + +class SdrDragRotate : public SdrDragMethod +{ +private: + double nSin; + double nCos; + Degree100 nAngle0; + Degree100 nAngle; + bool bRight; + +public: + explicit SdrDragRotate(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + + virtual basegfx::B2DHomMatrix getCurrentTransformation() const override; + virtual void applyCurrentTransformationToSdrObject(SdrObject& rTarget) override; +}; + +class SdrDragShear : public SdrDragMethod +{ +private: + Fraction aFact; + Degree100 nAngle0; + Degree100 nAngle; + double nTan; + bool bVertical; // contort vertically + bool bResize; // shear and resize + bool bUpSideDown; // mirror and shear/slant + bool bSlant; + +public: + SdrDragShear(SdrDragView& rNewView,bool bSlant1); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + + virtual basegfx::B2DHomMatrix getCurrentTransformation() const override; + virtual void applyCurrentTransformationToSdrObject(SdrObject& rTarget) override; +}; + +class SdrDragMirror : public SdrDragMethod +{ +private: + Point aDif; + Degree100 nAngle; + bool bMirrored; + bool bSide0; + + bool ImpCheckSide(const Point& rPnt) const; + +public: + explicit SdrDragMirror(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + + virtual basegfx::B2DHomMatrix getCurrentTransformation() const override; + virtual void applyCurrentTransformationToSdrObject(SdrObject& rTarget) override; +}; + +class SdrDragGradient : public SdrDragMethod +{ +private: + // Handles to work on + SdrHdlGradient* pIAOHandle; + + // is this for gradient (or for transparency)? + bool bIsGradient : 1; + +public: + SdrDragGradient(SdrDragView& rNewView, bool bGrad = true); + + bool IsGradient() const { return bIsGradient; } + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + virtual void CancelSdrDrag() override; +}; + +class SdrDragCrook : public SdrDragMethod +{ +private: + tools::Rectangle aMarkRect; + Point aMarkCenter; + Point aCenter; + Point aStart; + Fraction aFact; + Point aRad; + bool bContortionAllowed; + bool bNoContortionAllowed; + bool bContortion; + bool bResizeAllowed; + bool bResize; + bool bRotateAllowed; + bool bRotate; + bool bVertical; + bool bValid; + bool bLft; + bool bRgt; + bool bUpr; + bool bLwr; + bool bAtCenter; + Degree100 nAngle; + tools::Long nMarkSize; + SdrCrookMode eMode; + + // helpers for applyCurrentTransformationToPolyPolygon + void MovAllPoints(basegfx::B2DPolyPolygon& rTarget); + void MovCrookPoint(Point& rPnt, Point* pC1, Point* pC2); + +protected: + // needs to add drag geometry to the default + virtual void createSdrDragEntries() override; + +public: + explicit SdrDragCrook(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + + virtual void applyCurrentTransformationToSdrObject(SdrObject& rTarget) override; + virtual void applyCurrentTransformationToPolyPolygon(basegfx::B2DPolyPolygon& rTarget) override; +}; + +class SdrDragDistort : public SdrDragMethod +{ +private: + tools::Rectangle aMarkRect; + XPolygon aDistortedRect; + sal_uInt16 nPolyPt; + bool bContortionAllowed; + bool bNoContortionAllowed; + bool bContortion; + + // helper for applyCurrentTransformationToPolyPolygon + void MovAllPoints(basegfx::B2DPolyPolygon& rTarget); + +protected: + // needs to add drag geometry to the default + virtual void createSdrDragEntries() override; + +public: + explicit SdrDragDistort(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual void MoveSdrDrag(const Point& rPnt) override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; + + virtual void applyCurrentTransformationToSdrObject(SdrObject& rTarget) override; + virtual void applyCurrentTransformationToPolyPolygon(basegfx::B2DPolyPolygon& rTarget) override; +}; + +// derive from SdrDragObjOwn to have handles aligned to object when it +// is sheared or rotated +class SdrDragCrop : public SdrDragObjOwn +{ +public: + explicit SdrDragCrop(SdrDragView& rNewView); + + virtual OUString GetSdrDragComment() const override; + virtual bool BeginSdrDrag() override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual PointerStyle GetSdrDragPointer() const override; +}; + +#endif // INCLUDED_SVX_SOURCE_SVDRAW_SVDDRGM1_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svddrgmt.cxx b/svx/source/svdraw/svddrgmt.cxx new file mode 100644 index 0000000000..166b174c89 --- /dev/null +++ b/svx/source/svdraw/svddrgmt.cxx @@ -0,0 +1,3861 @@ +/* -*- 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 "svddrgm1.hxx" +#include <math.h> + +#include <o3tl/numeric.hxx> +#include <osl/diagnose.h> +#include <utility> +#include <vcl/canvastools.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> +#include <svx/xpoly.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdmark.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svddrgv.hxx> +#include <svx/svdograf.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/sdgcpitm.hxx> +#include <svx/sdooitm.hxx> +#include <svx/sdtagitm.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <sdr/overlay/overlayrollingrectangle.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/svditer.hxx> +#include <svx/svdopath.hxx> +#include <svx/polypolygoneditor.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonSelectionPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <sdr/primitive2d/sdrattributecreator.hxx> +#include <sdr/primitive2d/sdrdecompositiontools.hxx> +#include <sdr/primitive2d/sdrprimitivetools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrlinestartendattribute.hxx> +#include <svl/itempool.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <comphelper/lok.hxx> +#include <map> +#include <vector> + + +SdrDragEntry::SdrDragEntry() +: mbAddToTransparent(false) +{ +} + +SdrDragEntry::~SdrDragEntry() +{ +} + + +SdrDragEntryPolyPolygon::SdrDragEntryPolyPolygon(basegfx::B2DPolyPolygon aOriginalPolyPolygon) +: maOriginalPolyPolygon(std::move(aOriginalPolyPolygon)) +{ +} + +SdrDragEntryPolyPolygon::~SdrDragEntryPolyPolygon() +{ +} + +drawinglayer::primitive2d::Primitive2DContainer SdrDragEntryPolyPolygon::createPrimitive2DSequenceInCurrentState(SdrDragMethod& rDragMethod) +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + if(maOriginalPolyPolygon.count()) + { + basegfx::B2DPolyPolygon aCopy(maOriginalPolyPolygon); + + rDragMethod.applyCurrentTransformationToPolyPolygon(aCopy); + basegfx::BColor aColA(SvtOptionsDrawinglayer::GetStripeColorA().getBColor()); + basegfx::BColor aColB(SvtOptionsDrawinglayer::GetStripeColorB().getBColor()); + const double fStripeLength(SvtOptionsDrawinglayer::GetStripeLength()); + + if(Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + aColA = aColB = Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor(); + aColB.invert(); + } + + aRetval.resize(2); + aRetval[0] = new drawinglayer::primitive2d::PolyPolygonMarkerPrimitive2D( + aCopy, + aColA, + aColB, + fStripeLength); + + const basegfx::BColor aHilightColor(SvtOptionsDrawinglayer::getHilightColor().getBColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + + aRetval[1] = new drawinglayer::primitive2d::PolyPolygonSelectionPrimitive2D( + std::move(aCopy), + aHilightColor, + fTransparence, + 3.0, + false); + } + + return aRetval; +} + + +SdrDragEntrySdrObject::SdrDragEntrySdrObject( + const SdrObject& rOriginal, + bool bModify) +: maOriginal(rOriginal), + mbModify(bModify) +{ + // add SdrObject parts to transparent overlay stuff + setAddToTransparent(true); +} + +SdrDragEntrySdrObject::~SdrDragEntrySdrObject() +{ +} + +void SdrDragEntrySdrObject::prepareCurrentState(SdrDragMethod& rDragMethod) +{ + // for the moment, i need to re-create the clone in all cases. I need to figure + // out when clone and original have the same class, so that i can use operator= + // in those cases + + mxClone.clear(); + + if(mbModify) + { + mxClone = maOriginal.getFullDragClone(); + + // apply original transformation, implemented at the DragMethods + rDragMethod.applyCurrentTransformationToSdrObject(*mxClone); + } +} + +drawinglayer::primitive2d::Primitive2DContainer SdrDragEntrySdrObject::createPrimitive2DSequenceInCurrentState(SdrDragMethod&) +{ + const SdrObject* pSource = &maOriginal; + + if(mbModify && mxClone) + { + // choose source for geometry data + pSource = mxClone.get(); + } + + // use the view-independent primitive representation (without + // evtl. GridOffset, that may be applied to the DragEntry individually) + drawinglayer::primitive2d::Primitive2DContainer xRetval; + pSource->GetViewContact().getViewIndependentPrimitive2DContainer(xRetval); + return xRetval; +} + + +SdrDragEntryPrimitive2DSequence::SdrDragEntryPrimitive2DSequence( + drawinglayer::primitive2d::Primitive2DContainer&& rSequence) +: maPrimitive2DSequence(std::move(rSequence)) +{ + // add parts to transparent overlay stuff if necessary + setAddToTransparent(true); +} + +SdrDragEntryPrimitive2DSequence::~SdrDragEntryPrimitive2DSequence() +{ +} + +drawinglayer::primitive2d::Primitive2DContainer SdrDragEntryPrimitive2DSequence::createPrimitive2DSequenceInCurrentState(SdrDragMethod& rDragMethod) +{ + drawinglayer::primitive2d::Primitive2DReference aTransformPrimitive2D( + new drawinglayer::primitive2d::TransformPrimitive2D( + rDragMethod.getCurrentTransformation(), + drawinglayer::primitive2d::Primitive2DContainer(maPrimitive2DSequence))); + + return drawinglayer::primitive2d::Primitive2DContainer { aTransformPrimitive2D }; +} + + +SdrDragEntryPointGlueDrag::SdrDragEntryPointGlueDrag(std::vector< basegfx::B2DPoint >&& rPositions, bool bIsPointDrag) +: maPositions(std::move(rPositions)), + mbIsPointDrag(bIsPointDrag) +{ + // add SdrObject parts to transparent overlay stuff + setAddToTransparent(true); +} + +SdrDragEntryPointGlueDrag::~SdrDragEntryPointGlueDrag() +{ +} + +drawinglayer::primitive2d::Primitive2DContainer SdrDragEntryPointGlueDrag::createPrimitive2DSequenceInCurrentState(SdrDragMethod& rDragMethod) +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + if(!maPositions.empty()) + { + basegfx::B2DPolygon aPolygon; + + for(auto const & a: maPositions) + { + aPolygon.append(a); + } + + basegfx::B2DPolyPolygon aPolyPolygon(aPolygon); + + rDragMethod.applyCurrentTransformationToPolyPolygon(aPolyPolygon); + + const basegfx::B2DPolygon aTransformed(aPolyPolygon.getB2DPolygon(0)); + std::vector< basegfx::B2DPoint > aTransformedPositions; + + aTransformedPositions.reserve(aTransformed.count()); + + for(sal_uInt32 a = 0; a < aTransformed.count(); a++) + { + aTransformedPositions.push_back(aTransformed.getB2DPoint(a)); + } + + if(mbIsPointDrag) + { + basegfx::BColor aColor(SvtOptionsDrawinglayer::GetStripeColorA().getBColor()); + + if(Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + aColor = Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor(); + } + + drawinglayer::primitive2d::Primitive2DReference aMarkerArrayPrimitive2D( + new drawinglayer::primitive2d::MarkerArrayPrimitive2D(std::move(aTransformedPositions), + drawinglayer::primitive2d::createDefaultCross_3x3(aColor))); + + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aMarkerArrayPrimitive2D }; + } + else + { + drawinglayer::primitive2d::Primitive2DReference aMarkerArrayPrimitive2D( + new drawinglayer::primitive2d::MarkerArrayPrimitive2D(std::move(aTransformedPositions), + SdrHdl::createGluePointBitmap())); + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aMarkerArrayPrimitive2D }; + } + } + + return aRetval; +} + + +void SdrDragMethod::resetSdrDragEntries() +{ + // clear entries; creation is on demand + clearSdrDragEntries(); +} + +basegfx::B2DRange SdrDragMethod::getCurrentRange() const +{ + return maOverlayObjectList.getBaseRange(); +} + +void SdrDragMethod::clearSdrDragEntries() +{ + maSdrDragEntries.clear(); +} + +void SdrDragMethod::addSdrDragEntry(std::unique_ptr<SdrDragEntry> pNew) +{ + assert(pNew); + maSdrDragEntries.push_back(std::move(pNew)); +} + +void SdrDragMethod::createSdrDragEntries() +{ + if(!(getSdrDragView().GetSdrPageView() && getSdrDragView().GetSdrPageView()->HasMarkedObjPageView())) + return; + + if(getSdrDragView().IsDraggingPoints()) + { + createSdrDragEntries_PointDrag(); + } + else if(getSdrDragView().IsDraggingGluePoints()) + { + createSdrDragEntries_GlueDrag(); + } + else + { + if(getSolidDraggingActive()) + { + createSdrDragEntries_SolidDrag(); + } + else + { + createSdrDragEntries_PolygonDrag(); + } + } +} + +void SdrDragMethod::createSdrDragEntryForSdrObject(const SdrObject& rOriginal) +{ + // add full object drag; Clone() at the object has to work + // for this + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntrySdrObject(rOriginal, true/*bModify*/))); +} + +void SdrDragMethod::insertNewlyCreatedOverlayObjectForSdrDragMethod( + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject, + const sdr::contact::ObjectContact& rObjectContact, + sdr::overlay::OverlayManager& rOverlayManager) +{ + // check if we have an OverlayObject + if(!pOverlayObject) + { + return; + } + + // add to OverlayManager + rOverlayManager.add(*pOverlayObject); + + // Add GridOffset for non-linear ViewToDevice transformation (calc) + if(rObjectContact.supportsGridOffsets()) + { + const basegfx::B2DRange& rNewRange(pOverlayObject->getBaseRange()); + + if(!rNewRange.isEmpty()) + { + basegfx::B2DVector aOffset(0.0, 0.0); + rObjectContact.calculateGridOffsetForB2DRange(aOffset, rNewRange); + + if(!aOffset.equalZero()) + { + pOverlayObject->setOffset(aOffset); + } + } + } + + // add to local OverlayObjectList - ownership change (!) + maOverlayObjectList.append(std::move(pOverlayObject)); +} + +void SdrDragMethod::createSdrDragEntries_SolidDrag() +{ + const size_t nMarkCount(getSdrDragView().GetMarkedObjectCount()); + SdrPageView* pPV = getSdrDragView().GetSdrPageView(); + + if(!pPV) + return; + + for(size_t a = 0; a < nMarkCount; ++a) + { + SdrMark* pM = getSdrDragView().GetSdrMarkByIndex(a); + + if(pM->GetPageView() == pPV) + { + const SdrObject* pObject = pM->GetMarkedSdrObj(); + + if(pObject) + { + if(pPV->PageWindowCount()) + { + SdrObjListIter aIter(*pObject); + + while(aIter.IsMore()) + { + SdrObject* pCandidate = aIter.Next(); + + if(pCandidate) + { + const bool bSuppressFullDrag(!pCandidate->supportsFullDrag()); + bool bAddWireframe(bSuppressFullDrag); + + if(!bAddWireframe && !pCandidate->HasLineStyle()) + { + // add wireframe for objects without outline + bAddWireframe = true; + } + + if(!bSuppressFullDrag) + { + // add full object drag; Clone() at the object has to work + // for this + createSdrDragEntryForSdrObject(*pCandidate); + } + + if(bAddWireframe) + { + // when dragging a 50% transparent copy of a filled or not filled object without + // outline, this is normally hard to see. Add extra wireframe in that case. This + // works nice e.g. with text frames etc. + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(pCandidate->TakeXorPoly()))); + } + } + } + } + } + } + } +} + +void SdrDragMethod::createSdrDragEntries_PolygonDrag() +{ + const size_t nMarkCount(getSdrDragView().GetMarkedObjectCount()); + bool bNoPolygons(getSdrDragView().IsNoDragXorPolys() || nMarkCount > SdrDragView::GetDragXorPolyLimit()); + basegfx::B2DPolyPolygon aResult; + sal_uInt32 nPointCount(0); + + for(size_t a = 0; !bNoPolygons && a < nMarkCount; ++a) + { + SdrMark* pM = getSdrDragView().GetSdrMarkByIndex(a); + + if(pM->GetPageView() == getSdrDragView().GetSdrPageView()) + { + const basegfx::B2DPolyPolygon aNewPolyPolygon(pM->GetMarkedSdrObj()->TakeXorPoly()); + + for(auto const& rPolygon : aNewPolyPolygon) + { + nPointCount += rPolygon.count(); + } + + if(nPointCount > SdrDragView::GetDragXorPointLimit()) + { + bNoPolygons = true; + } + + if(!bNoPolygons) + { + aResult.append(aNewPolyPolygon); + } + } + } + + if(bNoPolygons) + { + const tools::Rectangle aR(getSdrDragView().GetSdrPageView()->MarkSnap()); + const basegfx::B2DRange aNewRectangle = vcl::unotools::b2DRectangleFromRectangle(aR); + basegfx::B2DPolygon aNewPolygon(basegfx::utils::createPolygonFromRect(aNewRectangle)); + + aResult = basegfx::B2DPolyPolygon(basegfx::utils::expandToCurve(aNewPolygon)); + } + + if(aResult.count()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(std::move(aResult)))); + } +} + +void SdrDragMethod::createSdrDragEntries_PointDrag() +{ + const size_t nMarkCount(getSdrDragView().GetMarkedObjectCount()); + std::vector< basegfx::B2DPoint > aPositions; + + for(size_t nm = 0; nm < nMarkCount; ++nm) + { + SdrMark* pM = getSdrDragView().GetSdrMarkByIndex(nm); + + if(pM->GetPageView() == getSdrDragView().GetSdrPageView()) + { + const SdrUShortCont& rPts = pM->GetMarkedPoints(); + + if (!rPts.empty()) + { + const SdrObject* pObj = pM->GetMarkedSdrObj(); + const SdrPathObj* pPath = dynamic_cast< const SdrPathObj* >(pObj); + + if(pPath) + { + const basegfx::B2DPolyPolygon& aPathXPP = pPath->GetPathPoly(); + + if(aPathXPP.count()) + { + for(const sal_uInt16 nObjPt : rPts) + { + sal_uInt32 nPolyNum, nPointNum; + + if(sdr::PolyPolygonEditor::GetRelativePolyPoint(aPathXPP, nObjPt, nPolyNum, nPointNum)) + { + aPositions.push_back(aPathXPP.getB2DPolygon(nPolyNum).getB2DPoint(nPointNum)); + } + } + } + } + } + } + } + + if(!aPositions.empty()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPointGlueDrag(std::move(aPositions), true))); + } +} + +void SdrDragMethod::createSdrDragEntries_GlueDrag() +{ + const size_t nMarkCount(getSdrDragView().GetMarkedObjectCount()); + std::vector< basegfx::B2DPoint > aPositions; + + for(size_t nm = 0; nm < nMarkCount; ++nm) + { + SdrMark* pM = getSdrDragView().GetSdrMarkByIndex(nm); + + if(pM->GetPageView() == getSdrDragView().GetSdrPageView()) + { + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + + if (!rPts.empty()) + { + const SdrObject* pObj = pM->GetMarkedSdrObj(); + const SdrGluePointList* pGPL = pObj->GetGluePointList(); + + if (pGPL) + { + for(const sal_uInt16 nObjPt : rPts) + { + const sal_uInt16 nGlueNum(pGPL->FindGluePoint(nObjPt)); + + if(SDRGLUEPOINT_NOTFOUND != nGlueNum) + { + const Point aPoint((*pGPL)[nGlueNum].GetAbsolutePos(*pObj)); + aPositions.emplace_back(aPoint.X(), aPoint.Y()); + } + } + } + } + } + } + + if(!aPositions.empty()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPointGlueDrag(std::move(aPositions), false))); + } +} + +OUString SdrDragMethod::ImpGetDescriptionStr(TranslateId pStrCacheID) const +{ + ImpGetDescriptionOptions nOpt=ImpGetDescriptionOptions::NONE; + if (IsDraggingPoints()) { + nOpt=ImpGetDescriptionOptions::POINTS; + } else if (IsDraggingGluePoints()) { + nOpt=ImpGetDescriptionOptions::GLUEPOINTS; + } + return getSdrDragView().ImpGetDescriptionString(pStrCacheID, nOpt); +} + +SdrObject* SdrDragMethod::GetDragObj() const +{ + SdrObject* pObj=nullptr; + if (getSdrDragView().mpDragHdl!=nullptr) pObj=getSdrDragView().mpDragHdl->GetObj(); + if (pObj==nullptr) pObj=getSdrDragView().mpMarkedObj; + return pObj; +} + +SdrPageView* SdrDragMethod::GetDragPV() const +{ + SdrPageView* pPV=nullptr; + if (getSdrDragView().mpDragHdl!=nullptr) pPV=getSdrDragView().mpDragHdl->GetPageView(); + if (pPV==nullptr) pPV=getSdrDragView().mpMarkedPV; + return pPV; +} + +void SdrDragMethod::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + // the original applies the transformation using TRGetBaseGeometry/TRSetBaseGeometry. + // Later this should be the only needed one for linear transforms (not for SdrDragCrook and + // SdrDragDistort, those are NOT linear). Currently, this can not yet be used since the + // special handling of rotate/mirror due to the not-being-able to handle it in the old + // drawinglayer stuff. Text would currently not correctly be mirrored in the preview. + basegfx::B2DHomMatrix aObjectTransform; + basegfx::B2DPolyPolygon aObjectPolyPolygon; + bool bPolyUsed(rTarget.TRGetBaseGeometry(aObjectTransform, aObjectPolyPolygon)); + + // apply transform to object transform + aObjectTransform *= getCurrentTransformation(); + + if(bPolyUsed) + { + // do something special since the object size is in the polygon + // break up matrix to get the scale + const basegfx::utils::B2DHomMatrixBufferedDecompose aTmpDecomp(aObjectTransform); + + // get polygon's position and size + const basegfx::B2DRange aPolyRange(aObjectPolyPolygon.getB2DRange()); + + // get the scaling factors (do not mirror, this is in the object transformation) + const double fScaleX(fabs(aTmpDecomp.getScale().getX()) / (basegfx::fTools::equalZero(aPolyRange.getWidth()) ? 1.0 : aPolyRange.getWidth())); + const double fScaleY(fabs(aTmpDecomp.getScale().getY()) / (basegfx::fTools::equalZero(aPolyRange.getHeight()) ? 1.0 : aPolyRange.getHeight())); + + // prepare transform matrix for polygon + basegfx::B2DHomMatrix aPolyTransform( + basegfx::utils::createTranslateB2DHomMatrix( + -aPolyRange.getMinX(), + -aPolyRange.getMinY())); + aPolyTransform.scale(fScaleX, fScaleY); + + // transform the polygon + aObjectPolyPolygon.transform(aPolyTransform); + } + + rTarget.TRSetBaseGeometry(getCurrentTransformation() * aObjectTransform, aObjectPolyPolygon); +} + +void SdrDragMethod::applyCurrentTransformationToPolyPolygon(basegfx::B2DPolyPolygon& rTarget) +{ + // original uses CurrentTransformation + rTarget.transform(getCurrentTransformation()); +} + +SdrDragMethod::SdrDragMethod(SdrDragView& rNewView) +: mrSdrDragView(rNewView), + mbMoveOnly(false), + mbSolidDraggingActive(getSdrDragView().IsSolidDragging()), + mbShiftPressed(false) +{ + if(mbSolidDraggingActive && Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + // fallback to wireframe when high contrast is used + mbSolidDraggingActive = false; + } +} + +SdrDragMethod::~SdrDragMethod() +{ + clearSdrDragEntries(); +} + +void SdrDragMethod::Show() +{ + getSdrDragView().ShowDragObj(); +} + +void SdrDragMethod::Hide() +{ + getSdrDragView().HideDragObj(); +} + +basegfx::B2DHomMatrix SdrDragMethod::getCurrentTransformation() const +{ + return basegfx::B2DHomMatrix(); +} + +void SdrDragMethod::CancelSdrDrag() +{ + Hide(); +} + +typedef std::map< const SdrObject*, SdrObject* > SdrObjectAndCloneMap; + +void SdrDragMethod::CreateOverlayGeometry( + sdr::overlay::OverlayManager& rOverlayManager, + const sdr::contact::ObjectContact& rObjectContact) +{ + // We do client-side object manipulation with the Kit API + if (comphelper::LibreOfficeKit::isActive()) + return; + + // create SdrDragEntries on demand + if(maSdrDragEntries.empty()) + { + createSdrDragEntries(); + } + + // if there are entries, derive OverlayObjects from the entries, including + // modification from current interactive state + if(!maSdrDragEntries.empty()) + { + // #i54102# SdrDragEntrySdrObject creates clones of SdrObjects as base for creating the needed + // primitives, holding the original and the clone. If connectors (Edges) are involved, + // the cloned connectors need to be connected to the cloned SdrObjects (after cloning + // they are connected to the original SdrObjects). To do so, trigger the preparation + // steps for SdrDragEntrySdrObject, save an association of (orig, clone) in a helper + // and evtl. remember if it was an edge + SdrObjectAndCloneMap aOriginalAndClones; + std::vector< SdrEdgeObj* > aEdges; + + // #i54102# execute prepareCurrentState for all SdrDragEntrySdrObject, register pair of original and + // clone, remember edges + for(auto const & a: maSdrDragEntries) + { + SdrDragEntrySdrObject* pSdrDragEntrySdrObject = dynamic_cast< SdrDragEntrySdrObject*>(a.get()); + + if(pSdrDragEntrySdrObject) + { + pSdrDragEntrySdrObject->prepareCurrentState(*this); + + SdrEdgeObj* pSdrEdgeObj = dynamic_cast< SdrEdgeObj* >(pSdrDragEntrySdrObject->getClone()); + + if(pSdrEdgeObj) + { + aEdges.push_back(pSdrEdgeObj); + } + + if(pSdrDragEntrySdrObject->getClone()) + { + aOriginalAndClones[&pSdrDragEntrySdrObject->getOriginal()] = pSdrDragEntrySdrObject->getClone(); + } + } + } + + // #i54102# if there are edges, reconnect their ends to the corresponding clones (if found) + for(SdrEdgeObj* pSdrEdgeObj: aEdges) + { + SdrObject* pConnectedTo = pSdrEdgeObj->GetConnectedNode(true); + + if(pConnectedTo) + { + SdrObjectAndCloneMap::iterator aEntry = aOriginalAndClones.find(pConnectedTo); + + if(aEntry != aOriginalAndClones.end()) + { + pSdrEdgeObj->ConnectToNode(true, aEntry->second); + } + } + + pConnectedTo = pSdrEdgeObj->GetConnectedNode(false); + + if(pConnectedTo) + { + SdrObjectAndCloneMap::iterator aEntry = aOriginalAndClones.find(pConnectedTo); + + if(aEntry != aOriginalAndClones.end()) + { + pSdrEdgeObj->ConnectToNode(false, aEntry->second); + } + } + } + + // collect primitives for visualisation + drawinglayer::primitive2d::Primitive2DContainer aResult; + drawinglayer::primitive2d::Primitive2DContainer aResultTransparent; + + for(auto & pCandidate: maSdrDragEntries) + { + const drawinglayer::primitive2d::Primitive2DContainer aCandidateResult(pCandidate->createPrimitive2DSequenceInCurrentState(*this)); + + if(!aCandidateResult.empty()) + { + if(pCandidate->getAddToTransparent()) + { + aResultTransparent.append(aCandidateResult); + } + else + { + aResult.append(aCandidateResult); + } + } + } + + if(DoAddConnectorOverlays()) + { + const drawinglayer::primitive2d::Primitive2DContainer aConnectorOverlays(AddConnectorOverlays()); + + if(!aConnectorOverlays.empty()) + { + // add connector overlays to transparent part + aResultTransparent.append(aConnectorOverlays); + } + } + + if(!aResult.empty()) + { + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject( + new sdr::overlay::OverlayPrimitive2DSequenceObject( + std::move(aResult))); + + insertNewlyCreatedOverlayObjectForSdrDragMethod( + std::move(pNewOverlayObject), + rObjectContact, + rOverlayManager); + } + + if(!aResultTransparent.empty()) + { + drawinglayer::primitive2d::Primitive2DReference aUnifiedTransparencePrimitive2D(new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(std::move(aResultTransparent), 0.5)); + aResultTransparent = drawinglayer::primitive2d::Primitive2DContainer { aUnifiedTransparencePrimitive2D }; + + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject( + new sdr::overlay::OverlayPrimitive2DSequenceObject( + std::move(aResultTransparent))); + + insertNewlyCreatedOverlayObjectForSdrDragMethod( + std::move(pNewOverlayObject), + rObjectContact, + rOverlayManager); + } + } + + // add DragStripes if necessary (help lines cross the page when dragging) + if(!getSdrDragView().IsDragStripes()) + return; + + tools::Rectangle aActionRectangle; + getSdrDragView().TakeActionRect(aActionRectangle); + + const basegfx::B2DPoint aTopLeft(aActionRectangle.Left(), aActionRectangle.Top()); + const basegfx::B2DPoint aBottomRight(aActionRectangle.Right(), aActionRectangle.Bottom()); + std::unique_ptr<sdr::overlay::OverlayRollingRectangleStriped> pNew( + new sdr::overlay::OverlayRollingRectangleStriped( + aTopLeft, + aBottomRight, + true, + false)); + + insertNewlyCreatedOverlayObjectForSdrDragMethod( + std::move(pNew), + rObjectContact, + rOverlayManager); +} + +void SdrDragMethod::destroyOverlayGeometry() +{ + maOverlayObjectList.clear(); +} + +bool SdrDragMethod::DoAddConnectorOverlays() +{ + // these conditions are translated from SdrDragView::ImpDrawEdgeXor + const SdrMarkList& rMarkedNodes = getSdrDragView().GetEdgesOfMarkedNodes(); + + if(!rMarkedNodes.GetMarkCount()) + { + return false; + } + + if(getSdrDragView().IsDraggingPoints() || getSdrDragView().IsDraggingGluePoints()) + { + return false; + } + + if(!getMoveOnly() && !( + dynamic_cast<const SdrDragMove*>(this) != nullptr || dynamic_cast<const SdrDragResize*>(this) != nullptr || + dynamic_cast<const SdrDragRotate*>(this) != nullptr || dynamic_cast<const SdrDragMirror*>(this) != nullptr )) + { + return false; + } + + // one more migrated from SdrEdgeObj::NspToggleEdgeXor + if( dynamic_cast< const SdrDragObjOwn* >(this) != nullptr || dynamic_cast< const SdrDragMovHdl* >(this) != nullptr ) + { + return false; + } + + return true; +} + +drawinglayer::primitive2d::Primitive2DContainer SdrDragMethod::AddConnectorOverlays() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + const bool bDetail(getMoveOnly()); + const SdrMarkList& rMarkedNodes = getSdrDragView().GetEdgesOfMarkedNodes(); + + for(size_t a = 0; a < rMarkedNodes.GetMarkCount(); ++a) + { + SdrMark* pEM = rMarkedNodes.GetMark(a); + + if(pEM && pEM->GetMarkedSdrObj()) + { + SdrEdgeObj* pEdge = dynamic_cast< SdrEdgeObj* >(pEM->GetMarkedSdrObj()); + + if(pEdge) + { + basegfx::B2DPolygon aEdgePolygon(pEdge->ImplAddConnectorOverlay(*this, pEM->IsCon1(), pEM->IsCon2(), bDetail)); + + if(aEdgePolygon.count()) + { + // this polygon is a temporary calculated connector path, so it is not possible to fetch + // the needed primitives directly from the pEdge object which does not get changed. If full + // drag is on, use the SdrObjects ItemSet to create an adequate representation + bool bUseSolidDragging(getSolidDraggingActive()); + + if(bUseSolidDragging) + { + // switch off solid dragging if connector is not visible + if(!pEdge->HasLineStyle()) + { + bUseSolidDragging = false; + } + } + + if(bUseSolidDragging) + { + const SfxItemSet& rItemSet = pEdge->GetMergedItemSet(); + const drawinglayer::attribute::SdrLineAttribute aLine( + drawinglayer::primitive2d::createNewSdrLineAttribute(rItemSet)); + + if(!aLine.isDefault()) + { + const drawinglayer::attribute::SdrLineStartEndAttribute aLineStartEnd( + drawinglayer::primitive2d::createNewSdrLineStartEndAttribute( + rItemSet, + aLine.getWidth())); + + aRetval.push_back(drawinglayer::primitive2d::createPolygonLinePrimitive( + aEdgePolygon, + aLine, + aLineStartEnd)); + } + } + else + { + basegfx::BColor aColA(SvtOptionsDrawinglayer::GetStripeColorA().getBColor()); + basegfx::BColor aColB(SvtOptionsDrawinglayer::GetStripeColorB().getBColor()); + const double fStripeLength(SvtOptionsDrawinglayer::GetStripeLength()); + + if(Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + aColA = aColB = Application::GetSettings().GetStyleSettings().GetHighlightColor().getBColor(); + aColB.invert(); + } + + drawinglayer::primitive2d::Primitive2DReference aPolyPolygonMarkerPrimitive2D( + new drawinglayer::primitive2d::PolygonMarkerPrimitive2D( + std::move(aEdgePolygon), aColA, aColB, fStripeLength)); + aRetval.push_back(aPolyPolygonMarkerPrimitive2D); + } + } + } + } + } + + return aRetval; +} + + +SdrDragMovHdl::SdrDragMovHdl(SdrDragView& rNewView) +: SdrDragMethod(rNewView) +{ +} + +void SdrDragMovHdl::createSdrDragEntries() +{ + // SdrDragMovHdl does not use the default drags, + // but creates nothing +} + +OUString SdrDragMovHdl::GetSdrDragComment() const +{ + OUString aStr=SvxResId(STR_DragMethMovHdl); + if (getSdrDragView().IsDragWithCopy()) aStr+=SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragMovHdl::BeginSdrDrag() +{ + if( !GetDragHdl() ) + return false; + + DragStat().SetRef1(GetDragHdl()->GetPos()); + DragStat().SetShown(!DragStat().IsShown()); + SdrHdlKind eKind=GetDragHdl()->GetKind(); + SdrHdl* pH1=GetHdlList().GetHdl(SdrHdlKind::Ref1); + SdrHdl* pH2=GetHdlList().GetHdl(SdrHdlKind::Ref2); + + if (eKind==SdrHdlKind::MirrorAxis) + { + if (pH1==nullptr || pH2==nullptr) + { + OSL_FAIL("SdrDragMovHdl::BeginSdrDrag(): Moving the axis of reflection: reference handles not found."); + return false; + } + + DragStat().SetActionRect(tools::Rectangle(pH1->GetPos(),pH2->GetPos())); + } + else + { + Point aPt(GetDragHdl()->GetPos()); + DragStat().SetActionRect(tools::Rectangle(aPt,aPt)); + } + + return true; +} + +void SdrDragMovHdl::MoveSdrDrag(const Point& rNoSnapPnt) +{ + Point aPnt(rNoSnapPnt); + + if ( !(GetDragHdl() && DragStat().CheckMinMoved(rNoSnapPnt))) + return; + + if (GetDragHdl()->GetKind()==SdrHdlKind::MirrorAxis) + { + SdrHdl* pH1=GetHdlList().GetHdl(SdrHdlKind::Ref1); + SdrHdl* pH2=GetHdlList().GetHdl(SdrHdlKind::Ref2); + + if (pH1==nullptr || pH2==nullptr) + return; + + if (!DragStat().IsNoSnap()) + { + tools::Long nBestXSnap=0; + tools::Long nBestYSnap=0; + bool bXSnapped=false; + bool bYSnapped=false; + Point aDif(aPnt-DragStat().GetStart()); + getSdrDragView().CheckSnap(Ref1()+aDif,nBestXSnap,nBestYSnap,bXSnapped,bYSnapped); + getSdrDragView().CheckSnap(Ref2()+aDif,nBestXSnap,nBestYSnap,bXSnapped,bYSnapped); + aPnt.AdjustX(nBestXSnap ); + aPnt.AdjustY(nBestYSnap ); + } + + if (aPnt!=DragStat().GetNow()) + { + Hide(); + DragStat().NextMove(aPnt); + Point aDif(DragStat().GetNow()-DragStat().GetStart()); + pH1->SetPos(Ref1()+aDif); + pH2->SetPos(Ref2()+aDif); + + SdrHdl* pHM = GetHdlList().GetHdl(SdrHdlKind::MirrorAxis); + + if(pHM) + pHM->Touch(); + + Show(); + DragStat().SetActionRect(tools::Rectangle(pH1->GetPos(),pH2->GetPos())); + } + } + else + { + if (!DragStat().IsNoSnap()) SnapPos(aPnt); + Degree100 nSA(0); + + if (getSdrDragView().IsAngleSnapEnabled()) + nSA=getSdrDragView().GetSnapAngle(); + + if (getSdrDragView().IsMirrorAllowed(true,true)) + { // limited + if (!getSdrDragView().IsMirrorAllowed()) nSA=4500_deg100; + if (!getSdrDragView().IsMirrorAllowed(true)) nSA=9000_deg100; + } + + if (getSdrDragView().IsOrtho() && nSA!=9000_deg100) + nSA=4500_deg100; + + if (nSA) + { // angle snapping + SdrHdlKind eRef=SdrHdlKind::Ref1; + + if (GetDragHdl()->GetKind()==SdrHdlKind::Ref1) + eRef=SdrHdlKind::Ref2; + + SdrHdl* pH=GetHdlList().GetHdl(eRef); + + if (pH!=nullptr) + { + Point aRef(pH->GetPos()); + Degree100 nAngle=NormAngle36000(GetAngle(aPnt-aRef)); + Degree100 nNewAngle=nAngle; + nNewAngle+=nSA/2_deg100; + nNewAngle/=nSA; + nNewAngle*=nSA; + nNewAngle=NormAngle36000(nNewAngle); + double a=toRadians(nNewAngle-nAngle); + double nSin=sin(a); + double nCos=cos(a); + RotatePoint(aPnt,aRef,nSin,nCos); + + // eliminate rounding errors for certain values + if (nSA==9000_deg100) + { + if (nNewAngle==0_deg100 || nNewAngle==18000_deg100) aPnt.setY(aRef.Y() ); + if (nNewAngle==9000_deg100 || nNewAngle==27000_deg100) aPnt.setX(aRef.X() ); + } + + if (nSA==4500_deg100) + OrthoDistance8(aRef,aPnt,true); + } + } + + if (aPnt!=DragStat().GetNow()) + { + Hide(); + DragStat().NextMove(aPnt); + GetDragHdl()->SetPos(DragStat().GetNow()); + SdrHdl* pHM = GetHdlList().GetHdl(SdrHdlKind::MirrorAxis); + + if(pHM) + pHM->Touch(); + + Show(); + DragStat().SetActionRect(tools::Rectangle(aPnt,aPnt)); + } + } +} + +bool SdrDragMovHdl::EndSdrDrag(bool /*bCopy*/) +{ + if( GetDragHdl() ) + { + switch (GetDragHdl()->GetKind()) + { + case SdrHdlKind::Ref1: + Ref1()=DragStat().GetNow(); + break; + + case SdrHdlKind::Ref2: + Ref2()=DragStat().GetNow(); + break; + + case SdrHdlKind::MirrorAxis: + Ref1()+=DragStat().GetNow()-DragStat().GetStart(); + Ref2()+=DragStat().GetNow()-DragStat().GetStart(); + break; + + default: break; + } + } + + return true; +} + +void SdrDragMovHdl::CancelSdrDrag() +{ + Hide(); + + SdrHdl* pHdl = GetDragHdl(); + if( pHdl ) + pHdl->SetPos(DragStat().GetRef1()); + + SdrHdl* pHM = GetHdlList().GetHdl(SdrHdlKind::MirrorAxis); + + if(pHM) + pHM->Touch(); +} + +PointerStyle SdrDragMovHdl::GetSdrDragPointer() const +{ + const SdrHdl* pHdl = GetDragHdl(); + + if (pHdl!=nullptr) + { + return pHdl->GetPointer(); + } + + return PointerStyle::RefHand; +} + + +SdrDragObjOwn::SdrDragObjOwn(SdrDragView& rNewView) +: SdrDragMethod(rNewView) +{ + const SdrObject* pObj = GetDragObj(); + + if(pObj) + { + // suppress full drag for some object types + setSolidDraggingActive(pObj->supportsFullDrag()); + } +} + +SdrDragObjOwn::~SdrDragObjOwn() +{ +} + +void SdrDragObjOwn::createSdrDragEntries() +{ + if(!mxClone) + return; + + basegfx::B2DPolyPolygon aDragPolyPolygon; + bool bAddWireframe(true); + + if(getSolidDraggingActive()) + { + SdrPageView* pPV = getSdrDragView().GetSdrPageView(); + + if(pPV && pPV->PageWindowCount()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntrySdrObject(*mxClone, false))); + + // potentially no wireframe needed, full drag works + bAddWireframe = false; + } + } + + if(!bAddWireframe) + { + // check for extra conditions for wireframe, e.g. no border at + // objects + if(!mxClone->HasLineStyle()) + { + bAddWireframe = true; + } + } + + if(bAddWireframe) + { + // use wireframe poly when full drag is off or did not work + aDragPolyPolygon = mxClone->TakeXorPoly(); + } + + // add evtl. extra DragPolyPolygon + const basegfx::B2DPolyPolygon aSpecialDragPolyPolygon(mxClone->getSpecialDragPoly(DragStat())); + + if(aSpecialDragPolyPolygon.count()) + { + aDragPolyPolygon.append(aSpecialDragPolyPolygon); + } + + if(aDragPolyPolygon.count()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(std::move(aDragPolyPolygon)))); + } +} + +OUString SdrDragObjOwn::GetSdrDragComment() const +{ + OUString aStr; + // #i103058# get info string from the clone preferred, the original will + // not be changed. For security, use original as fallback + if(mxClone) + { + aStr = mxClone->getSpecialDragComment(DragStat()); + } + else + { + const SdrObject* pObj = GetDragObj(); + + if(pObj) + { + aStr = pObj->getSpecialDragComment(DragStat()); + } + } + return aStr; +} + +bool SdrDragObjOwn::BeginSdrDrag() +{ + if(!mxClone) + { + const SdrObject* pObj = GetDragObj(); + + if(pObj && !pObj->IsResizeProtect()) + { + if(pObj->beginSpecialDrag(DragStat())) + { + // create initial clone to have a start visualization + mxClone = pObj->getFullDragClone(); + mxClone->applySpecialDrag(DragStat()); + + return true; + } + } + } + + return false; +} + +void SdrDragObjOwn::MoveSdrDrag(const Point& rNoSnapPnt) +{ + const SdrObject* pObj = GetDragObj(); + + if (!pObj) + // No object to drag. Bail out. + return; + + Point aPnt(rNoSnapPnt); + SdrPageView* pPV = GetDragPV(); + + if (!pPV) + // No page view available. Bail out. + return; + + if(!DragStat().IsNoSnap()) + { + SnapPos(aPnt); + } + if(getSdrDragView().IsOrtho()) + { + if (DragStat().IsOrtho8Possible()) + { + OrthoDistance8(DragStat().GetStart(),aPnt,getSdrDragView().IsBigOrtho()); + } + else if (DragStat().IsOrtho4Possible()) + { + OrthoDistance4(DragStat().GetStart(),aPnt,getSdrDragView().IsBigOrtho()); + } + } + + if (!DragStat().CheckMinMoved(rNoSnapPnt)) + // Not moved by the minimum threshold. Nothing to do. + return; + + Hide(); + DragStat().NextMove(aPnt); + + // since SdrDragObjOwn currently supports no transformation of + // existing SdrDragEntries but only their recreation, a recreation + // after every move is needed in this mode. Delete existing + // SdrDragEntries here to force their recreation in the following Show(). + clearSdrDragEntries(); + + // delete current clone (after the last reference to it is deleted above) + mxClone.clear(); + + // create a new clone and modify to current drag state + mxClone = pObj->getFullDragClone(); + mxClone->applySpecialDrag(DragStat()); + + // AutoGrowWidth may change for SdrTextObj due to the automatism used + // with bDisableAutoWidthOnDragging, so not only geometry changes but + // also this (pretty indirect) property change is possible. If it gets + // changed, it needs to be copied to the original since nothing will + // happen when it only changes in the drag clone + const bool bOldAutoGrowWidth(pObj->GetMergedItem(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue()); + const bool bNewAutoGrowWidth(mxClone->GetMergedItem(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue()); + + if (bOldAutoGrowWidth != bNewAutoGrowWidth) + { + GetDragObj()->SetMergedItem(makeSdrTextAutoGrowWidthItem(bNewAutoGrowWidth)); + } + + Show(); +} + +bool SdrDragObjOwn::EndSdrDrag(bool /*bCopy*/) +{ + Hide(); + std::vector< std::unique_ptr<SdrUndoAction> > vConnectorUndoActions; + bool bRet = false; + SdrObject* pObj = GetDragObj(); + + if(pObj) + { + std::unique_ptr<SdrUndoAction> pUndo; + std::unique_ptr<SdrUndoAction> pUndo2; + const bool bUndo = getSdrDragView().IsUndoEnabled(); + + if( bUndo ) + { + getSdrDragView().EndTextEditCurrentView(); + if(!getSdrDragView().IsInsObjPoint() && pObj->IsInserted() ) + { + if (DragStat().IsEndDragChangesAttributes()) + { + pUndo=getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pObj); + + if (DragStat().IsEndDragChangesGeoAndAttributes()) + { + vConnectorUndoActions = getSdrDragView().CreateConnectorUndo( *pObj ); + pUndo2 = getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj); + } + } + else + { + vConnectorUndoActions = getSdrDragView().CreateConnectorUndo( *pObj ); + pUndo= getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj); + } + } + + if( pUndo ) + { + getSdrDragView().BegUndo( pUndo->GetComment() ); + } + else + { + getSdrDragView().BegUndo(); + } + } + + // Maybe use operator = for setting changed object data (do not change selection in + // view, this will destroy the interactor). This is possible since a clone is now + // directly modified by the modifiers. Only SdrTableObj is adding own UNDOs + // in its SdrTableObj::endSpecialDrag, so currently not possible. OTOH it uses + // a CreateUndoGeoObject(), so maybe setting SetEndDragChangesAttributes is okay. I + // will test this now + tools::Rectangle aBoundRect0; + + if(pObj->GetUserCall()) + { + aBoundRect0 = pObj->GetLastBoundRect(); + } + + bRet = pObj->applySpecialDrag(DragStat()); + if (DragStat().IsEndDragChangesLayout()) + { + auto pGeoUndo = dynamic_cast<SdrUndoGeoObj*>(pUndo.get()); + if (pGeoUndo) + pGeoUndo->SetSkipChangeLayout(true); + } + + if(bRet) + { + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + pObj->SendUserCall( SdrUserCallType::Resize, aBoundRect0 ); + } + + if(bRet && bUndo ) + { + getSdrDragView().AddUndoActions( std::move(vConnectorUndoActions) ); + + if ( pUndo ) + { + getSdrDragView().AddUndo(std::move(pUndo)); + } + + if ( pUndo2 ) + { + getSdrDragView().AddUndo(std::move(pUndo2)); + } + } + + if( bUndo ) + getSdrDragView().EndUndo(); + } + + return bRet; +} + +PointerStyle SdrDragObjOwn::GetSdrDragPointer() const +{ + const SdrHdl* pHdl=GetDragHdl(); + + if (pHdl) + { + return pHdl->GetPointer(); + } + + return PointerStyle::Move; +} + + +void SdrDragMove::createSdrDragEntryForSdrObject(const SdrObject& rOriginal) +{ + // use the view-independent primitive representation (without + // evtl. GridOffset, that may be applied to the DragEntry individually) + drawinglayer::primitive2d::Primitive2DContainer xRetval; + rOriginal.GetViewContact().getViewIndependentPrimitive2DContainer(xRetval); + addSdrDragEntry( + std::unique_ptr<SdrDragEntry>( + new SdrDragEntryPrimitive2DSequence( + std::move(xRetval)))); + +} + +void SdrDragMove::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + rTarget.Move(Size(DragStat().GetDX(), DragStat().GetDY())); +} + +SdrDragMove::SdrDragMove(SdrDragView& rNewView) + : SdrDragMethod(rNewView) + , nBestXSnap(0) + , nBestYSnap(0) + , bXSnapped(false) + , bYSnapped(false) +{ + setMoveOnly(true); +} + +OUString SdrDragMove::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethMove) + + " (x=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDX()) + + " y=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDY()) + + ")"; + + if(getSdrDragView().IsDragWithCopy()) + { + if(!getSdrDragView().IsInsObjPoint() && !getSdrDragView().IsInsGluePoint()) + { + aStr += SvxResId(STR_EditWithCopy); + } + } + return aStr; +} + +bool SdrDragMove::BeginSdrDrag() +{ + DragStat().SetActionRect(GetMarkedRect()); + Show(); + + return true; +} + +basegfx::B2DHomMatrix SdrDragMove::getCurrentTransformation() const +{ + return basegfx::utils::createTranslateB2DHomMatrix(DragStat().GetDX(), DragStat().GetDY()); +} + +void SdrDragMove::ImpCheckSnap(const Point& rPt) +{ + Point aPt(rPt); + SdrSnap nRet=SnapPos(aPt); + aPt-=rPt; + + if (nRet & SdrSnap::XSNAPPED) + { + if (bXSnapped) + { + if (std::abs(aPt.X())<std::abs(nBestXSnap)) + { + nBestXSnap=aPt.X(); + } + } + else + { + nBestXSnap=aPt.X(); + bXSnapped=true; + } + } + + if (!(nRet & SdrSnap::YSNAPPED)) + return; + + if (bYSnapped) + { + if (std::abs(aPt.Y())<std::abs(nBestYSnap)) + { + nBestYSnap=aPt.Y(); + } + } + else + { + nBestYSnap=aPt.Y(); + bYSnapped=true; + } +} + +void SdrDragMove::MoveSdrDrag(const Point& rNoSnapPnt_) +{ + nBestXSnap=0; + nBestYSnap=0; + bXSnapped=false; + bYSnapped=false; + Point aNoSnapPnt(rNoSnapPnt_); + const tools::Rectangle& aSR=GetMarkedRect(); + tools::Long nMovedx=aNoSnapPnt.X()-DragStat().GetStart().X(); + tools::Long nMovedy=aNoSnapPnt.Y()-DragStat().GetStart().Y(); + Point aLO(aSR.TopLeft()); aLO.AdjustX(nMovedx ); aLO.AdjustY(nMovedy ); + Point aRU(aSR.BottomRight()); aRU.AdjustX(nMovedx ); aRU.AdjustY(nMovedy ); + Point aLU(aLO.X(),aRU.Y()); + Point aRO(aRU.X(),aLO.Y()); + ImpCheckSnap(aLO); + + if (!getSdrDragView().IsMoveSnapOnlyTopLeft()) + { + ImpCheckSnap(aRO); + ImpCheckSnap(aLU); + ImpCheckSnap(aRU); + } + + Point aPnt(aNoSnapPnt.X()+nBestXSnap,aNoSnapPnt.Y()+nBestYSnap); + bool bOrtho=getSdrDragView().IsOrtho(); + + if (bOrtho) + OrthoDistance8(DragStat().GetStart(),aPnt,getSdrDragView().IsBigOrtho()); + + if (!DragStat().CheckMinMoved(aNoSnapPnt)) + return; + + Point aPt1(aPnt); + tools::Rectangle aLR(getSdrDragView().GetWorkArea()); + bool bWorkArea=!aLR.IsEmpty(); + bool bDragLimit=IsDragLimit(); + + if (bDragLimit || bWorkArea) + { + tools::Rectangle aSR2(GetMarkedRect()); + Point aD(aPt1-DragStat().GetStart()); + + if (bDragLimit) + { + tools::Rectangle aR2(GetDragLimitRect()); + + if (bWorkArea) + aLR.Intersection(aR2); + else + aLR=aR2; + } + + if (aSR2.Left()>aLR.Left() || aSR2.Right()<aLR.Right()) + { // any space to move to? + aSR2.Move(aD.X(),0); + + if (aSR2.Left()<aLR.Left()) + { + aPt1.AdjustX( -(aSR2.Left()-aLR.Left()) ); + } + else if (aSR2.Right()>aLR.Right()) + { + aPt1.AdjustX( -(aSR2.Right()-aLR.Right()) ); + } + } + else + aPt1.setX(DragStat().GetStart().X() ); // no space to move to + + if (aSR2.Top()>aLR.Top() || aSR2.Bottom()<aLR.Bottom()) + { // any space to move to? + aSR2.Move(0,aD.Y()); + + if (aSR2.Top()<aLR.Top()) + { + aPt1.AdjustY( -(aSR2.Top()-aLR.Top()) ); + } + else if (aSR2.Bottom()>aLR.Bottom()) + { + aPt1.AdjustY( -(aSR2.Bottom()-aLR.Bottom()) ); + } + } + else + aPt1.setY(DragStat().GetStart().Y() ); // no space to move to + } + + if (getSdrDragView().IsDraggingGluePoints()) + { // restrict gluepoints to the BoundRect of the Obj + aPt1-=DragStat().GetStart(); + const SdrMarkList& rML=GetMarkedObjectList(); + const size_t nMarkCount=rML.GetMarkCount(); + + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) + { + const SdrMark* pM=rML.GetMark(nMarkNum); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + + if (!rPts.empty()) + { + const SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + tools::Rectangle aBound(pObj->GetCurrentBoundRect()); + + for (sal_uInt16 nId : rPts) + { + sal_uInt16 nGlueNum=pGPL->FindGluePoint(nId); + + if (nGlueNum!=SDRGLUEPOINT_NOTFOUND) + { + Point aPt((*pGPL)[nGlueNum].GetAbsolutePos(*pObj)); + aPt+=aPt1; // move by this much + if (aPt.X()<aBound.Left() ) aPt1.AdjustX( -(aPt.X()-aBound.Left()) ) ; + if (aPt.X()>aBound.Right() ) aPt1.AdjustX( -(aPt.X()-aBound.Right()) ) ; + if (aPt.Y()<aBound.Top() ) aPt1.AdjustY( -(aPt.Y()-aBound.Top()) ) ; + if (aPt.Y()>aBound.Bottom()) aPt1.AdjustY( -(aPt.Y()-aBound.Bottom()) ); + } + } + } + } + + aPt1+=DragStat().GetStart(); + } + + if (bOrtho) + OrthoDistance8(DragStat().GetStart(),aPt1,false); + + if (aPt1!=DragStat().GetNow()) + { + Hide(); + DragStat().NextMove(aPt1); + tools::Rectangle aAction(GetMarkedRect()); + aAction.Move(DragStat().GetDX(),DragStat().GetDY()); + DragStat().SetActionRect(aAction); + Show(); + } +} + +bool SdrDragMove::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (getSdrDragView().IsInsObjPoint() || getSdrDragView().IsInsGluePoint()) + bCopy=false; + + if (IsDraggingPoints()) + { + getSdrDragView().MoveMarkedPoints(Size(DragStat().GetDX(),DragStat().GetDY())); + } + else if (IsDraggingGluePoints()) + { + getSdrDragView().MoveMarkedGluePoints(Size(DragStat().GetDX(),DragStat().GetDY()),bCopy); + } + else + { + getSdrDragView().MoveMarkedObj(Size(DragStat().GetDX(),DragStat().GetDY()),bCopy); + } + + return true; +} + +PointerStyle SdrDragMove::GetSdrDragPointer() const +{ + if (IsDraggingPoints() || IsDraggingGluePoints()) + { + return PointerStyle::MovePoint; + } + else + { + return PointerStyle::Move; + } +} + + +SdrDragResize::SdrDragResize(SdrDragView& rNewView) +: SdrDragMethod(rNewView), + aXFact(1,1), + aYFact(1,1) +{ +} + +OUString SdrDragResize::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethResize); + Fraction aFact1(1,1); + Point aStart(DragStat().GetStart()); + Point aRef(DragStat().GetRef1()); + sal_Int32 nXDiv(aStart.X() - aRef.X()); + + if(!nXDiv) + nXDiv = 1; + + sal_Int32 nYDiv(aStart.Y() - aRef.Y()); + + if(!nYDiv) + nYDiv = 1; + + bool bX(aXFact != aFact1 && std::abs(nXDiv) > 1); + bool bY(aYFact != aFact1 && std::abs(nYDiv) > 1); + + if(bX || bY) + { + aStr += " ("; + + bool bEqual(aXFact == aYFact); + if(bX) + { + if(!bEqual) + aStr += "x="; + + aStr += SdrModel::GetPercentString(aXFact); + } + + if(bY && !bEqual) + { + if(bX) + aStr += " "; + + aStr += "y=" + SdrModel::GetPercentString(aYFact); + } + + aStr += ")"; + } + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragResize::BeginSdrDrag() +{ + SdrHdlKind eRefHdl=SdrHdlKind::Move; + SdrHdl* pRefHdl=nullptr; + + switch (GetDragHdlKind()) + { + case SdrHdlKind::UpperLeft: eRefHdl=SdrHdlKind::LowerRight; break; + case SdrHdlKind::Upper: eRefHdl=SdrHdlKind::Lower; DragStat().SetHorFixed(true); break; + case SdrHdlKind::UpperRight: eRefHdl=SdrHdlKind::LowerLeft; break; + case SdrHdlKind::Left : eRefHdl=SdrHdlKind::Right; DragStat().SetVerFixed(true); break; + case SdrHdlKind::Right: eRefHdl=SdrHdlKind::Left ; DragStat().SetVerFixed(true); break; + case SdrHdlKind::LowerLeft: eRefHdl=SdrHdlKind::UpperRight; break; + case SdrHdlKind::Lower: eRefHdl=SdrHdlKind::Upper; DragStat().SetHorFixed(true); break; + case SdrHdlKind::LowerRight: eRefHdl=SdrHdlKind::UpperLeft; break; + default: break; + } + + if (eRefHdl!=SdrHdlKind::Move) + pRefHdl=GetHdlList().GetHdl(eRefHdl); + + if (pRefHdl!=nullptr && !getSdrDragView().IsResizeAtCenter()) + { + DragStat().SetRef1(pRefHdl->GetPos()); + } + else + { + SdrHdl* pRef1=GetHdlList().GetHdl(SdrHdlKind::UpperLeft); + SdrHdl* pRef2=GetHdlList().GetHdl(SdrHdlKind::LowerRight); + + if (pRef1!=nullptr && pRef2!=nullptr) + { + DragStat().SetRef1(tools::Rectangle(pRef1->GetPos(),pRef2->GetPos()).Center()); + } + else + { + DragStat().SetRef1(GetMarkedRect().Center()); + } + } + + Show(); + + return true; +} + +basegfx::B2DHomMatrix SdrDragResize::getCurrentTransformation() const +{ + basegfx::B2DHomMatrix aRetval(basegfx::utils::createTranslateB2DHomMatrix( + -DragStat().GetRef1().X(), -DragStat().GetRef1().Y())); + aRetval.scale(double(aXFact), double(aYFact)); + aRetval.translate(DragStat().GetRef1().X(), DragStat().GetRef1().Y()); + + return aRetval; +} + +void SdrDragResize::MoveSdrDrag(const Point& rNoSnapPnt) +{ + Point aPnt(GetSnapPos(rNoSnapPnt)); + Point aStart(DragStat().GetStart()); + Point aRef(DragStat().GetRef1()); + Fraction aMaxFact(0x7FFFFFFF,1); + tools::Rectangle aLR(getSdrDragView().GetWorkArea()); + bool bWorkArea=!aLR.IsEmpty(); + bool bDragLimit=IsDragLimit(); + + if (bDragLimit || bWorkArea) + { + tools::Rectangle aSR(GetMarkedRect()); + + if (bDragLimit) + { + tools::Rectangle aR2(GetDragLimitRect()); + + if (bWorkArea) + aLR.Intersection(aR2); + else + aLR=aR2; + } + + if (aPnt.X()<aLR.Left()) + aPnt.setX(aLR.Left() ); + else if (aPnt.X()>aLR.Right()) + aPnt.setX(aLR.Right() ); + + if (aPnt.Y()<aLR.Top()) + aPnt.setY(aLR.Top() ); + else if (aPnt.Y()>aLR.Bottom()) + aPnt.setY(aLR.Bottom() ); + + if (aRef.X()>aSR.Left()) + { + Fraction aMax(aRef.X()-aLR.Left(),aRef.X()-aSR.Left()); + + if (aMax<aMaxFact) + aMaxFact=aMax; + } + + if (aRef.X()<aSR.Right()) + { + Fraction aMax(aLR.Right()-aRef.X(),aSR.Right()-aRef.X()); + + if (aMax<aMaxFact) + aMaxFact=aMax; + } + + if (aRef.Y()>aSR.Top()) + { + Fraction aMax(aRef.Y()-aLR.Top(),aRef.Y()-aSR.Top()); + + if (aMax<aMaxFact) + aMaxFact=aMax; + } + + if (aRef.Y()<aSR.Bottom()) + { + Fraction aMax(aLR.Bottom()-aRef.Y(),aSR.Bottom()-aRef.Y()); + + if (aMax<aMaxFact) + aMaxFact=aMax; + } + } + + tools::Long nXDiv=aStart.X()-aRef.X(); if (nXDiv==0) nXDiv=1; + tools::Long nYDiv=aStart.Y()-aRef.Y(); if (nYDiv==0) nYDiv=1; + tools::Long nXMul=aPnt.X()-aRef.X(); + tools::Long nYMul=aPnt.Y()-aRef.Y(); + + if (nXDiv<0) + { + nXDiv=-nXDiv; + nXMul=-nXMul; + } + + if (nYDiv<0) + { + nYDiv=-nYDiv; + nYMul=-nYMul; + } + + bool bXNeg=nXMul<0; if (bXNeg) nXMul=-nXMul; + bool bYNeg=nYMul<0; if (bYNeg) nYMul=-nYMul; + bool bOrtho=getSdrDragView().IsOrtho() || !getSdrDragView().IsResizeAllowed(); + + if (!DragStat().IsHorFixed() && !DragStat().IsVerFixed()) + { + if (std::abs(nXDiv)<=1 || std::abs(nYDiv)<=1) + bOrtho=false; + + if (bOrtho) + { + if ((Fraction(nXMul,nXDiv)>Fraction(nYMul,nYDiv)) !=getSdrDragView().IsBigOrtho()) + { + nXMul=nYMul; + nXDiv=nYDiv; + } + else + { + nYMul=nXMul; + nYDiv=nXDiv; + } + } + } + else + { + if (bOrtho) + { + if (DragStat().IsHorFixed()) + { + bXNeg=false; + nXMul=nYMul; + nXDiv=nYDiv; + } + + if (DragStat().IsVerFixed()) + { + bYNeg=false; + nYMul=nXMul; + nYDiv=nXDiv; + } + } + else + { + if (DragStat().IsHorFixed()) + { + bXNeg=false; + nXMul=1; + nXDiv=1; + } + + if (DragStat().IsVerFixed()) + { + bYNeg=false; + nYMul=1; + nYDiv=1; + } + } + } + + Fraction aNewXFact(nXMul,nXDiv); + Fraction aNewYFact(nYMul,nYDiv); + + if (bOrtho) + { + if (aNewXFact>aMaxFact) + { + aNewXFact=aMaxFact; + aNewYFact=aMaxFact; + } + + if (aNewYFact>aMaxFact) + { + aNewXFact=aMaxFact; + aNewYFact=aMaxFact; + } + } + + if (bXNeg) + aNewXFact=Fraction(-aNewXFact.GetNumerator(),aNewXFact.GetDenominator()); + + if (bYNeg) + aNewYFact=Fraction(-aNewYFact.GetNumerator(),aNewYFact.GetDenominator()); + + if (DragStat().CheckMinMoved(aPnt)) + { + if ((!DragStat().IsHorFixed() && aPnt.X()!=DragStat().GetNow().X()) || + (!DragStat().IsVerFixed() && aPnt.Y()!=DragStat().GetNow().Y())) + { + Hide(); + DragStat().NextMove(aPnt); + aXFact=aNewXFact; + aYFact=aNewYFact; + Show(); + } + } +} + +void SdrDragResize::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + rTarget.Resize(DragStat().GetRef1(),aXFact,aYFact); +} + +bool SdrDragResize::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (IsDraggingPoints()) + { + getSdrDragView().ResizeMarkedPoints(DragStat().GetRef1(),aXFact,aYFact); + } + else if (IsDraggingGluePoints()) + { + getSdrDragView().ResizeMarkedGluePoints(DragStat().GetRef1(),aXFact,aYFact,bCopy); + } + else + { + getSdrDragView().ResizeMarkedObj(DragStat().GetRef1(),aXFact,aYFact,bCopy); + } + + return true; +} + +PointerStyle SdrDragResize::GetSdrDragPointer() const +{ + const SdrHdl* pHdl=GetDragHdl(); + + if (pHdl!=nullptr) + { + return pHdl->GetPointer(); + } + + return PointerStyle::Move; +} + + +void SdrDragRotate::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + rTarget.Rotate(DragStat().GetRef1(), nAngle, nSin, nCos); +} + +SdrDragRotate::SdrDragRotate(SdrDragView& rNewView) +: SdrDragMethod(rNewView), + nSin(0.0), + nCos(1.0), + nAngle0(0), + nAngle(0), + bRight(false) +{ +} + +OUString SdrDragRotate::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethRotate) + + " ("; + Degree100 nTmpAngle(NormAngle36000(nAngle)); + + if(bRight && nAngle) + { + nTmpAngle -= 36000_deg100; + } + + aStr += SdrModel::GetAngleString(nTmpAngle) + ")"; + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragRotate::BeginSdrDrag() +{ + SdrHdl* pH=GetHdlList().GetHdl(SdrHdlKind::Ref1); + + if (nullptr != pH) + { + Show(); + DragStat().SetRef1(pH->GetPos()); + nAngle0=GetAngle(DragStat().GetStart()-DragStat().GetRef1()); + return true; + } + + // RotGrfFlyFrame: Support rotation around center *without* Ref1 (normally + // the rotation point) + const tools::Rectangle aLocalMarkRect(getSdrDragView().GetMarkedObjRect()); + + if(!aLocalMarkRect.IsEmpty()) + { + Show(); + DragStat().SetRef1(aLocalMarkRect.Center()); + nAngle0=GetAngle(DragStat().GetStart()-DragStat().GetRef1()); + return true; + } + + OSL_FAIL("SdrDragRotate::BeginSdrDrag(): No reference point handle found."); + return false; +} + +basegfx::B2DHomMatrix SdrDragRotate::getCurrentTransformation() const +{ + return basegfx::utils::createRotateAroundPoint( + DragStat().GetRef1().X(), DragStat().GetRef1().Y(), + -atan2(nSin, nCos)); +} + +void SdrDragRotate::MoveSdrDrag(const Point& rPnt_) +{ + Point aPnt(rPnt_); + if (!DragStat().CheckMinMoved(aPnt)) + return; + + Degree100 nNewAngle=NormAngle36000(GetAngle(aPnt-DragStat().GetRef1())-nAngle0); + Degree100 nSA(0); + + if (getSdrDragView().IsAngleSnapEnabled()) + nSA=getSdrDragView().GetSnapAngle(); + + if (!getSdrDragView().IsRotateAllowed()) + nSA=9000_deg100; + + if (nSA) + { // angle snapping + nNewAngle += nSA / 2_deg100; + nNewAngle /= nSA; + nNewAngle *= nSA; + } + + nNewAngle=NormAngle18000(nNewAngle); + + if (nAngle==nNewAngle) + return; + + sal_uInt16 nSekt0=GetAngleSector(nAngle); + sal_uInt16 nSekt1=GetAngleSector(nNewAngle); + + if (nSekt0==0 && nSekt1==3) + bRight=true; + + if (nSekt0==3 && nSekt1==0) + bRight=false; + + nAngle=nNewAngle; + double a = toRadians(nAngle); + double nSin1=sin(a); // calculate now, so as little time as possible + double nCos1=cos(a); // passes between Hide() and Show() + Hide(); + nSin=nSin1; + nCos=nCos1; + DragStat().NextMove(aPnt); + Show(); +} + +bool SdrDragRotate::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (nAngle!=0_deg100) + { + if (IsDraggingPoints()) + { + getSdrDragView().RotateMarkedPoints(DragStat().GetRef1(),nAngle); + } + else if (IsDraggingGluePoints()) + { + getSdrDragView().RotateMarkedGluePoints(DragStat().GetRef1(),nAngle,bCopy); + } + else + { + getSdrDragView().RotateMarkedObj(DragStat().GetRef1(),nAngle,bCopy); + } + } + return true; +} + +PointerStyle SdrDragRotate::GetSdrDragPointer() const +{ + return PointerStyle::Rotate; +} + + +SdrDragShear::SdrDragShear(SdrDragView& rNewView, bool bSlant1) +: SdrDragMethod(rNewView), + aFact(1,1), + nAngle0(0), + nAngle(0), + nTan(0.0), + bVertical(false), + bResize(false), + bUpSideDown(false), + bSlant(bSlant1) +{ +} + +OUString SdrDragShear::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethShear) + + " ("; + + Degree100 nTmpAngle(nAngle); + + if(bUpSideDown) + nTmpAngle += 18000_deg100; + + nTmpAngle = NormAngle18000(nTmpAngle); + + aStr += SdrModel::GetAngleString(nTmpAngle) + ")"; + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragShear::BeginSdrDrag() +{ + SdrHdlKind eRefHdl=SdrHdlKind::Move; + SdrHdl* pRefHdl=nullptr; + + switch (GetDragHdlKind()) + { + case SdrHdlKind::Upper: eRefHdl=SdrHdlKind::Lower; break; + case SdrHdlKind::Lower: eRefHdl=SdrHdlKind::Upper; break; + case SdrHdlKind::Left : eRefHdl=SdrHdlKind::Right; bVertical=true; break; + case SdrHdlKind::Right: eRefHdl=SdrHdlKind::Left ; bVertical=true; break; + default: break; + } + + if (eRefHdl!=SdrHdlKind::Move) + pRefHdl=GetHdlList().GetHdl(eRefHdl); + + if (pRefHdl!=nullptr) + { + DragStat().SetRef1(pRefHdl->GetPos()); + nAngle0=GetAngle(DragStat().GetStart()-DragStat().GetRef1()); + } + else + { + OSL_FAIL("SdrDragShear::BeginSdrDrag(): No reference point handle for shearing found."); + return false; + } + + Show(); + return true; +} + +basegfx::B2DHomMatrix SdrDragShear::getCurrentTransformation() const +{ + basegfx::B2DHomMatrix aRetval(basegfx::utils::createTranslateB2DHomMatrix( + -DragStat().GetRef1().X(), -DragStat().GetRef1().Y())); + + if (bResize) + { + if (bVertical) + { + aRetval.scale(double(aFact), 1.0); + aRetval.shearY(-nTan); + } + else + { + aRetval.scale(1.0, double(aFact)); + aRetval.shearX(-nTan); + } + } + + aRetval.translate(DragStat().GetRef1().X(), DragStat().GetRef1().Y()); + + return aRetval; +} + +void SdrDragShear::MoveSdrDrag(const Point& rPnt) +{ + if (!DragStat().CheckMinMoved(rPnt)) + return; + + bResize=!getSdrDragView().IsOrtho(); + Degree100 nSA(0); + + if (getSdrDragView().IsAngleSnapEnabled()) + nSA=getSdrDragView().GetSnapAngle(); + + Point aP0(DragStat().GetStart()); + Point aPnt(rPnt); + Fraction aNewFract(1,1); + + // if angle snapping not activated, snap to raster (except when using slant) + if (nSA==0_deg100 && !bSlant) + aPnt=GetSnapPos(aPnt); + + if (!bSlant && !bResize) + { // shear, but no resize + if (bVertical) + aPnt.setX(aP0.X() ); + else + aPnt.setY(aP0.Y() ); + } + + Point aRef(DragStat().GetRef1()); + Point aDif(aPnt-aRef); + + Degree100 nNewAngle(0); + + if (bSlant) + { + nNewAngle=NormAngle18000(-(GetAngle(aDif)-nAngle0)); + + if (bVertical) + nNewAngle=NormAngle18000(-nNewAngle); + } + else + { + if (bVertical) + nNewAngle=NormAngle18000(GetAngle(aDif)); + else + nNewAngle=NormAngle18000(-(GetAngle(aDif)-9000_deg100)); + + if (nNewAngle<Degree100(-9000) || nNewAngle>9000_deg100) + nNewAngle=NormAngle18000(nNewAngle+18000_deg100); + + if (bResize) + { + Point aPt2(aPnt); + + if (nSA!=0_deg100) + aPt2=GetSnapPos(aPnt); // snap this one in any case + + if (bVertical) + { + aNewFract=Fraction(aPt2.X()-aRef.X(),aP0.X()-aRef.X()); + } + else + { + aNewFract=Fraction(aPt2.Y()-aRef.Y(),aP0.Y()-aRef.Y()); + } + } + } + + bool bNeg=nNewAngle<0_deg100; + + if (bNeg) + nNewAngle=-nNewAngle; + + if (nSA) + { // angle snapping + nNewAngle += nSA / 2_deg100; + nNewAngle /= nSA; + nNewAngle *= nSA; + } + + nNewAngle=NormAngle36000(nNewAngle); + bUpSideDown=nNewAngle>9000_deg100 && nNewAngle<27000_deg100; + + if (bSlant) + { // calculate resize for slant + // when angle snapping is activated, disable 89 degree limit + Degree100 nTmpAngle=nNewAngle; + if (bUpSideDown) nNewAngle -= 18000_deg100; + if (bNeg) nTmpAngle=-nTmpAngle; + bResize=true; + aNewFract = cos(toRadians(nTmpAngle)); + aFact.ReduceInaccurate(10); // three decimals should be enough + } + + if (nNewAngle > 8900_deg100) + nNewAngle = 8900_deg100; + + if (bNeg) + nNewAngle=-nNewAngle; + + if (nAngle!=nNewAngle || aFact!=aNewFract) + { + nAngle=nNewAngle; + aFact=aNewFract; + double a = toRadians(nAngle); + double nTan1=tan(a); // calculate now, so as little time as possible passes between Hide() and Show() + Hide(); + nTan=nTan1; + DragStat().NextMove(rPnt); + Show(); + } +} + +void SdrDragShear::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + if (bResize) + { + if (bVertical) + { + rTarget.Resize(DragStat().GetRef1(),aFact,Fraction(1,1)); + } + else + { + rTarget.Resize(DragStat().GetRef1(),Fraction(1,1),aFact); + } + } + + if (nAngle) + { + rTarget.Shear(DragStat().GetRef1(), nAngle, nTan, bVertical); + } +} + +bool SdrDragShear::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (bResize && aFact==Fraction(1,1)) + bResize=false; + + if (nAngle || bResize) + { + if (nAngle && bResize) + { + OUString aStr = ImpGetDescriptionStr(STR_EditShear); + + if (bCopy) + aStr += SvxResId(STR_EditWithCopy); + + getSdrDragView().BegUndo(aStr); + } + + if (bResize) + { + if (bVertical) + { + getSdrDragView().ResizeMarkedObj(DragStat().GetRef1(),aFact,Fraction(1,1),bCopy); + } + else + { + getSdrDragView().ResizeMarkedObj(DragStat().GetRef1(),Fraction(1,1),aFact,bCopy); + } + + bCopy=false; + } + + if (nAngle) + { + getSdrDragView().ShearMarkedObj(DragStat().GetRef1(),nAngle,bVertical,bCopy); + } + + if (nAngle && bResize) + getSdrDragView().EndUndo(); + + return true; + } + + return false; +} + +PointerStyle SdrDragShear::GetSdrDragPointer() const +{ + if (bVertical) + return PointerStyle::VShear; + else + return PointerStyle::HShear; +} + + +void SdrDragMirror::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + if(bMirrored) + { + rTarget.Mirror(DragStat().GetRef1(), DragStat().GetRef2()); + } +} + +SdrDragMirror::SdrDragMirror(SdrDragView& rNewView) +: SdrDragMethod(rNewView), + nAngle(0), + bMirrored(false), + bSide0(false) +{ +} + +bool SdrDragMirror::ImpCheckSide(const Point& rPnt) const +{ + Degree100 nAngle1=GetAngle(rPnt-DragStat().GetRef1()); + nAngle1-=nAngle; + nAngle1=NormAngle36000(nAngle1); + + return nAngle1<18000_deg100; +} + +OUString SdrDragMirror::GetSdrDragComment() const +{ + OUString aStr; + if (aDif.X()==0) + aStr = ImpGetDescriptionStr(STR_DragMethMirrorHori); + else if (aDif.Y()==0) + aStr = ImpGetDescriptionStr(STR_DragMethMirrorVert); + else if (std::abs(aDif.X()) == std::abs(aDif.Y())) + aStr = ImpGetDescriptionStr(STR_DragMethMirrorDiag); + else + aStr = ImpGetDescriptionStr(STR_DragMethMirrorFree); + + if (getSdrDragView().IsDragWithCopy()) + aStr+=SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragMirror::BeginSdrDrag() +{ + SdrHdl* pH1=GetHdlList().GetHdl(SdrHdlKind::Ref1); + SdrHdl* pH2=GetHdlList().GetHdl(SdrHdlKind::Ref2); + + if (pH1!=nullptr && pH2!=nullptr) + { + DragStat().SetRef1(pH1->GetPos()); + DragStat().SetRef2(pH2->GetPos()); + Ref1()=pH1->GetPos(); + Ref2()=pH2->GetPos(); + aDif=pH2->GetPos()-pH1->GetPos(); + bool b90=(aDif.X()==0) || aDif.Y()==0; + bool b45=b90 || (std::abs(aDif.X()) == std::abs(aDif.Y())); + nAngle=NormAngle36000(GetAngle(aDif)); + + if (!getSdrDragView().IsMirrorAllowed() && !b45) + return false; // free choice of axis angle not allowed + + if (!getSdrDragView().IsMirrorAllowed() && !b90) + return false; // 45 degrees not allowed either + + bSide0=ImpCheckSide(DragStat().GetStart()); + Show(); + return true; + } + else + { + OSL_FAIL("SdrDragMirror::BeginSdrDrag(): Axis of reflection not found."); + return false; + } +} + +basegfx::B2DHomMatrix SdrDragMirror::getCurrentTransformation() const +{ + basegfx::B2DHomMatrix aRetval; + + if (bMirrored) + { + const double fDeltaX(DragStat().GetRef2().X() - DragStat().GetRef1().X()); + const double fDeltaY(DragStat().GetRef2().Y() - DragStat().GetRef1().Y()); + const double fRotation(atan2(fDeltaY, fDeltaX)); + + aRetval = basegfx::utils::createTranslateB2DHomMatrix(-DragStat().GetRef1().X(), -DragStat().GetRef1().Y()); + aRetval.rotate(-fRotation); + aRetval.scale(1.0, -1.0); + aRetval.rotate(fRotation); + aRetval.translate(DragStat().GetRef1().X(), DragStat().GetRef1().Y()); + } + + return aRetval; +} + +void SdrDragMirror::MoveSdrDrag(const Point& rPnt) +{ + if (!DragStat().CheckMinMoved(rPnt)) + return; + + bool bNewSide=ImpCheckSide(rPnt); + bool bNewMirrored=bSide0!=bNewSide; + + if (bMirrored!=bNewMirrored) + { + Hide(); + bMirrored=bNewMirrored; + DragStat().NextMove(rPnt); + Show(); + } +} + +bool SdrDragMirror::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (bMirrored) + { + getSdrDragView().MirrorMarkedObj(DragStat().GetRef1(),DragStat().GetRef2(),bCopy); + } + + return true; +} + +PointerStyle SdrDragMirror::GetSdrDragPointer() const +{ + return PointerStyle::Mirror; +} + + +SdrDragGradient::SdrDragGradient(SdrDragView& rNewView, bool bGrad) +: SdrDragMethod(rNewView), + pIAOHandle(nullptr), + bIsGradient(bGrad) +{ +} + +OUString SdrDragGradient::GetSdrDragComment() const +{ + if(IsGradient()) + return ImpGetDescriptionStr(STR_DragMethGradient); + else + return ImpGetDescriptionStr(STR_DragMethTransparence); +} + +bool SdrDragGradient::BeginSdrDrag() +{ + bool bRetval(false); + + pIAOHandle = static_cast<SdrHdlGradient*>(GetHdlList().GetHdl(IsGradient() ? SdrHdlKind::Gradient : SdrHdlKind::Transparence)); + + if(pIAOHandle) + { + // save old values + DragStat().SetRef1( pIAOHandle->GetPos() ); + DragStat().SetRef2( pIAOHandle->Get2ndPos() ); + + // what was hit? + bool bHit(false); + SdrHdlColor* pColHdl = pIAOHandle->GetColorHdl1(); + + // init handling flags + pIAOHandle->SetMoveSingleHandle(false); + pIAOHandle->SetMoveFirstHandle(false); + + // test first color handle + if(pColHdl) + { + basegfx::B2DPoint aPosition(DragStat().GetStart().X(), DragStat().GetStart().Y()); + + if(pColHdl->getOverlayObjectList().isHitLogic(aPosition)) + { + bHit = true; + pIAOHandle->SetMoveSingleHandle(true); + pIAOHandle->SetMoveFirstHandle(true); + } + } + + // test second color handle + pColHdl = pIAOHandle->GetColorHdl2(); + + if(!bHit && pColHdl) + { + basegfx::B2DPoint aPosition(DragStat().GetStart().X(), DragStat().GetStart().Y()); + + if(pColHdl->getOverlayObjectList().isHitLogic(aPosition)) + { + bHit = true; + pIAOHandle->SetMoveSingleHandle(true); + } + } + + // test gradient handle itself + if(!bHit) + { + basegfx::B2DPoint aPosition(DragStat().GetStart().X(), DragStat().GetStart().Y()); + + if(pIAOHandle->getOverlayObjectList().isHitLogic(aPosition)) + { + bHit = true; + } + } + + // everything up and running :o} + bRetval = bHit; + } + else + { + OSL_FAIL("SdrDragGradient::BeginSdrDrag(): IAOGradient not found."); + } + + return bRetval; +} + +void SdrDragGradient::MoveSdrDrag(const Point& rPnt) +{ + if(!(pIAOHandle && DragStat().CheckMinMoved(rPnt))) + return; + + DragStat().NextMove(rPnt); + + // Do the Move here!!! DragStat().GetStart() + Point aMoveDiff = rPnt - DragStat().GetStart(); + + if(pIAOHandle->IsMoveSingleHandle()) + { + if(pIAOHandle->IsMoveFirstHandle()) + { + pIAOHandle->SetPos(DragStat().GetRef1() + aMoveDiff); + if(pIAOHandle->GetColorHdl1()) + pIAOHandle->GetColorHdl1()->SetPos(DragStat().GetRef1() + aMoveDiff); + } + else + { + pIAOHandle->Set2ndPos(DragStat().GetRef2() + aMoveDiff); + if(pIAOHandle->GetColorHdl2()) + pIAOHandle->GetColorHdl2()->SetPos(DragStat().GetRef2() + aMoveDiff); + } + } + else + { + pIAOHandle->SetPos(DragStat().GetRef1() + aMoveDiff); + pIAOHandle->Set2ndPos(DragStat().GetRef2() + aMoveDiff); + + if(pIAOHandle->GetColorHdl1()) + pIAOHandle->GetColorHdl1()->SetPos(DragStat().GetRef1() + aMoveDiff); + + if(pIAOHandle->GetColorHdl2()) + pIAOHandle->GetColorHdl2()->SetPos(DragStat().GetRef2() + aMoveDiff); + } + + // new state + pIAOHandle->FromIAOToItem(getSdrDragView().GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(), false, false); +} + +bool SdrDragGradient::EndSdrDrag(bool /*bCopy*/) +{ + Ref1() = pIAOHandle->GetPos(); + Ref2() = pIAOHandle->Get2ndPos(); + + // new state + pIAOHandle->FromIAOToItem(getSdrDragView().GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(), true, true); + + return true; +} + +void SdrDragGradient::CancelSdrDrag() +{ + // restore old values + pIAOHandle->SetPos(DragStat().GetRef1()); + pIAOHandle->Set2ndPos(DragStat().GetRef2()); + + if(pIAOHandle->GetColorHdl1()) + pIAOHandle->GetColorHdl1()->SetPos(DragStat().GetRef1()); + + if(pIAOHandle->GetColorHdl2()) + pIAOHandle->GetColorHdl2()->SetPos(DragStat().GetRef2()); + + // new state + pIAOHandle->FromIAOToItem(getSdrDragView().GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(), true, false); +} + +PointerStyle SdrDragGradient::GetSdrDragPointer() const +{ + return PointerStyle::RefHand; +} + + +SdrDragCrook::SdrDragCrook(SdrDragView& rNewView) +: SdrDragMethod(rNewView), + aFact(1,1), + bContortionAllowed(false), + bNoContortionAllowed(false), + bContortion(false), + bResizeAllowed(false), + bResize(false), + bRotateAllowed(false), + bRotate(false), + bVertical(false), + bValid(false), + bLft(false), + bRgt(false), + bUpr(false), + bLwr(false), + bAtCenter(false), + nAngle(0), + nMarkSize(0), + eMode(SdrCrookMode::Rotate) +{ +} + +OUString SdrDragCrook::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(!bContortion ? STR_DragMethCrook : STR_DragMethCrookContortion); + + if(bValid) + { + aStr += " ("; + + sal_Int32 nVal(nAngle); + + if(bAtCenter) + nVal *= 2; + + nVal = std::abs(nVal); + aStr += SdrModel::GetAngleString(Degree100(nVal)) + ")"; + } + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +// These defines parametrize the created raster +// for interactions +#define DRAG_CROOK_RASTER_MINIMUM (4) +#define DRAG_CROOK_RASTER_MAXIMUM (15) +#define DRAG_CROOK_RASTER_DISTANCE (30) + +static basegfx::B2DPolyPolygon impCreateDragRaster(SdrPageView const & rPageView, const tools::Rectangle& rMarkRect) +{ + basegfx::B2DPolyPolygon aRetval; + + if(rPageView.PageWindowCount()) + { + OutputDevice& rOut = rPageView.GetPageWindow(0)->GetPaintWindow().GetOutputDevice(); + tools::Rectangle aPixelSize = rOut.LogicToPixel(rMarkRect); + sal_uInt32 nHorDiv(aPixelSize.GetWidth() / DRAG_CROOK_RASTER_DISTANCE); + sal_uInt32 nVerDiv(aPixelSize.GetHeight() / DRAG_CROOK_RASTER_DISTANCE); + + if(nHorDiv > DRAG_CROOK_RASTER_MAXIMUM) + nHorDiv = DRAG_CROOK_RASTER_MAXIMUM; + if(nHorDiv < DRAG_CROOK_RASTER_MINIMUM) + nHorDiv = DRAG_CROOK_RASTER_MINIMUM; + + if(nVerDiv > DRAG_CROOK_RASTER_MAXIMUM) + nVerDiv = DRAG_CROOK_RASTER_MAXIMUM; + if(nVerDiv < DRAG_CROOK_RASTER_MINIMUM) + nVerDiv = DRAG_CROOK_RASTER_MINIMUM; + + const double fXLen(rMarkRect.GetWidth() / static_cast<double>(nHorDiv)); + const double fYLen(rMarkRect.GetHeight() / static_cast<double>(nVerDiv)); + double fYPos(rMarkRect.Top()); + sal_uInt32 a, b; + + for(a = 0; a <= nVerDiv; a++) + { + // horizontal lines + for(b = 0; b < nHorDiv; b++) + { + basegfx::B2DPolygon aHorLineSegment; + + const double fNewX(rMarkRect.Left() + (b * fXLen)); + aHorLineSegment.append(basegfx::B2DPoint(fNewX, fYPos)); + aHorLineSegment.appendBezierSegment( + basegfx::B2DPoint(fNewX + (fXLen * (1.0 / 3.0)), fYPos), + basegfx::B2DPoint(fNewX + (fXLen * (2.0 / 3.0)), fYPos), + basegfx::B2DPoint(fNewX + fXLen, fYPos)); + aRetval.append(aHorLineSegment); + } + + // increments + fYPos += fYLen; + } + + double fXPos(rMarkRect.Left()); + + for(a = 0; a <= nHorDiv; a++) + { + // vertical lines + for(b = 0; b < nVerDiv; b++) + { + basegfx::B2DPolygon aVerLineSegment; + + const double fNewY(rMarkRect.Top() + (b * fYLen)); + aVerLineSegment.append(basegfx::B2DPoint(fXPos, fNewY)); + aVerLineSegment.appendBezierSegment( + basegfx::B2DPoint(fXPos, fNewY + (fYLen * (1.0 / 3.0))), + basegfx::B2DPoint(fXPos, fNewY + (fYLen * (2.0 / 3.0))), + basegfx::B2DPoint(fXPos, fNewY + fYLen)); + aRetval.append(aVerLineSegment); + } + + // increments + fXPos += fXLen; + } + } + + return aRetval; +} + +void SdrDragCrook::createSdrDragEntries() +{ + // Add extended frame raster first, so it will be behind objects + if(getSdrDragView().GetSdrPageView()) + { + const basegfx::B2DPolyPolygon aDragRaster(impCreateDragRaster(*getSdrDragView().GetSdrPageView(), GetMarkedRect())); + + if(aDragRaster.count()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(aDragRaster))); + } + } + + // call parent + SdrDragMethod::createSdrDragEntries(); +} + +bool SdrDragCrook::BeginSdrDrag() +{ + bContortionAllowed=getSdrDragView().IsCrookAllowed(); + bNoContortionAllowed=getSdrDragView().IsCrookAllowed(true); + bResizeAllowed=getSdrDragView().IsResizeAllowed(); + bRotateAllowed=getSdrDragView().IsRotateAllowed(); + + if (bContortionAllowed || bNoContortionAllowed) + { + bVertical=(GetDragHdlKind()==SdrHdlKind::Lower || GetDragHdlKind()==SdrHdlKind::Upper); + aMarkRect=GetMarkedRect(); + aMarkCenter=aMarkRect.Center(); + nMarkSize=bVertical ? (aMarkRect.GetHeight()-1) : (aMarkRect.GetWidth()-1); + aCenter=aMarkCenter; + aStart=DragStat().GetStart(); + Show(); + return true; + } + else + { + return false; + } +} + +void SdrDragCrook::MovAllPoints(basegfx::B2DPolyPolygon& rTarget) +{ + SdrPageView* pPV = getSdrDragView().GetSdrPageView(); + + if(!pPV) + return; + + XPolyPolygon aTempPolyPoly(rTarget); + + if (pPV->HasMarkedObjPageView()) + { + sal_uInt16 nPolyCount=aTempPolyPoly.Count(); + + if (!bContortion && !getSdrDragView().IsNoDragXorPolys()) + { + sal_uInt16 n1st=0,nLast=0; + Point aC(aCenter); + + while (n1st<nPolyCount) + { + nLast=n1st; + while (nLast<nPolyCount && aTempPolyPoly[nLast].GetPointCount()!=0) nLast++; + tools::Rectangle aBound(aTempPolyPoly[n1st].GetBoundRect()); + sal_uInt16 i; + + for (i=n1st+1; i<nLast; i++) + { + aBound.Union(aTempPolyPoly[n1st].GetBoundRect()); + } + + Point aCtr0(aBound.Center()); + Point aCtr1(aCtr0); + + if (bResize) + { + Fraction aFact1(1,1); + + if (bVertical) + { + ResizePoint(aCtr1,aC,aFact1,aFact); + } + else + { + ResizePoint(aCtr1,aC,aFact,aFact1); + } + } + + bool bRotOk=false; + double nSin=0,nCos=0; + + if (aRad.X()!=0 && aRad.Y()!=0) + { + bRotOk=bRotate; + + switch (eMode) + { + case SdrCrookMode::Rotate : CrookRotateXPoint (aCtr1,nullptr,nullptr,aC,aRad,nSin,nCos,bVertical); break; + case SdrCrookMode::Slant : CrookSlantXPoint (aCtr1,nullptr,nullptr,aC,aRad,nSin,nCos,bVertical); break; + case SdrCrookMode::Stretch: CrookStretchXPoint(aCtr1,nullptr,nullptr,aC,aRad,nSin,nCos,bVertical,aMarkRect); break; + } // switch + } + + aCtr1-=aCtr0; + + for (i=n1st; i<nLast; i++) + { + if (bRotOk) + { + RotateXPoly(aTempPolyPoly[i],aCtr0,nSin,nCos); + } + + aTempPolyPoly[i].Move(aCtr1.X(),aCtr1.Y()); + } + + n1st=nLast+1; + } + } + else + { + sal_uInt16 i,j; + + for (j=0; j<nPolyCount; j++) + { + XPolygon& aPol=aTempPolyPoly[j]; + sal_uInt16 nPointCount=aPol.GetPointCount(); + i=0; + + while (i<nPointCount) + { + Point* pPnt=&aPol[i]; + Point* pC1=nullptr; + Point* pC2=nullptr; + + if (i+1<nPointCount && aPol.IsControl(i)) + { // control point on the left + pC1=pPnt; + i++; + pPnt=&aPol[i]; + } + + i++; + + if (i<nPointCount && aPol.IsControl(i)) + { // control point on the right + pC2=&aPol[i]; + i++; + } + + MovCrookPoint(*pPnt,pC1,pC2); + } + } + } + } + + rTarget = aTempPolyPoly.getB2DPolyPolygon(); +} + +void SdrDragCrook::MovCrookPoint(Point& rPnt, Point* pC1, Point* pC2) +{ + bool bVert=bVertical; + bool bC1=pC1!=nullptr; + bool bC2=pC2!=nullptr; + Point aC(aCenter); + + if (bResize) + { + Fraction aFact1(1,1); + + if (bVert) + { + ResizePoint(rPnt,aC,aFact1,aFact); + + if (bC1) + ResizePoint(*pC1,aC,aFact1,aFact); + + if (bC2) + ResizePoint(*pC2,aC,aFact1,aFact); + } + else + { + ResizePoint(rPnt,aC,aFact,aFact1); + + if (bC1) + ResizePoint(*pC1,aC,aFact,aFact1); + + if (bC2) + ResizePoint(*pC2,aC,aFact,aFact1); + } + } + + if (aRad.X()!=0 && aRad.Y()!=0) + { + double nSin,nCos; + + switch (eMode) + { + case SdrCrookMode::Rotate : CrookRotateXPoint (rPnt,pC1,pC2,aC,aRad,nSin,nCos,bVert); break; + case SdrCrookMode::Slant : CrookSlantXPoint (rPnt,pC1,pC2,aC,aRad,nSin,nCos,bVert); break; + case SdrCrookMode::Stretch: CrookStretchXPoint(rPnt,pC1,pC2,aC,aRad,nSin,nCos,bVert,aMarkRect); break; + } // switch + } +} + +void SdrDragCrook::MoveSdrDrag(const Point& rPnt) +{ + if (!DragStat().CheckMinMoved(rPnt)) + return; + + bool bNewMoveOnly=getSdrDragView().IsMoveOnlyDragging(); + bAtCenter=false; + SdrCrookMode eNewMode=getSdrDragView().GetCrookMode(); + bool bNewContortion=!bNewMoveOnly && ((bContortionAllowed && !getSdrDragView().IsCrookNoContortion()) || !bNoContortionAllowed); + bResize=!getSdrDragView().IsOrtho() && bResizeAllowed && !bNewMoveOnly; + bool bNewRotate=bRotateAllowed && !bNewContortion && !bNewMoveOnly && eNewMode==SdrCrookMode::Rotate; + + Point aPnt(GetSnapPos(rPnt)); + + Point aNewCenter(aMarkCenter.X(),aStart.Y()); + + if (bVertical) + { + aNewCenter.setX(aStart.X() ); + aNewCenter.setY(aMarkCenter.Y() ); + } + + if (!getSdrDragView().IsCrookAtCenter()) + { + switch (GetDragHdlKind()) + { + case SdrHdlKind::UpperLeft: aNewCenter.setX(aMarkRect.Right() ); bLft=true; break; + case SdrHdlKind::Upper: aNewCenter.setY(aMarkRect.Bottom() ); bUpr=true; break; + case SdrHdlKind::UpperRight: aNewCenter.setX(aMarkRect.Left() ); bRgt=true; break; + case SdrHdlKind::Left : aNewCenter.setX(aMarkRect.Right() ); bLft=true; break; + case SdrHdlKind::Right: aNewCenter.setX(aMarkRect.Left() ); bRgt=true; break; + case SdrHdlKind::LowerLeft: aNewCenter.setX(aMarkRect.Right() ); bLft=true; break; + case SdrHdlKind::Lower: aNewCenter.setY(aMarkRect.Top() ); bLwr=true; break; + case SdrHdlKind::LowerRight: aNewCenter.setX(aMarkRect.Left() ); bRgt=true; break; + default: bAtCenter=true; + } + } + else + bAtCenter=true; + + Fraction aNewFract(1,1); + tools::Long dx1=aPnt.X()-aNewCenter.X(); + tools::Long dy1=aPnt.Y()-aNewCenter.Y(); + bValid=bVertical ? dx1!=0 : dy1!=0; + + if (bValid) + { + if (bVertical) + bValid = std::abs(dx1)*100>std::abs(dy1); + else + bValid = std::abs(dy1)*100>std::abs(dx1); + } + + tools::Long nNewRad=0; + nAngle=0_deg100; + + if (bValid) + { + double a=0; // slope of the radius + Degree100 nPntAngle(0); + + if (bVertical) + { + a=static_cast<double>(dy1)/static_cast<double>(dx1); // slope of the radius + nNewRad=(static_cast<tools::Long>(dy1*a)+dx1) /2; + aNewCenter.AdjustX(nNewRad ); + nPntAngle=GetAngle(aPnt-aNewCenter); + } + else + { + a=static_cast<double>(dx1)/static_cast<double>(dy1); // slope of the radius + nNewRad=(static_cast<tools::Long>(dx1*a)+dy1) /2; + aNewCenter.AdjustY(nNewRad ); + nPntAngle=GetAngle(aPnt-aNewCenter)-9000_deg100; + } + + if (!bAtCenter) + { + if (nNewRad<0) + { + if (bRgt) nPntAngle += 18000_deg100; + if (bLft) nPntAngle = 18000_deg100 - nPntAngle; + if (bLwr) nPntAngle =- nPntAngle; + } + else + { + if (bRgt) nPntAngle = -nPntAngle; + if (bUpr) nPntAngle = 18000_deg100 - nPntAngle; + if (bLwr) nPntAngle += 18000_deg100; + } + + nPntAngle=NormAngle36000(nPntAngle); + } + else + { + if (nNewRad<0) nPntAngle += 18000_deg100; + if (bVertical) nPntAngle = 18000_deg100 - nPntAngle; + nPntAngle = NormAngle18000(nPntAngle); + nPntAngle = abs(nPntAngle); + } + + double nCircumference = 2 * std::abs(nNewRad) * M_PI; + + if (bResize) + { + tools::Long nMul=static_cast<tools::Long>(nCircumference * NormAngle36000(nPntAngle).get() / 36000.0); + + if (bAtCenter) + nMul*=2; + + aNewFract=Fraction(nMul,nMarkSize); + nAngle=nPntAngle; + } + else + { + nAngle = Degree100(static_cast<tools::Long>((nMarkSize*360/nCircumference)*100)/2); + + if (nAngle==0_deg100) + bValid=false; + } + } + + if (nAngle==0_deg100 || nNewRad==0) + bValid=false; + + if (!bValid) + nNewRad=0; + + if (!bValid && bResize) + { + tools::Long nMul=bVertical ? dy1 : dx1; + + if (bLft || bUpr) + nMul=-nMul; + + tools::Long nDiv=nMarkSize; + + if (bAtCenter) + { + nMul*=2; + nMul = std::abs(nMul); + } + + aNewFract=Fraction(nMul,nDiv); + } + + if (aNewCenter==aCenter && bNewContortion==bContortion && aNewFract==aFact && + bNewMoveOnly == getMoveOnly() && bNewRotate==bRotate && eNewMode==eMode) + return; + + Hide(); + setMoveOnly(bNewMoveOnly); + bRotate=bNewRotate; + eMode=eNewMode; + bContortion=bNewContortion; + aCenter=aNewCenter; + aFact=aNewFract; + aRad=Point(nNewRad,nNewRad); + bResize=aFact!=Fraction(1,1) && aFact.GetDenominator()!=0 && aFact.IsValid(); + DragStat().NextMove(aPnt); + Show(); +} + +void SdrDragCrook::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + const bool bDoResize(aFact!=Fraction(1,1)); + const bool bDoCrook(aCenter!=aMarkCenter && aRad.X()!=0 && aRad.Y()!=0); + + if (!(bDoCrook || bDoResize)) + return; + + if (bDoResize) + { + Fraction aFact1(1,1); + + if (bContortion) + { + if (bVertical) + { + rTarget.Resize(aCenter,aFact1,aFact); + } + else + { + rTarget.Resize(aCenter,aFact,aFact1); + } + } + else + { + Point aCtr0(rTarget.GetSnapRect().Center()); + Point aCtr1(aCtr0); + + if (bVertical) + { + ResizePoint(aCtr1,aCenter,aFact1,aFact); + } + else + { + ResizePoint(aCtr1,aCenter,aFact,aFact1); + } + + Size aSiz(aCtr1.X()-aCtr0.X(),aCtr1.Y()-aCtr0.Y()); + + rTarget.Move(aSiz); + } + } + + if (bDoCrook) + { + const tools::Rectangle aLocalMarkRect(getSdrDragView().GetMarkedObjRect()); + const bool bLocalRotate(!bContortion && eMode == SdrCrookMode::Rotate && getSdrDragView().IsRotateAllowed()); + + SdrEditView::ImpCrookObj(&rTarget,aCenter,aRad,eMode,bVertical,!bContortion,bLocalRotate,aLocalMarkRect); + } +} + +void SdrDragCrook::applyCurrentTransformationToPolyPolygon(basegfx::B2DPolyPolygon& rTarget) +{ + // use helper derived from old stuff + MovAllPoints(rTarget); +} + +bool SdrDragCrook::EndSdrDrag(bool bCopy) +{ + Hide(); + + if (bResize && aFact==Fraction(1,1)) + bResize=false; + + const bool bUndo = getSdrDragView().IsUndoEnabled(); + + bool bDoCrook=aCenter!=aMarkCenter && aRad.X()!=0 && aRad.Y()!=0; + + if (bDoCrook || bResize) + { + if (bResize && bUndo) + { + OUString aStr = ImpGetDescriptionStr(!bContortion?STR_EditCrook:STR_EditCrookContortion); + + if (bCopy) + aStr += SvxResId(STR_EditWithCopy); + + getSdrDragView().BegUndo(aStr); + } + + if (bResize) + { + Fraction aFact1(1,1); + + if (bContortion) + { + if (bVertical) + getSdrDragView().ResizeMarkedObj(aCenter,aFact1,aFact,bCopy); + else + getSdrDragView().ResizeMarkedObj(aCenter,aFact,aFact1,bCopy); + } + else + { + if (bCopy) + getSdrDragView().CopyMarkedObj(); + + const size_t nMarkCount=getSdrDragView().GetMarkedObjectList().GetMarkCount(); + + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=getSdrDragView().GetMarkedObjectList().GetMark(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + Point aCtr0(pO->GetSnapRect().Center()); + Point aCtr1(aCtr0); + + if (bVertical) + ResizePoint(aCtr1,aCenter,aFact1,aFact); + else + ResizePoint(aCtr1,aCenter,aFact,aFact1); + + Size aSiz(aCtr1.X()-aCtr0.X(),aCtr1.Y()-aCtr0.Y()); + if( bUndo ) + AddUndo(getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoMoveObject(*pO,aSiz)); + pO->Move(aSiz); + } + } + + bCopy=false; + } + + if (bDoCrook) + { + getSdrDragView().CrookMarkedObj(aCenter,aRad,eMode,bVertical,!bContortion,bCopy); + } + + if (bResize && bUndo) + getSdrDragView().EndUndo(); + + return true; + } + + return false; +} + +PointerStyle SdrDragCrook::GetSdrDragPointer() const +{ + return PointerStyle::Crook; +} + + +SdrDragDistort::SdrDragDistort(SdrDragView& rNewView) +: SdrDragMethod(rNewView), + nPolyPt(0), + bContortionAllowed(false), + bNoContortionAllowed(false), + bContortion(false) +{ +} + +OUString SdrDragDistort::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethDistort) + + " (x=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDX()) + + " y=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDY()) + + ")"; + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +void SdrDragDistort::createSdrDragEntries() +{ + // Add extended frame raster first, so it will be behind objects + if(getSdrDragView().GetSdrPageView()) + { + const basegfx::B2DPolyPolygon aDragRaster(impCreateDragRaster(*getSdrDragView().GetSdrPageView(), GetMarkedRect())); + + if(aDragRaster.count()) + { + addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(aDragRaster))); + } + } + + // call parent + SdrDragMethod::createSdrDragEntries(); +} + +bool SdrDragDistort::BeginSdrDrag() +{ + bContortionAllowed=getSdrDragView().IsDistortAllowed(); + bNoContortionAllowed=getSdrDragView().IsDistortAllowed(true); + + if (bContortionAllowed || bNoContortionAllowed) + { + SdrHdlKind eKind=GetDragHdlKind(); + nPolyPt=0xFFFF; + + if (eKind==SdrHdlKind::UpperLeft) nPolyPt=0; + if (eKind==SdrHdlKind::UpperRight) nPolyPt=1; + if (eKind==SdrHdlKind::LowerRight) nPolyPt=2; + if (eKind==SdrHdlKind::LowerLeft) nPolyPt=3; + if (nPolyPt>3) return false; + + aMarkRect=GetMarkedRect(); + aDistortedRect=XPolygon(aMarkRect); + Show(); + return true; + } + else + { + return false; + } +} + +void SdrDragDistort::MovAllPoints(basegfx::B2DPolyPolygon& rTarget) +{ + if (!bContortion) + return; + + SdrPageView* pPV = getSdrDragView().GetSdrPageView(); + + if(pPV && pPV->HasMarkedObjPageView()) + { + basegfx::B2DPolyPolygon aDragPolygon(rTarget); + const basegfx::B2DRange aOriginalRange = vcl::unotools::b2DRectangleFromRectangle(aMarkRect); + const basegfx::B2DPoint aTopLeft(aDistortedRect[0].X(), aDistortedRect[0].Y()); + const basegfx::B2DPoint aTopRight(aDistortedRect[1].X(), aDistortedRect[1].Y()); + const basegfx::B2DPoint aBottomLeft(aDistortedRect[3].X(), aDistortedRect[3].Y()); + const basegfx::B2DPoint aBottomRight(aDistortedRect[2].X(), aDistortedRect[2].Y()); + + aDragPolygon = basegfx::utils::distort(aDragPolygon, aOriginalRange, aTopLeft, aTopRight, aBottomLeft, aBottomRight); + rTarget = aDragPolygon; + } +} + +void SdrDragDistort::MoveSdrDrag(const Point& rPnt) +{ + if (!DragStat().CheckMinMoved(rPnt)) + return; + + Point aPnt(GetSnapPos(rPnt)); + + if (getSdrDragView().IsOrtho()) + OrthoDistance8(DragStat().GetStart(),aPnt,getSdrDragView().IsBigOrtho()); + + bool bNewContortion=(bContortionAllowed && !getSdrDragView().IsCrookNoContortion()) || !bNoContortionAllowed; + + if (bNewContortion!=bContortion || aDistortedRect[nPolyPt]!=aPnt) + { + Hide(); + aDistortedRect[nPolyPt]=aPnt; + bContortion=bNewContortion; + DragStat().NextMove(aPnt); + Show(); + } +} + +bool SdrDragDistort::EndSdrDrag(bool bCopy) +{ + Hide(); + bool bDoDistort=DragStat().GetDX()!=0 || DragStat().GetDY()!=0; + + if (bDoDistort) + { + getSdrDragView().DistortMarkedObj(aMarkRect,aDistortedRect,!bContortion,bCopy); + return true; + } + + return false; +} + +PointerStyle SdrDragDistort::GetSdrDragPointer() const +{ + return PointerStyle::RefHand; +} + +void SdrDragDistort::applyCurrentTransformationToSdrObject(SdrObject& rTarget) +{ + const bool bDoDistort(DragStat().GetDX()!=0 || DragStat().GetDY()!=0); + + if (bDoDistort) + { + SdrEditView::ImpDistortObj(&rTarget, aMarkRect, aDistortedRect, !bContortion); + } +} + +void SdrDragDistort::applyCurrentTransformationToPolyPolygon(basegfx::B2DPolyPolygon& rTarget) +{ + // use helper derived from old stuff + MovAllPoints(rTarget); +} + + +SdrDragCrop::SdrDragCrop(SdrDragView& rNewView) +: SdrDragObjOwn(rNewView) +{ + // switch off solid dragging for crop; it just makes no sense since showing + // a 50% transparent object above the original will not be visible + setSolidDraggingActive(false); +} + +OUString SdrDragCrop::GetSdrDragComment() const +{ + OUString aStr = ImpGetDescriptionStr(STR_DragMethCrop) + + " (x=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDX()) + + " y=" + + getSdrDragView().GetModel().GetMetricString(DragStat().GetDY()) + + ")"; + + if(getSdrDragView().IsDragWithCopy()) + aStr += SvxResId(STR_EditWithCopy); + return aStr; +} + +bool SdrDragCrop::BeginSdrDrag() +{ + // call parent + bool bRetval(SdrDragObjOwn::BeginSdrDrag()); + + if(!GetDragHdl()) + { + // we need the DragHdl, break if not there + bRetval = false; + } + + return bRetval; +} + +bool SdrDragCrop::EndSdrDrag(bool /*bCopy*/) +{ + Hide(); + + if(0 == DragStat().GetDX() && 0 == DragStat().GetDY()) + { + // no change, done + return false; + } + + const SdrMarkList& rMarkList = getSdrDragView().GetMarkedObjectList(); + + if(1 != rMarkList.GetMarkCount()) + { + // Crop only with single Object selected + return false; + } + + // prepare for SdrGrafObj or others. This code has to work with usual + // SdrGrafObj's from Draw/Impress/Calc, but also with SdrObjects from + // Writer. It would be better to handle this in Writer directly, but + // there are currently no easy mechanisms to plug an alternative interaction + // from there + SdrObject* pSdrObject = rMarkList.GetMark(0)->GetMarkedSdrObj(); + rtl::Reference<SdrObject> pFullDragClone; + bool bExternal(false); + SdrObject* pExternalSdrObject(nullptr); + + // RotGrfFlyFrame: Crop decision for DrawingLayer/Writer now + // locally, no two-in-one methods any more + if (nullptr != pSdrObject && dynamic_cast< const SdrGrafObj* >(pSdrObject) == nullptr) + { + // If Writer, get the already offered for interaction SdrGrafObj + // and set up for using that replacement object that contains the + // real transformation. That SdrObject is owned and has to be deleted, + // so use a std::unique_ptr with special handling for the protected + // SDrObject destructor + pFullDragClone = pSdrObject->getFullDragClone(); + + if(dynamic_cast< SdrGrafObj* >(pFullDragClone.get())) + { + bExternal = true; + pExternalSdrObject = pSdrObject; + pSdrObject = pFullDragClone.get(); + } + } + + // get and check for SdrGrafObj now + SdrGrafObj* pObj = dynamic_cast<SdrGrafObj*>( pSdrObject ); + + if(!pObj) + { + return false; + } + + // no undo for external needed, done there + const bool bUndo(!bExternal && getSdrDragView().IsUndoEnabled()); + + if(bUndo) + { + OUString aUndoStr = ImpGetDescriptionStr(STR_DragMethCrop); + + getSdrDragView().BegUndo( aUndoStr ); + getSdrDragView().AddUndo(getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + // also need attr undo, the SdrGrafCropItem will be changed + getSdrDragView().AddUndo(getSdrDragView().GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pObj)); + } + + // get the original objects transformation + basegfx::B2DHomMatrix aOriginalMatrix; + basegfx::B2DPolyPolygon aPolyPolygon; + bool bShearCorrected(false); + pObj->TRGetBaseGeometry(aOriginalMatrix, aPolyPolygon); + + { // correct shear, it comes currently mirrored from TRGetBaseGeometry, can be removed with aw080 + const basegfx::utils::B2DHomMatrixBufferedDecompose aTmpDecomp(aOriginalMatrix); + + if(!basegfx::fTools::equalZero(aTmpDecomp.getShearX())) + { + bShearCorrected = true; + aOriginalMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aTmpDecomp.getScale(), + -aTmpDecomp.getShearX(), + aTmpDecomp.getRotate(), + aTmpDecomp.getTranslate()); + } + } + + // generate start point of original drag vector in unit coordinates (the + // vis-a-vis of the drag point) + basegfx::B2DPoint aLocalStart(0.0, 0.0); + bool bOnAxis(false); + + switch(GetDragHdlKind()) + { + case SdrHdlKind::UpperLeft: aLocalStart.setX(1.0); aLocalStart.setY(1.0); break; + case SdrHdlKind::Upper: aLocalStart.setX(0.5); aLocalStart.setY(1.0); bOnAxis = true; break; + case SdrHdlKind::UpperRight: aLocalStart.setX(0.0); aLocalStart.setY(1.0); break; + case SdrHdlKind::Left : aLocalStart.setX(1.0); aLocalStart.setY(0.5); bOnAxis = true; break; + case SdrHdlKind::Right: aLocalStart.setX(0.0); aLocalStart.setY(0.5); bOnAxis = true; break; + case SdrHdlKind::LowerLeft: aLocalStart.setX(1.0); aLocalStart.setY(0.0); break; + case SdrHdlKind::Lower: aLocalStart.setX(0.5); aLocalStart.setY(0.0); bOnAxis = true; break; + case SdrHdlKind::LowerRight: aLocalStart.setX(0.0); aLocalStart.setY(0.0); break; + default: break; + } + + // create the current drag position in unit coordinates. To get there, + // transform back the DragPoint to UnitCoordinates + basegfx::B2DHomMatrix aInverse(aOriginalMatrix); + aInverse.invert(); + basegfx::B2DPoint aLocalCurrent(aInverse * basegfx::B2DPoint(DragStat().GetNow().X(), DragStat().GetNow().Y())); + + // if one of the edge handles is used, limit to X or Y drag only + if(bOnAxis) + { + if(basegfx::fTools::equal(aLocalStart.getX(), 0.5)) + { + aLocalCurrent.setX(aLocalStart.getX()); + } + else + { + aLocalCurrent.setY(aLocalStart.getY()); + } + } + + // create internal change in unit coordinates + basegfx::B2DHomMatrix aDiscreteChangeMatrix; + + if(!basegfx::fTools::equal(aLocalCurrent.getX(), aLocalStart.getX())) + { + if(aLocalStart.getX() < 0.5) + { + aDiscreteChangeMatrix.scale(aLocalCurrent.getX(), 1.0); + } + else + { + aDiscreteChangeMatrix.scale(1.0 - aLocalCurrent.getX(), 1.0); + aDiscreteChangeMatrix.translate(aLocalCurrent.getX(), 0.0); + } + } + + if(!basegfx::fTools::equal(aLocalCurrent.getY(), aLocalStart.getY())) + { + if(aLocalStart.getY() < 0.5) + { + aDiscreteChangeMatrix.scale(1.0, aLocalCurrent.getY()); + } + else + { + aDiscreteChangeMatrix.scale(1.0, 1.0 - aLocalCurrent.getY()); + aDiscreteChangeMatrix.translate(0.0, aLocalCurrent.getY()); + } + } + + // We now have the whole executed Crop in UnitCoordinates in + // aDiscreteChangeMatrix, go to concrete sizes now. + // Create the unrotated original rectangle and the unrotated modified + // rectangle as Ranges + const basegfx::utils::B2DHomMatrixBufferedDecompose aOriginalMatrixDecomp(aOriginalMatrix); + + // prepare unsheared/unrotated versions of the old and new transformation + const basegfx::B2DHomMatrix aOriginalMatrixNoShearNoRotate( + basegfx::utils::createScaleTranslateB2DHomMatrix( + basegfx::absolute(aOriginalMatrixDecomp.getScale()), + aOriginalMatrixDecomp.getTranslate())); + + // create the ranges for these + basegfx::B2DRange aRangeOriginalNoShearNoRotate(0.0, 0.0, 1.0, 1.0); + basegfx::B2DRange aRangeNewNoShearNoRotate(0.0, 0.0, 1.0, 1.0); + aRangeOriginalNoShearNoRotate.transform(aOriginalMatrixNoShearNoRotate); + aRangeNewNoShearNoRotate.transform(aOriginalMatrixNoShearNoRotate * aDiscreteChangeMatrix); + + if(bExternal) + { + // With aLocalStart point (opposed to dragged point), X scale and Y scale, + // we call crop (virtual method) on pSdrObject which calls VirtFlyDrawObj + // crop. Use aLocalStart unchanged, so being relative to the Crop-Action, + // the called instance knows best how to use it + const double fScaleX(aRangeNewNoShearNoRotate.getWidth() / aRangeOriginalNoShearNoRotate.getWidth()); + const double fScaleY(aRangeNewNoShearNoRotate.getHeight() / aRangeOriginalNoShearNoRotate.getHeight()); + + pExternalSdrObject->Crop( + aLocalStart, + fScaleX, + fScaleY); + } + else + { + // prepare matrix to apply to object; evtl. back-correct shear + basegfx::B2DHomMatrix aNewObjectMatrix(aOriginalMatrix * aDiscreteChangeMatrix); + + if(bShearCorrected) + { + // back-correct shear + const basegfx::utils::B2DHomMatrixBufferedDecompose aTmpDecomp(aNewObjectMatrix); + + aNewObjectMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aTmpDecomp.getScale(), + -aTmpDecomp.getShearX(), + aTmpDecomp.getRotate(), + aTmpDecomp.getTranslate()); + } + + // apply change to object by applying the unit coordinate change followed + // by the original change + pObj->TRSetBaseGeometry(aNewObjectMatrix, aPolyPolygon); + + // extract the old Rectangle structures + tools::Rectangle aOldRect( + basegfx::fround(aRangeOriginalNoShearNoRotate.getMinX()), + basegfx::fround(aRangeOriginalNoShearNoRotate.getMinY()), + basegfx::fround(aRangeOriginalNoShearNoRotate.getMaxX()), + basegfx::fround(aRangeOriginalNoShearNoRotate.getMaxY())); + tools::Rectangle aNewRect( + basegfx::fround(aRangeNewNoShearNoRotate.getMinX()), + basegfx::fround(aRangeNewNoShearNoRotate.getMinY()), + basegfx::fround(aRangeNewNoShearNoRotate.getMaxX()), + basegfx::fround(aRangeNewNoShearNoRotate.getMaxY())); + + // continue with the old original stuff + if (!aOldRect.GetWidth() || !aOldRect.GetHeight()) + { + throw o3tl::divide_by_zero(); + } + + if((pObj->GetGraphicType() == GraphicType::NONE) || (pObj->GetGraphicType() == GraphicType::Default)) + { + return false; + } + + const GraphicObject& rGraphicObject(pObj->GetGraphicObject()); + // tdf#117145 Usually Writer will go the bExternal path (see above), but more correct for + // the future is to use the MapMode from the SdrModel/SfxItemPool if the Writer's current + // special handling should be unified to this path in the future. Usually it *should* be + // MapUnit::Map100thMM, but better do not mix up Units. + // Checked now what SwVirtFlyDrawObj::NbcCrop is doing - it calculates everything forced + // to MapUnit::Map100thMM, but extracts/packs Twips to the used SdrGrafCropItem in Writer. + const MapMode aMapModePool(pObj->getSdrModelFromSdrObject().GetItemPool().GetMetric(0)); + Size aGraphicSize(rGraphicObject.GetPrefSize()); + + if(MapUnit::MapPixel == rGraphicObject.GetPrefMapMode().GetMapUnit()) + { + aGraphicSize = Application::GetDefaultDevice()->PixelToLogic(aGraphicSize, aMapModePool); + } + else + { + aGraphicSize = OutputDevice::LogicToLogic(aGraphicSize, rGraphicObject.GetPrefMapMode(), aMapModePool); + } + + if(0 == aGraphicSize.Width() || 0 == aGraphicSize.Height()) + { + return false; + } + + const SdrGrafCropItem& rOldCrop = pObj->GetMergedItem(SDRATTR_GRAFCROP); + double fScaleX = ( aGraphicSize.Width() - rOldCrop.GetLeft() - rOldCrop.GetRight() ) / static_cast<double>(aOldRect.GetWidth()); + double fScaleY = ( aGraphicSize.Height() - rOldCrop.GetTop() - rOldCrop.GetBottom() ) / static_cast<double>(aOldRect.GetHeight()); + + sal_Int32 nDiffLeft = aNewRect.Left() - aOldRect.Left(); + sal_Int32 nDiffTop = aNewRect.Top() - aOldRect.Top(); + sal_Int32 nDiffRight = aNewRect.Right() - aOldRect.Right(); + sal_Int32 nDiffBottom = aNewRect.Bottom() - aOldRect.Bottom(); + + if(pObj->IsMirrored()) + { + // mirrored X or Y, for old stuff, exchange X + // check for aw080 + sal_Int32 nTmp(nDiffLeft); + nDiffLeft = -nDiffRight; + nDiffRight = -nTmp; + } + + sal_Int32 nLeftCrop = static_cast<sal_Int32>( rOldCrop.GetLeft() + nDiffLeft * fScaleX ); + sal_Int32 nTopCrop = static_cast<sal_Int32>( rOldCrop.GetTop() + nDiffTop * fScaleY ); + sal_Int32 nRightCrop = static_cast<sal_Int32>( rOldCrop.GetRight() - nDiffRight * fScaleX ); + sal_Int32 nBottomCrop = static_cast<sal_Int32>( rOldCrop.GetBottom() - nDiffBottom * fScaleY ); + + SfxItemPool& rPool = getSdrDragView().GetModel().GetItemPool(); + SfxItemSetFixed<SDRATTR_GRAFCROP, SDRATTR_GRAFCROP> aSet( rPool ); + aSet.Put( SdrGrafCropItem( nLeftCrop, nTopCrop, nRightCrop, nBottomCrop ) ); + getSdrDragView().SetAttributes( aSet, false ); + } + + if(bUndo) + { + getSdrDragView().EndUndo(); + } + + return true; +} + +PointerStyle SdrDragCrop::GetSdrDragPointer() const +{ + return PointerStyle::Crop; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svddrgv.cxx b/svx/source/svdraw/svddrgv.cxx new file mode 100644 index 0000000000..8b9567c20b --- /dev/null +++ b/svx/source/svdraw/svddrgv.cxx @@ -0,0 +1,920 @@ +/* -*- 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 <osl/diagnose.h> +#include <tools/debug.hxx> +#include <svx/svddrgv.hxx> +#include <svx/svdview.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdoedge.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include "svddrgm1.hxx" +#include <svx/obj3d.hxx> +#include <svx/svdoashp.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpagewindow.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/lok.hxx> + +using namespace sdr; + +SdrDragView::SdrDragView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrExchangeView(rSdrModel, pOut) + , mpDragHdl(nullptr) + , mpInsPointUndo(nullptr) + , meDragHdl(SdrHdlKind::Move) + , mnDragThresholdPixels(6) + , mbFramDrag(false) + , mbMarkedHitMovesAlways(false) + , mbDragLimit(false) + , mbDragHdl(false) + , mbDragStripes(false) + , mbSolidDragging(utl::ConfigManager::IsFuzzing() || SvtOptionsDrawinglayer::IsSolidDragCreate()) + , mbResizeAtCenter(false) + , mbCrookAtCenter(false) + , mbDragWithCopy(false) + , mbInsGluePoint(false) + , mbInsObjPointMode(false) + , mbInsGluePointMode(false) + , mbNoDragXorPolys(false) +{ + meDragMode = SdrDragMode::Move; +} + +SdrDragView::~SdrDragView() +{ +} + +bool SdrDragView::IsAction() const +{ + return (mpCurrentSdrDragMethod || SdrExchangeView::IsAction()); +} + +void SdrDragView::MovAction(const Point& rPnt) +{ + SdrExchangeView::MovAction(rPnt); + if (mpCurrentSdrDragMethod) + { + MovDragObj(rPnt); + } +} + +void SdrDragView::EndAction() +{ + if (mpCurrentSdrDragMethod) + { + EndDragObj(); + } + SdrExchangeView::EndAction(); +} + +void SdrDragView::BckAction() +{ + SdrExchangeView::BckAction(); + BrkDragObj(); +} + +void SdrDragView::BrkAction() +{ + SdrExchangeView::BrkAction(); + BrkDragObj(); +} + +void SdrDragView::TakeActionRect(tools::Rectangle& rRect) const +{ + if (mpCurrentSdrDragMethod) + { + rRect=maDragStat.GetActionRect(); + if (rRect.IsEmpty()) + { + SdrPageView* pPV = GetSdrPageView(); + + if(pPV&& pPV->HasMarkedObjPageView()) + { + // #i95646# is this used..? + const basegfx::B2DRange aBoundRange(mpCurrentSdrDragMethod->getCurrentRange()); + if (aBoundRange.isEmpty()) + { + rRect.SetEmpty(); + } + else + { + rRect = tools::Rectangle( + basegfx::fround(aBoundRange.getMinX()), basegfx::fround(aBoundRange.getMinY()), + basegfx::fround(aBoundRange.getMaxX()), basegfx::fround(aBoundRange.getMaxY())); + } + } + } + if (rRect.IsEmpty()) + { + rRect=tools::Rectangle(maDragStat.GetNow(),maDragStat.GetNow()); + } + } + else + { + SdrExchangeView::TakeActionRect(rRect); + } +} + +bool SdrDragView::TakeDragObjAnchorPos(Point& rPos, bool bTR ) const +{ + tools::Rectangle aR; + TakeActionRect(aR); + rPos = bTR ? aR.TopRight() : aR.TopLeft(); + if (GetMarkedObjectCount()==1 && IsDragObj() && // only on single selection + !IsDraggingPoints() && !IsDraggingGluePoints() && // not when moving points + dynamic_cast<const SdrDragMovHdl*>( mpCurrentSdrDragMethod.get() ) == nullptr) // not when moving handles + { + SdrObject* pObj=GetMarkedObjectByIndex(0); + if (auto pCaptionObj = dynamic_cast<SdrCaptionObj*>(pObj)) + { + Point aPt(pCaptionObj->GetTailPos()); + bool bTail=meDragHdl==SdrHdlKind::Poly; // drag tail + bool bOwn=dynamic_cast<const SdrDragObjOwn*>( mpCurrentSdrDragMethod.get() ) != nullptr; // specific to object + if (!bTail) + { // for bTail, TakeActionRect already does the right thing + if (bOwn) + { // bOwn may be MoveTextFrame, ResizeTextFrame, but may not (any more) be DragTail + rPos=aPt; + } + else + { + // drag the whole Object (Move, Resize, ...) + const basegfx::B2DPoint aTransformed(mpCurrentSdrDragMethod->getCurrentTransformation() * basegfx::B2DPoint(aPt.X(), aPt.Y())); + rPos.setX( basegfx::fround(aTransformed.getX()) ); + rPos.setY( basegfx::fround(aTransformed.getY()) ); + } + } + } + return true; + } + return false; +} + + +bool SdrDragView::TakeDragLimit(SdrDragMode /*eMode*/, tools::Rectangle& /*rRect*/) const +{ + return false; +} + +bool SdrDragView::BegDragObj(const Point& rPnt, OutputDevice* pOut, SdrHdl* pHdl, short nMinMov, SdrDragMethod* _pForcedMeth) +{ + BrkAction(); + + // so we don't leak the object on early return + std::unique_ptr<SdrDragMethod> pForcedMeth(_pForcedMeth); + + bool bRet=false; + { + SetDragWithCopy(false); + //TODO: aAni.Reset(); + mpCurrentSdrDragMethod=nullptr; + SdrDragMode eTmpMode=meDragMode; + if (eTmpMode==SdrDragMode::Move && pHdl!=nullptr && pHdl->GetKind()!=SdrHdlKind::Move) { + eTmpMode=SdrDragMode::Resize; + } + mbDragLimit=TakeDragLimit(eTmpMode,maDragLimit); + mbFramDrag=ImpIsFrameHandles(); + if (!mbFramDrag && + (mpMarkedObj==nullptr || !mpMarkedObj->hasSpecialDrag()) && + (pHdl==nullptr || pHdl->GetObj()==nullptr)) { + mbFramDrag=true; + } + + Point aPnt(rPnt); + basegfx::B2DVector aGridOffset(0.0, 0.0); + + // Coordinate maybe affected by GridOffset, so we may need to + // adapt to Model-coordinates here + if((comphelper::LibreOfficeKit::isActive() && mpMarkedObj + && getPossibleGridOffsetForSdrObject(aGridOffset, GetMarkedObjectByIndex(0), GetSdrPageView())) + || (getPossibleGridOffsetForPosition( + aGridOffset, + basegfx::B2DPoint(aPnt.X(), aPnt.Y()), + GetSdrPageView()))) + { + aPnt.AdjustX(basegfx::fround(-aGridOffset.getX())); + aPnt.AdjustY(basegfx::fround(-aGridOffset.getY())); + } + + if(pHdl == nullptr + || pHdl->GetKind() == SdrHdlKind::Move + || pHdl->GetKind() == SdrHdlKind::MirrorAxis + || pHdl->GetKind() == SdrHdlKind::Transparence + || pHdl->GetKind() == SdrHdlKind::Gradient) + { + maDragStat.Reset(aPnt); + } + else + { + maDragStat.Reset(pHdl->GetPos()); + } + + maDragStat.SetView(static_cast<SdrView*>(this)); + maDragStat.SetPageView(mpMarkedPV); // <<-- DragPV has to go here!!! + maDragStat.SetMinMove(ImpGetMinMovLogic(nMinMov,pOut)); + maDragStat.SetHdl(pHdl); + maDragStat.NextPoint(); + mpDragWin=pOut; + mpDragHdl=pHdl; + meDragHdl= pHdl==nullptr ? SdrHdlKind::Move : pHdl->GetKind(); + mbDragHdl=meDragHdl==SdrHdlKind::Ref1 || meDragHdl==SdrHdlKind::Ref2 || meDragHdl==SdrHdlKind::MirrorAxis; + + // Expand test for SdrHdlKind::Anchor_TR + bool bNotDraggable = (SdrHdlKind::Anchor == meDragHdl || SdrHdlKind::Anchor_TR == meDragHdl); + + if(pHdl && (pHdl->GetKind() == SdrHdlKind::SmartTag) && pForcedMeth ) + { + // just use the forced method for smart tags + } + else if(mbDragHdl) + { + mpCurrentSdrDragMethod.reset(new SdrDragMovHdl(*this)); + } + else if(!bNotDraggable) + { + switch (meDragMode) + { + case SdrDragMode::Rotate: case SdrDragMode::Shear: + { + switch (meDragHdl) + { + case SdrHdlKind::Left: case SdrHdlKind::Right: + case SdrHdlKind::Upper: case SdrHdlKind::Lower: + { + // are 3D objects selected? + bool b3DObjSelected = false; + for(size_t a=0; !b3DObjSelected && a<GetMarkedObjectCount(); ++a) + { + SdrObject* pObj = GetMarkedObjectByIndex(a); + if(DynCastE3dObject(pObj)) + b3DObjSelected = true; + } + // If yes, allow shear even when !IsShearAllowed, + // because 3D objects are limited rotations + if (!b3DObjSelected && !IsShearAllowed()) + return false; + mpCurrentSdrDragMethod.reset(new SdrDragShear(*this,meDragMode==SdrDragMode::Rotate)); + } break; + case SdrHdlKind::UpperLeft: case SdrHdlKind::UpperRight: + case SdrHdlKind::LowerLeft: case SdrHdlKind::LowerRight: + { + if (meDragMode==SdrDragMode::Shear) + { + if (!IsDistortAllowed(true) && !IsDistortAllowed()) return false; + mpCurrentSdrDragMethod.reset(new SdrDragDistort(*this)); + } + else + { + if (!IsRotateAllowed(true)) return false; + mpCurrentSdrDragMethod.reset(new SdrDragRotate(*this)); + } + } break; + default: + { + if (IsMarkedHitMovesAlways() && meDragHdl==SdrHdlKind::Move) + { // SdrHdlKind::Move is true, even if Obj is hit directly + if (!IsMoveAllowed()) return false; + mpCurrentSdrDragMethod.reset(new SdrDragMove(*this)); + } + else + { + if (!IsRotateAllowed(true)) return false; + mpCurrentSdrDragMethod.reset(new SdrDragRotate(*this)); + } + } + } + } break; + case SdrDragMode::Mirror: + { + if (meDragHdl==SdrHdlKind::Move && IsMarkedHitMovesAlways()) + { + if (!IsMoveAllowed()) return false; + mpCurrentSdrDragMethod.reset(new SdrDragMove(*this)); + } + else + { + if (!IsMirrorAllowed(true,true)) return false; + mpCurrentSdrDragMethod.reset(new SdrDragMirror(*this)); + } + } break; + + case SdrDragMode::Crop: + { + if (meDragHdl==SdrHdlKind::Move && IsMarkedHitMovesAlways()) + { + if (!IsMoveAllowed()) + return false; + mpCurrentSdrDragMethod.reset(new SdrDragMove(*this)); + } + else + { + if (!IsCropAllowed()) + return false; + mpCurrentSdrDragMethod.reset(new SdrDragCrop(*this)); + } + } + break; + + case SdrDragMode::Transparence: + { + if(meDragHdl == SdrHdlKind::Move && IsMarkedHitMovesAlways()) + { + if(!IsMoveAllowed()) + return false; + mpCurrentSdrDragMethod.reset(new SdrDragMove(*this)); + } + else + { + if(!IsTransparenceAllowed()) + return false; + + mpCurrentSdrDragMethod.reset(new SdrDragGradient(*this, false)); + } + break; + } + case SdrDragMode::Gradient: + { + if(meDragHdl == SdrHdlKind::Move && IsMarkedHitMovesAlways()) + { + if(!IsMoveAllowed()) + return false; + mpCurrentSdrDragMethod.reset(new SdrDragMove(*this)); + } + else + { + if(!IsGradientAllowed()) + return false; + + mpCurrentSdrDragMethod.reset(new SdrDragGradient(*this)); + } + break; + } + + case SdrDragMode::Crook : + { + if (meDragHdl==SdrHdlKind::Move && IsMarkedHitMovesAlways()) + { + if (!IsMoveAllowed()) return false; + mpCurrentSdrDragMethod.reset( new SdrDragMove(*this) ); + } + else + { + if (!IsCrookAllowed(true) && !IsCrookAllowed()) return false; + mpCurrentSdrDragMethod.reset( new SdrDragCrook(*this) ); + } + } break; + + default: + { + // SdrDragMode::Move + if((meDragHdl == SdrHdlKind::Move) && !IsMoveAllowed()) + { + return false; + } + else if(meDragHdl == SdrHdlKind::Glue) + { + mpCurrentSdrDragMethod.reset( new SdrDragMove(*this) ); + } + else + { + if(mbFramDrag) + { + if(meDragHdl == SdrHdlKind::Move) + { + mpCurrentSdrDragMethod.reset( new SdrDragMove(*this) ); + } + else + { + if(!IsResizeAllowed(true)) + { + return false; + } + + bool bSingleTextObjMark = false; // SJ: #i100490# + if ( GetMarkedObjectCount() == 1 ) + { + mpMarkedObj=GetMarkedObjectByIndex(0); + if ( mpMarkedObj && + DynCastSdrTextObj( mpMarkedObj) != nullptr && + static_cast<SdrTextObj*>(mpMarkedObj)->IsTextFrame() ) + bSingleTextObjMark = true; + } + if ( bSingleTextObjMark ) + mpCurrentSdrDragMethod.reset( new SdrDragObjOwn(*this) ); + else + mpCurrentSdrDragMethod.reset( new SdrDragResize(*this) ); + } + } + else + { + if(SdrHdlKind::Move == meDragHdl) + { + const bool bCustomShapeSelected(1 == GetMarkedObjectCount() && dynamic_cast<const SdrObjCustomShape*>(GetMarkedObjectByIndex(0)) != nullptr); + + if(bCustomShapeSelected) + { + mpCurrentSdrDragMethod.reset( new SdrDragMove( *this ) ); + } + } + else if(SdrHdlKind::Poly == meDragHdl) + { + const bool bConnectorSelected(1 == GetMarkedObjectCount() && dynamic_cast<const SdrEdgeObj*>(GetMarkedObjectByIndex(0)) != nullptr); + + if(bConnectorSelected) + { + // #i97784# + // fallback to old behaviour for connectors (see + // text in task description for more details) + } + else if(!IsMoveAllowed() || !IsResizeAllowed()) + { + // #i77187# + // do not allow move of polygon points if object is move or size protected + return false; + } + } + + if(!mpCurrentSdrDragMethod) + { + // fallback to DragSpecial if no interaction defined + mpCurrentSdrDragMethod.reset( new SdrDragObjOwn(*this) ); + } + } + } + } + } + } + if (pForcedMeth) + { + mpCurrentSdrDragMethod = std::move(pForcedMeth); + } + maDragStat.SetDragMethod(mpCurrentSdrDragMethod.get()); + if (mpCurrentSdrDragMethod) + { + bRet = mpCurrentSdrDragMethod->BeginSdrDrag(); + if (!bRet) + { + if (pHdl==nullptr && dynamic_cast< const SdrDragObjOwn* >(mpCurrentSdrDragMethod.get()) != nullptr) + { + // Obj may not Move SpecialDrag, so try with MoveFrameDrag + mpCurrentSdrDragMethod.reset(); + + if (!IsMoveAllowed()) + return false; + + mbFramDrag=true; + mpCurrentSdrDragMethod.reset( new SdrDragMove(*this) ); + maDragStat.SetDragMethod(mpCurrentSdrDragMethod.get()); + bRet = mpCurrentSdrDragMethod->BeginSdrDrag(); + } + } + if (!bRet) + { + mpCurrentSdrDragMethod.reset(); + maDragStat.SetDragMethod(mpCurrentSdrDragMethod.get()); + } + } + } + + return bRet; +} + +void SdrDragView::MovDragObj(const Point& rPnt) +{ + if (!mpCurrentSdrDragMethod) + return; + + Point aPnt(rPnt); + basegfx::B2DVector aGridOffset(0.0, 0.0); + + // Coordinate maybe affected by GridOffset, so we may need to + // adapt to Model-coordinates here + if((comphelper::LibreOfficeKit::isActive() && mpMarkedObj + && getPossibleGridOffsetForSdrObject(aGridOffset, GetMarkedObjectByIndex(0), GetSdrPageView())) + || (getPossibleGridOffsetForPosition( + aGridOffset, + basegfx::B2DPoint(aPnt.X(), aPnt.Y()), + GetSdrPageView()))) + { + aPnt.AdjustX(basegfx::fround(-aGridOffset.getX())); + aPnt.AdjustY(basegfx::fround(-aGridOffset.getY())); + } + + ImpLimitToWorkArea(aPnt); + mpCurrentSdrDragMethod->MoveSdrDrag(aPnt); // this call already makes a Hide()/Show combination +} + +bool SdrDragView::EndDragObj(bool bCopy) +{ + bool bRet(false); + + // #i73341# If inserting GluePoint, do not insist on last points being different + if(mpCurrentSdrDragMethod && maDragStat.IsMinMoved() && (IsInsertGluePoint() || maDragStat.GetNow() != maDragStat.GetPrev())) + { + sal_Int32 nSavedHdlCount=0; + + if (bEliminatePolyPoints) + { + nSavedHdlCount=GetMarkablePointCount(); + } + + const bool bUndo = IsUndoEnabled(); + if (IsInsertGluePoint() && bUndo) + { + BegUndo(maInsPointUndoStr); + AddUndo(std::unique_ptr<SdrUndoAction>(mpInsPointUndo)); + } + + bRet = mpCurrentSdrDragMethod->EndSdrDrag(bCopy); + + if( IsInsertGluePoint() && bUndo) + EndUndo(); + + mpCurrentSdrDragMethod.reset(); + + if (bEliminatePolyPoints) + { + if (nSavedHdlCount!=GetMarkablePointCount()) + { + UnmarkAllPoints(); + } + } + + if (mbInsPolyPoint) + { + SetMarkHandles(nullptr); + mbInsPolyPoint=false; + if( bUndo ) + { + BegUndo(maInsPointUndoStr); + AddUndo(std::unique_ptr<SdrUndoAction>(mpInsPointUndo)); + EndUndo(); + } + } + + meDragHdl=SdrHdlKind::Move; + mpDragHdl=nullptr; + + if (!mbSomeObjChgdFlag) + { + // Obj did not broadcast (e. g. Writer FlyFrames) + if(!mbDragHdl) + { + AdjustMarkHdl(); + } + } + } + else + { + BrkDragObj(); + } + + mbInsPolyPoint=false; + SetInsertGluePoint(false); + + return bRet; +} + +void SdrDragView::BrkDragObj() +{ + if (!mpCurrentSdrDragMethod) + return; + + mpCurrentSdrDragMethod->CancelSdrDrag(); + + mpCurrentSdrDragMethod.reset(); + + if (mbInsPolyPoint) + { + mpInsPointUndo->Undo(); // delete inserted point again + delete mpInsPointUndo; + mpInsPointUndo=nullptr; + SetMarkHandles(nullptr); + mbInsPolyPoint=false; + } + + if (IsInsertGluePoint()) + { + mpInsPointUndo->Undo(); // delete inserted gluepoint again + delete mpInsPointUndo; + mpInsPointUndo=nullptr; + SetInsertGluePoint(false); + } + + meDragHdl=SdrHdlKind::Move; + mpDragHdl=nullptr; +} + +bool SdrDragView::IsInsObjPointPossible() const +{ + return mpMarkedObj!=nullptr && mpMarkedObj->IsPolyObj(); +} + +bool SdrDragView::ImpBegInsObjPoint(bool bIdxZwang, const Point& rPnt, bool bNewObj, OutputDevice* pOut) +{ + bool bRet(false); + + if(auto pMarkedPath = dynamic_cast<SdrPathObj*>( mpMarkedObj)) + { + BrkAction(); + mpInsPointUndo = dynamic_cast<SdrUndoGeoObj*>(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*mpMarkedObj).release()); + DBG_ASSERT( mpInsPointUndo, "svx::SdrDragView::BegInsObjPoint(), could not create correct undo object!" ); + + OUString aStr(SvxResId(STR_DragInsertPoint)); + + maInsPointUndoStr = aStr.replaceFirst("%1", mpMarkedObj->TakeObjNameSingul() ); + + Point aPt(rPnt); + + if(bNewObj) + aPt = GetSnapPos(aPt,mpMarkedPV); + + bool bClosed0 = pMarkedPath->IsClosedObj(); + + const sal_uInt32 nInsPointNum { bIdxZwang + ? pMarkedPath->NbcInsPoint(aPt, bNewObj) + : pMarkedPath->NbcInsPointOld(aPt, bNewObj) + }; + + if(bClosed0 != pMarkedPath->IsClosedObj()) + { + // Obj was closed implicitly + // object changed + pMarkedPath->SetChanged(); + pMarkedPath->BroadcastObjectChange(); + } + + if (nInsPointNum != SAL_MAX_UINT32) + { + mbInsPolyPoint = true; + UnmarkAllPoints(); + AdjustMarkHdl(); + + bRet = BegDragObj(rPnt, pOut, maHdlList.GetHdl(nInsPointNum), 0); + + if (bRet) + { + maDragStat.SetMinMoved(); + MovDragObj(rPnt); + } + } + else + { + delete mpInsPointUndo; + mpInsPointUndo = nullptr; + } + } + + return bRet; +} + +bool SdrDragView::EndInsObjPoint(SdrCreateCmd eCmd) +{ + if(IsInsObjPoint()) + { + Point aPnt(maDragStat.GetNow()); + bool bOk=EndDragObj(); + if (bOk && eCmd!=SdrCreateCmd::ForceEnd) + { + // Ret=True means: Action is over. + bOk = ! ImpBegInsObjPoint(true, aPnt, eCmd == SdrCreateCmd::NextObject, mpDragWin); + } + + return bOk; + } else return false; +} + +bool SdrDragView::IsInsGluePointPossible() const +{ + bool bRet=false; + if (IsInsGluePointMode() && AreObjectsMarked()) + { + if (GetMarkedObjectCount()==1) + { + // return sal_False, if only 1 object which is a connector. + const SdrObject* pObj=GetMarkedObjectByIndex(0); + if (dynamic_cast<const SdrEdgeObj *>(pObj) == nullptr) + { + bRet=true; + } + } + else + { + bRet=true; + } + } + return bRet; +} + +bool SdrDragView::BegInsGluePoint(const Point& rPnt) +{ + bool bRet=false; + SdrObject* pObj; + SdrPageView* pPV; + if (PickMarkedObj(rPnt,pObj,pPV,SdrSearchOptions::PASS2BOUND)) + { + BrkAction(); + UnmarkAllGluePoints(); + mpInsPointUndo = dynamic_cast<SdrUndoGeoObj*>(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj).release()); + DBG_ASSERT( mpInsPointUndo, "svx::SdrDragView::BegInsObjPoint(), could not create correct undo object!" ); + OUString aStr(SvxResId(STR_DragInsertGluePoint)); + + maInsPointUndoStr = aStr.replaceFirst("%1", pObj->TakeObjNameSingul() ); + + SdrGluePointList* pGPL=pObj->ForceGluePointList(); + if (pGPL!=nullptr) + { + sal_uInt16 nGlueIdx=pGPL->Insert(SdrGluePoint()); + SdrGluePoint& rGP=(*pGPL)[nGlueIdx]; + sal_uInt16 nGlueId=rGP.GetId(); + rGP.SetAbsolutePos(rPnt,*pObj); + + SdrHdl* pHdl=nullptr; + if (MarkGluePoint(pObj,nGlueId,false)) + { + pHdl=GetGluePointHdl(pObj,nGlueId); + } + if (pHdl!=nullptr && pHdl->GetKind()==SdrHdlKind::Glue && pHdl->GetObj()==pObj && pHdl->GetObjHdlNum()==nGlueId) + { + SetInsertGluePoint(true); + bRet=BegDragObj(rPnt,nullptr,pHdl,0); + if (bRet) + { + maDragStat.SetMinMoved(); + MovDragObj(rPnt); + } + else + { + SetInsertGluePoint(false); + delete mpInsPointUndo; + mpInsPointUndo=nullptr; + } + } + else + { + OSL_FAIL("BegInsGluePoint(): GluePoint handle not found."); + } + } + else + { + // no gluepoints possible for this object (e. g. Edge) + SetInsertGluePoint(false); + delete mpInsPointUndo; + mpInsPointUndo=nullptr; + } + } + + return bRet; +} + +void SdrDragView::ShowDragObj() +{ + if(!mpCurrentSdrDragMethod || maDragStat.IsShown()) + return; + + // Changed for the GridOffset stuff: No longer iterate over + // SdrPaintWindow(s), but now over SdrPageWindow(s), so doing the + // same as the SdrHdl visualizations (see ::CreateB2dIAObject) do. + // This is needed to get access to an ObjectContact which is needed + // to evtl. process that GridOffset in CreateOverlayGeometry + SdrPageView* pPageView(GetSdrPageView()); + + if(nullptr != pPageView) + { + for(sal_uInt32 a(0); a < pPageView->PageWindowCount(); a++) + { + const SdrPageWindow& rPageWindow(*pPageView->GetPageWindow(a)); + const SdrPaintWindow& rPaintWindow(rPageWindow.GetPaintWindow()); + + if(rPaintWindow.OutputToWindow()) + { + const rtl::Reference<sdr::overlay::OverlayManager>& xOverlayManager( + rPaintWindow.GetOverlayManager()); + + if(xOverlayManager.is()) + { + mpCurrentSdrDragMethod->CreateOverlayGeometry( + *xOverlayManager, + rPageWindow.GetObjectContact()); + + // #i101679# Force changed overlay to be shown + xOverlayManager->flush(); + } + } + } + } + + maDragStat.SetShown(true); +} + +void SdrDragView::HideDragObj() +{ + if(mpCurrentSdrDragMethod && maDragStat.IsShown()) + { + mpCurrentSdrDragMethod->destroyOverlayGeometry(); + maDragStat.SetShown(false); + } +} + + +void SdrDragView::SetNoDragXorPolys(bool bOn) +{ + if (IsNoDragXorPolys()==bOn) + return; + + const bool bDragging(mpCurrentSdrDragMethod); + const bool bShown(bDragging && maDragStat.IsShown()); + + if(bShown) + { + HideDragObj(); + } + + mbNoDragXorPolys = bOn; + + if(bDragging) + { + // force recreation of drag content + mpCurrentSdrDragMethod->resetSdrDragEntries(); + } + + if(bShown) + { + ShowDragObj(); + } +} + +void SdrDragView::SetDragStripes(bool bOn) +{ + if (mpCurrentSdrDragMethod && maDragStat.IsShown()) + { + HideDragObj(); + mbDragStripes=bOn; + ShowDragObj(); + } + else + { + mbDragStripes=bOn; + } +} + +bool SdrDragView::IsOrthoDesired() const +{ + if( dynamic_cast< const SdrDragObjOwn* >( mpCurrentSdrDragMethod.get() ) + || dynamic_cast< const SdrDragResize* >(mpCurrentSdrDragMethod.get() )) + { + return m_bOrthoDesiredOnMarked; + } + + return false; +} + +void SdrDragView::SetMarkHandles(SfxViewShell* pOtherShell) +{ + if( mpDragHdl ) + mpDragHdl = nullptr; + + SdrExchangeView::SetMarkHandles(pOtherShell); +} + +void SdrDragView::SetSolidDragging(bool bOn) +{ + if(mbSolidDragging != bOn) + { + mbSolidDragging = bOn; + } +} + +bool SdrDragView::IsSolidDragging() const +{ + // allow each user to disable by having a local setting, but using AND for + // checking allowance + return mbSolidDragging && SvtOptionsDrawinglayer::IsSolidDragCreate(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdedtv.cxx b/svx/source/svdraw/svdedtv.cxx new file mode 100644 index 0000000000..8c1c60bba6 --- /dev/null +++ b/svx/source/svdraw/svdedtv.cxx @@ -0,0 +1,1084 @@ +/* -*- 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/svdedtv.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpoev.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/e3dsceneupdater.hxx> +#include <rtl/strbuf.hxx> +#include <svx/svdview.hxx> +#include <clonelist.hxx> +#include <svx/svdogrp.hxx> +#include <svx/scene3d.hxx> +#include <svx/xfillit0.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +using namespace com::sun::star; + +void SdrEditView::ImpResetPossibilityFlags() +{ + m_bReadOnly =false; + + m_bGroupPossible =false; + m_bUnGroupPossible =false; + m_bGrpEnterPossible =false; + m_bToTopPossible =false; + m_bToBtmPossible =false; + m_bReverseOrderPossible =false; + + m_bImportMtfPossible =false; + m_bCombinePossible =false; + m_bDismantlePossible =false; + m_bCombineNoPolyPolyPossible =false; + m_bDismantleMakeLinesPossible=false; + m_bOrthoDesiredOnMarked =false; + + m_bOneOrMoreMovable =false; + m_bMoreThanOneNoMovRot =false; + m_bContortionPossible =false; + m_bMoveAllowed =false; + m_bResizeFreeAllowed =false; + m_bResizePropAllowed =false; + m_bRotateFreeAllowed =false; + m_bRotate90Allowed =false; + m_bMirrorFreeAllowed =false; + m_bMirror45Allowed =false; + m_bMirror90Allowed =false; + m_bTransparenceAllowed =false; + m_bCropAllowed =false; + m_bGradientAllowed =false; + m_bShearAllowed =false; + m_bEdgeRadiusAllowed =false; + m_bCanConvToPath =false; + m_bCanConvToPoly =false; + m_bCanConvToContour =false; + m_bMoveProtect =false; + m_bResizeProtect =false; +} + +SdrEditView::SdrEditView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrMarkView(rSdrModel, pOut) + , m_bPossibilitiesDirty(true) + , m_bReadOnly(false) + , m_bGroupPossible(false) + , m_bUnGroupPossible(false) + , m_bGrpEnterPossible(false) + , m_bToTopPossible(false) + , m_bToBtmPossible(false) + , m_bReverseOrderPossible(false) + , m_bImportMtfPossible(false) + , m_bCombinePossible(false) + , m_bDismantlePossible(false) + , m_bCombineNoPolyPolyPossible(false) + , m_bDismantleMakeLinesPossible(false) + , m_bOrthoDesiredOnMarked(false) + , m_bOneOrMoreMovable(false) + , m_bMoreThanOneNoMovRot(false) + , m_bContortionPossible(false) + , m_bMoveAllowed(false) + , m_bResizeFreeAllowed(false) + , m_bResizePropAllowed(false) + , m_bRotateFreeAllowed(false) + , m_bRotate90Allowed(false) + , m_bMirrorFreeAllowed(false) + , m_bMirror45Allowed(false) + , m_bMirror90Allowed(false) + , m_bShearAllowed(false) + , m_bEdgeRadiusAllowed(false) + , m_bTransparenceAllowed(false) + , m_bCropAllowed(false) + , m_bGradientAllowed(false) + , m_bCanConvToPath(false) + , m_bCanConvToPoly(false) + , m_bCanConvToContour(false) + , m_bMoveProtect(false) + , m_bResizeProtect(false) +{ +} + +SdrEditView::~SdrEditView() +{ +} + +void SdrEditView::InsertNewLayer(const OUString& rName, sal_uInt16 nPos) +{ + SdrLayerAdmin& rLA = GetModel().GetLayerAdmin(); + sal_uInt16 nMax=rLA.GetLayerCount(); + if (nPos>nMax) nPos=nMax; + rLA.NewLayer(rName,nPos); + + if( GetModel().IsUndoEnabled() ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewLayer(nPos,rLA, GetModel())); + + GetModel().SetChanged(); +} + +bool SdrEditView::ImpDelLayerCheck(SdrObjList const * pOL, SdrLayerID nDelID) const +{ + bool bDelAll(true); + + for(size_t nObjNum = pOL->GetObjCount(); nObjNum > 0 && bDelAll;) + { + nObjNum--; + SdrObject* pObj = pOL->GetObj(nObjNum); + SdrObjList* pSubOL = pObj->GetSubList(); + + // explicitly test for group objects and 3d scenes + if(pSubOL && (dynamic_cast<const SdrObjGroup*>(pObj) != nullptr || DynCastE3dScene(pObj))) + { + if(!ImpDelLayerCheck(pSubOL, nDelID)) + { + bDelAll = false; + } + } + else + { + if(pObj->GetLayer() != nDelID) + { + bDelAll = false; + } + } + } + + return bDelAll; +} + +void SdrEditView::ImpDelLayerDelObjs(SdrObjList* pOL, SdrLayerID nDelID) +{ + const size_t nObjCount(pOL->GetObjCount()); + // make sure OrdNums are correct + pOL->GetObj(0)->GetOrdNum(); + + const bool bUndo = GetModel().IsUndoEnabled(); + + for(size_t nObjNum = nObjCount; nObjNum > 0;) + { + nObjNum--; + SdrObject* pObj = pOL->GetObj(nObjNum); + SdrObjList* pSubOL = pObj->GetSubList(); + + + // explicitly test for group objects and 3d scenes + if(pSubOL && (dynamic_cast<const SdrObjGroup*>( pObj) != nullptr || DynCastE3dScene(pObj))) + { + if(ImpDelLayerCheck(pSubOL, nDelID)) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj, true)); + pOL->RemoveObject(nObjNum); + } + else + { + ImpDelLayerDelObjs(pSubOL, nDelID); + } + } + else + { + if(pObj->GetLayer() == nDelID) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj, true)); + pOL->RemoveObject(nObjNum); + } + } + } +} + +void SdrEditView::DeleteLayer(const OUString& rName) +{ + SdrLayerAdmin& rLA = GetModel().GetLayerAdmin(); + SdrLayer* pLayer = rLA.GetLayer(rName); + + if(!pLayer) + return; + + sal_uInt16 nLayerNum(rLA.GetLayerPos(pLayer)); + SdrLayerID nDelID = pLayer->GetID(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_UndoDelLayer)); + + bool bMaPg(true); + + for(sal_uInt16 nPageKind(0); nPageKind < 2; nPageKind++) + { + // MasterPages and DrawPages + sal_uInt16 nPgCount(bMaPg ? GetModel().GetMasterPageCount() : GetModel().GetPageCount()); + + for(sal_uInt16 nPgNum(0); nPgNum < nPgCount; nPgNum++) + { + // over all pages + SdrPage* pPage = bMaPg ? GetModel().GetMasterPage(nPgNum) : GetModel().GetPage(nPgNum); + const size_t nObjCount(pPage->GetObjCount()); + + // make sure OrdNums are correct + if(nObjCount) + pPage->GetObj(0)->GetOrdNum(); + + for(size_t nObjNum(nObjCount); nObjNum > 0;) + { + nObjNum--; + SdrObject* pObj = pPage->GetObj(nObjNum); + SdrObjList* pSubOL = pObj->GetSubList(); + + // explicitly test for group objects and 3d scenes + if(pSubOL && (dynamic_cast<const SdrObjGroup*>(pObj) != nullptr || DynCastE3dScene(pObj))) + { + if(ImpDelLayerCheck(pSubOL, nDelID)) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj, true)); + pPage->RemoveObject(nObjNum); + } + else + { + ImpDelLayerDelObjs(pSubOL, nDelID); + } + } + else + { + if(pObj->GetLayer() == nDelID) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj, true)); + pPage->RemoveObject(nObjNum); + } + } + } + } + bMaPg = false; + } + + if( bUndo ) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteLayer(nLayerNum, rLA, GetModel())); + // coverity[leaked_storage] - ownership transferred to UndoDeleteLayer + rLA.RemoveLayer(nLayerNum).release(); + EndUndo(); + } + else + { + rLA.RemoveLayer(nLayerNum); + } + + GetModel().SetChanged(); +} + + +void SdrEditView::EndUndo() +{ + // #i13033# + // Comparison changed to 1L since EndUndo() is called later now + // and EndUndo WILL change count to count-1 + if(1 == GetModel().GetUndoBracketLevel()) + { + ImpBroadcastEdgesOfMarkedNodes(); + } + + // #i13033# + // moved to bottom to still have access to UNDOs inside of + // ImpBroadcastEdgesOfMarkedNodes() + GetModel().EndUndo(); +} + +void SdrEditView::ImpBroadcastEdgesOfMarkedNodes() +{ + std::vector<SdrObject*>::const_iterator iterPos; + const std::vector<SdrObject*>& rAllMarkedObjects = GetTransitiveHullOfMarkedObjects(); + + // #i13033# + // New mechanism to search for necessary disconnections for + // changed connectors inside the transitive hull of all at + // the beginning of UNDO selected objects + for(size_t a(0); a < rAllMarkedObjects.size(); a++) + { + SdrEdgeObj* pEdge = dynamic_cast<SdrEdgeObj*>( rAllMarkedObjects[a] ); + + if(pEdge) + { + SdrObject* pObj1 = pEdge->GetConnectedNode(false); + SdrObject* pObj2 = pEdge->GetConnectedNode(true); + + if(pObj1 && !pEdge->CheckNodeConnection(false)) + { + iterPos = std::find(rAllMarkedObjects.begin(),rAllMarkedObjects.end(),pObj1); + + if (iterPos == rAllMarkedObjects.end()) + { + if( IsUndoEnabled() ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pEdge)); + pEdge->DisconnectFromNode(false); + } + } + + if(pObj2 && !pEdge->CheckNodeConnection(true)) + { + iterPos = std::find(rAllMarkedObjects.begin(),rAllMarkedObjects.end(),pObj2); + + if (iterPos == rAllMarkedObjects.end()) + { + if( IsUndoEnabled() ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pEdge)); + pEdge->DisconnectFromNode(true); + } + } + } + } + + const size_t nMarkedEdgeCnt = GetMarkedEdgesOfMarkedNodes().GetMarkCount(); + + for (size_t i=0; i<nMarkedEdgeCnt; ++i) { + SdrMark* pEM = GetMarkedEdgesOfMarkedNodes().GetMark(i); + SdrObject* pEdgeTmp=pEM->GetMarkedSdrObj(); + SdrEdgeObj* pEdge=dynamic_cast<SdrEdgeObj*>( pEdgeTmp ); + if (pEdge!=nullptr) { + pEdge->SetEdgeTrackDirty(); + } + } +} + + +// Possibilities + + +void SdrEditView::MarkListHasChanged() +{ + SdrMarkView::MarkListHasChanged(); + m_bPossibilitiesDirty=true; +} + +void SdrEditView::ModelHasChanged() +{ + SdrMarkView::ModelHasChanged(); + m_bPossibilitiesDirty=true; +} + +bool SdrEditView::IsResizeAllowed(bool bProp) const +{ + ForcePossibilities(); + if (m_bResizeProtect) return false; + if (bProp) return m_bResizePropAllowed; + return m_bResizeFreeAllowed; +} + +bool SdrEditView::IsRotateAllowed(bool b90Deg) const +{ + ForcePossibilities(); + if (m_bMoveProtect) return false; + if (b90Deg) return m_bRotate90Allowed; + return m_bRotateFreeAllowed; +} + +bool SdrEditView::IsMirrorAllowed(bool b45Deg, bool b90Deg) const +{ + ForcePossibilities(); + if (m_bMoveProtect) return false; + if (b90Deg) return m_bMirror90Allowed; + if (b45Deg) return m_bMirror45Allowed; + return m_bMirrorFreeAllowed; +} + +bool SdrEditView::IsTransparenceAllowed() const +{ + ForcePossibilities(); + return m_bTransparenceAllowed; +} + +bool SdrEditView::IsCropAllowed() const +{ + ForcePossibilities(); + return m_bCropAllowed; +} + +bool SdrEditView::IsGradientAllowed() const +{ + ForcePossibilities(); + return m_bGradientAllowed; +} + +bool SdrEditView::IsShearAllowed() const +{ + ForcePossibilities(); + if (m_bResizeProtect) return false; + return m_bShearAllowed; +} + +bool SdrEditView::IsEdgeRadiusAllowed() const +{ + ForcePossibilities(); + return m_bEdgeRadiusAllowed; +} + +bool SdrEditView::IsCrookAllowed(bool bNoContortion) const +{ + // CrookMode missing here (no rotations allowed when shearing ...) + ForcePossibilities(); + if (bNoContortion) { + if (!m_bRotateFreeAllowed) return false; + return !m_bMoveProtect && m_bMoveAllowed; + } else { + return !m_bResizeProtect && m_bContortionPossible; + } +} + +bool SdrEditView::IsDistortAllowed(bool bNoContortion) const +{ + ForcePossibilities(); + if (bNoContortion) { + return false; + } else { + return !m_bResizeProtect && m_bContortionPossible; + } +} + +bool SdrEditView::IsCombinePossible(bool bNoPolyPoly) const +{ + ForcePossibilities(); + if (bNoPolyPoly) return m_bCombineNoPolyPolyPossible; + else return m_bCombinePossible; +} + +bool SdrEditView::IsDismantlePossible(bool bMakeLines) const +{ + ForcePossibilities(); + if (bMakeLines) return m_bDismantleMakeLinesPossible; + else return m_bDismantlePossible; +} + +void SdrEditView::CheckPossibilities() +{ + if (mbSomeObjChgdFlag) + { + m_bPossibilitiesDirty = true; + + // This call IS necessary to correct the MarkList, in which + // no longer to the model belonging objects still can reside. + // These ones need to be removed. + CheckMarked(); + } + + if (!m_bPossibilitiesDirty) + return; + + ImpResetPossibilityFlags(); + SortMarkedObjects(); + const size_t nMarkCount = GetMarkedObjectCount(); + if (nMarkCount != 0) + { + m_bReverseOrderPossible = (nMarkCount >= 2); + + size_t nMovableCount=0; + m_bGroupPossible=nMarkCount>=2; + m_bCombinePossible=nMarkCount>=2; + if (nMarkCount==1) + { + // check bCombinePossible more thoroughly + // still missing ... + const SdrObject* pObj=GetMarkedObjectByIndex(0); + //const SdrPathObj* pPath=dynamic_cast<SdrPathObj*>( pObj ); + bool bGroup=pObj->GetSubList()!=nullptr; + bool bHasText=pObj->GetOutlinerParaObject()!=nullptr; + if (bGroup || bHasText) { + m_bCombinePossible=true; + } + } + m_bCombineNoPolyPolyPossible=m_bCombinePossible; + // accept transformations for now + m_bMoveAllowed =true; + m_bResizeFreeAllowed=true; + m_bResizePropAllowed=true; + m_bRotateFreeAllowed=true; + m_bRotate90Allowed =true; + m_bMirrorFreeAllowed=true; + m_bMirror45Allowed =true; + m_bMirror90Allowed =true; + m_bShearAllowed =true; + m_bEdgeRadiusAllowed=false; + m_bContortionPossible=true; + m_bCanConvToContour = true; + + // these ones are only allowed when single object is selected + m_bTransparenceAllowed = (nMarkCount == 1); + m_bGradientAllowed = (nMarkCount == 1); + m_bCropAllowed = (nMarkCount == 1); + if(m_bGradientAllowed) + { + // gradient depends on fill style + const SdrMark* pM = GetSdrMarkByIndex(0); + const SdrObject* pObj = pM->GetMarkedSdrObj(); + + // may be group object, so get merged ItemSet + const SfxItemSet& rSet = pObj->GetMergedItemSet(); + SfxItemState eState = rSet.GetItemState(XATTR_FILLSTYLE, false); + + if(SfxItemState::DONTCARE != eState) + { + // If state is not DONTCARE, test the item + drawing::FillStyle eFillStyle = rSet.Get(XATTR_FILLSTYLE).GetValue(); + + if(eFillStyle != drawing::FillStyle_GRADIENT) + { + m_bGradientAllowed = false; + } + } + } + + bool bNoMovRotFound=false; + const SdrPageView* pPV0=nullptr; + + for (size_t nm=0; nm<nMarkCount; ++nm) { + const SdrMark* pM=GetSdrMarkByIndex(nm); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrPageView* pPV=pM->GetPageView(); + if (pPV!=pPV0) { + if (pPV->IsReadOnly()) m_bReadOnly=true; + pPV0=pPV; + } + + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + bool bMovPrt=pObj->IsMoveProtect(); + bool bSizPrt=pObj->IsResizeProtect(); + if (!bMovPrt && aInfo.bMoveAllowed) nMovableCount++; // count MovableObjs + if (bMovPrt) m_bMoveProtect=true; + if (bSizPrt) m_bResizeProtect=true; + + // not allowed when not allowed at one object + if(!aInfo.bTransparenceAllowed) + m_bTransparenceAllowed = false; + + // If one of these can't do something, none can + if (!aInfo.bMoveAllowed ) m_bMoveAllowed =false; + if (!aInfo.bResizeFreeAllowed) m_bResizeFreeAllowed=false; + if (!aInfo.bResizePropAllowed) m_bResizePropAllowed=false; + if (!aInfo.bRotateFreeAllowed) m_bRotateFreeAllowed=false; + if (!aInfo.bRotate90Allowed ) m_bRotate90Allowed =false; + if (!aInfo.bMirrorFreeAllowed) m_bMirrorFreeAllowed=false; + if (!aInfo.bMirror45Allowed ) m_bMirror45Allowed =false; + if (!aInfo.bMirror90Allowed ) m_bMirror90Allowed =false; + if (!aInfo.bShearAllowed ) m_bShearAllowed =false; + if (aInfo.bEdgeRadiusAllowed) m_bEdgeRadiusAllowed=true; + if (aInfo.bNoContortion ) m_bContortionPossible=false; + // For Crook with Contortion: all objects have to be + // Movable and Rotatable, except for a maximum of 1 of them + if (!m_bMoreThanOneNoMovRot) { + if (!aInfo.bMoveAllowed || !aInfo.bResizeFreeAllowed) { + m_bMoreThanOneNoMovRot=bNoMovRotFound; + bNoMovRotFound=true; + } + } + + // Must be resizable to allow cropping + if (!aInfo.bResizeFreeAllowed && !aInfo.bResizePropAllowed) + m_bCropAllowed = false; + + // if one member cannot be converted, no conversion is possible + if(!aInfo.bCanConvToContour) + m_bCanConvToContour = false; + + // Ungroup + if (!m_bUnGroupPossible) m_bUnGroupPossible=pObj->GetSubList()!=nullptr; + // ConvertToCurve: If at least one can be converted, that is fine. + if (aInfo.bCanConvToPath ) m_bCanConvToPath =true; + if (aInfo.bCanConvToPoly ) m_bCanConvToPoly =true; + + // Combine/Dismantle + if(m_bCombinePossible) + { + m_bCombinePossible = ImpCanConvertForCombine(pObj); + m_bCombineNoPolyPolyPossible = m_bCombinePossible; + } + + if (!m_bDismantlePossible) m_bDismantlePossible = ImpCanDismantle(pObj, false); + if (!m_bDismantleMakeLinesPossible) m_bDismantleMakeLinesPossible = ImpCanDismantle(pObj, true); + // check OrthoDesiredOnMarked + if (!m_bOrthoDesiredOnMarked && !aInfo.bNoOrthoDesired) m_bOrthoDesiredOnMarked=true; + // check ImportMtf + + if (!m_bImportMtfPossible) + { + const SdrGrafObj* pSdrGrafObj = dynamic_cast< const SdrGrafObj* >(pObj); + if (pSdrGrafObj != nullptr) + { + if ((pSdrGrafObj->HasGDIMetaFile() && !pSdrGrafObj->IsEPS()) || + pSdrGrafObj->isEmbeddedVectorGraphicData()) + { + m_bImportMtfPossible = true; + } + } + + const SdrOle2Obj* pSdrOle2Obj = dynamic_cast< const SdrOle2Obj* >(pObj); + if (pSdrOle2Obj) + { + m_bImportMtfPossible = pSdrOle2Obj->GetObjRef().is(); + } + } + } + + m_bOneOrMoreMovable=nMovableCount!=0; + m_bGrpEnterPossible=m_bUnGroupPossible; + } + ImpCheckToTopBtmPossible(); + static_cast<SdrPolyEditView*>(this)->ImpCheckPolyPossibilities(); + m_bPossibilitiesDirty=false; + + if (m_bReadOnly) { + bool bTemp=m_bGrpEnterPossible; + ImpResetPossibilityFlags(); + m_bReadOnly=true; + m_bGrpEnterPossible=bTemp; + } + if (!m_bMoveAllowed) return; + + // Don't allow moving glued connectors. + // Currently only implemented for single selection. + if (nMarkCount==1) { + SdrObject* pObj=GetMarkedObjectByIndex(0); + SdrEdgeObj* pEdge=dynamic_cast<SdrEdgeObj*>( pObj ); + if (pEdge!=nullptr) { + SdrObject* pNode1=pEdge->GetConnectedNode(true); + SdrObject* pNode2=pEdge->GetConnectedNode(false); + if (pNode1!=nullptr || pNode2!=nullptr) m_bMoveAllowed=false; + } + } + + // Don't allow enter Diagrams + if (1 == nMarkCount && m_bGrpEnterPossible) + { + SdrObject* pCandidate(GetMarkedObjectByIndex(0)); + + if(nullptr != pCandidate && pCandidate->isDiagram()) + m_bGrpEnterPossible = false; + } +} + + +void SdrEditView::ForceMarkedObjToAnotherPage() +{ + bool bFlg=false; + for (size_t nm=0; nm<GetMarkedObjectCount(); ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + tools::Rectangle aObjRect(pObj->GetCurrentBoundRect()); + tools::Rectangle aPgRect(pM->GetPageView()->GetPageRect()); + if (!aObjRect.Overlaps(aPgRect)) { + bool bFnd=false; + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + bFnd = aObjRect.Overlaps(pPV->GetPageRect()); + } + + if(bFnd) + { + pM->GetPageView()->GetObjList()->RemoveObject(pObj->GetOrdNum()); + pPV->GetObjList()->InsertObject(pObj, SAL_MAX_SIZE); + pM->SetPageView(pPV); + InvalidateAllWin(aObjRect); + bFlg=true; + } + } + } + if (bFlg) { + MarkListHasChanged(); + } +} + +std::vector<rtl::Reference<SdrObject>> SdrEditView::DeleteMarkedList(SdrMarkList const& rMark) +{ + std::vector<rtl::Reference<SdrObject>> ret; + if (rMark.GetMarkCount()!=0) + { + rMark.ForceSort(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(); + const size_t nMarkCount(rMark.GetMarkCount()); + + if(nMarkCount) + { + std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters; + + if( bUndo ) + { + for(size_t nm = nMarkCount; nm > 0;) + { + --nm; + SdrMark* pM = rMark.GetMark(nm); + SdrObject* pObj = pM->GetMarkedSdrObj(); + + // extra undo actions for changed connector which now may hold its laid out path (SJ) + AddUndoActions(CreateConnectorUndo( *pObj )); + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj)); + } + } + + // make sure, OrderNums are correct: + rMark.GetMark(0)->GetMarkedSdrObj()->GetOrdNum(); + + for(size_t nm = nMarkCount; nm > 0;) + { + --nm; + SdrMark* pM = rMark.GetMark(nm); + SdrObject* pObj = pM->GetMarkedSdrObj(); + SdrObjList* pOL = pObj->getParentSdrObjListFromSdrObject(); + const size_t nOrdNum(pObj->GetOrdNumDirect()); + + bool bIs3D = DynCastE3dObject(pObj); + // set up a scene updater if object is a 3d object + if(bIs3D) + { + aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pObj)); + } + + if( !bUndo ) + { + // tdf#108863 and tdf#108889 don't delete objects before EndUndo() + ret.push_back(pObj); + } + + pOL->RemoveObject(nOrdNum); + } + + // fire scene updaters + while(!aUpdaters.empty()) + { + delete aUpdaters.back(); + aUpdaters.pop_back(); + } + } + + if( bUndo ) + EndUndo(); + } + return ret; +} + +static void lcl_LazyDelete(std::vector<rtl::Reference<SdrObject>> & rLazyDelete) +{ + // now delete removed scene objects + while (!rLazyDelete.empty()) + rLazyDelete.pop_back(); +} + +void SdrEditView::DeleteMarkedObj() +{ + // #i110981# return when nothing is to be done at all + if(!GetMarkedObjectCount()) + { + return; + } + + // moved breaking action and undo start outside loop + BrkAction(); + BegUndo(SvxResId(STR_EditDelete),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::Delete); + + std::vector<rtl::Reference<SdrObject>> lazyDeleteObjects; + // remove as long as something is selected. This allows to schedule objects for + // removal for a next run as needed + while(GetMarkedObjectCount()) + { + // vector to remember the parents which may be empty after object removal + std::vector< SdrObject* > aParents; + + { + const SdrMarkList& rMarkList = GetMarkedObjectList(); + const size_t nCount(rMarkList.GetMarkCount()); + + for(size_t a = 0; a < nCount; ++a) + { + // in the first run, add all found parents, but only once + SdrMark* pMark(rMarkList.GetMark(a)); + SdrObject* pObject(pMark->GetMarkedSdrObj()); + SdrObject* pParent(pObject->getParentSdrObjectFromSdrObject()); + + if(pParent) + { + if(!aParents.empty()) + { + std::vector< SdrObject* >::iterator aFindResult = + std::find(aParents.begin(), aParents.end(), pParent); + + if(aFindResult == aParents.end()) + { + aParents.push_back(pParent); + } + } + else + { + aParents.push_back(pParent); + } + } + } + + if(!aParents.empty()) + { + // in a 2nd run, remove all objects which may already be scheduled for + // removal. I am not sure if this can happen, but theoretically + // a to-be-removed object may already be the group/3DScene itself + for(size_t a = 0; a < nCount; ++a) + { + SdrMark* pMark = rMarkList.GetMark(a); + SdrObject* pObject = pMark->GetMarkedSdrObj(); + + std::vector< SdrObject* >::iterator aFindResult = + std::find(aParents.begin(), aParents.end(), pObject); + + if(aFindResult != aParents.end()) + { + aParents.erase(aFindResult); + } + } + } + } + + // original stuff: remove selected objects. Handle clear will + // do something only once + auto temp(DeleteMarkedList(GetMarkedObjectList())); + lazyDeleteObjects.insert(lazyDeleteObjects.end(), temp.begin(), temp.end()); + GetMarkedObjectListWriteAccess().Clear(); + maHdlList.Clear(); + + while(!aParents.empty() && !GetMarkedObjectCount()) + { + // iterate over remembered parents + SdrObject* pParent = aParents.back(); + aParents.pop_back(); + + if(pParent->GetSubList() && 0 == pParent->GetSubList()->GetObjCount()) + { + // we detected an empty parent, a candidate to leave group/3DScene + // if entered + if(GetSdrPageView()->GetCurrentGroup() + && GetSdrPageView()->GetCurrentGroup() == pParent) + { + GetSdrPageView()->LeaveOneGroup(); + } + + // schedule empty parent for removal + GetMarkedObjectListWriteAccess().InsertEntry( + SdrMark(pParent, GetSdrPageView())); + } + } + } + + // end undo and change messaging moved at the end + EndUndo(); + MarkListHasChanged(); + + lcl_LazyDelete(lazyDeleteObjects); +} + +void SdrEditView::CopyMarkedObj() +{ + SortMarkedObjects(); + + SdrMarkList aSourceObjectsForCopy(GetMarkedObjectList()); + // The following loop is used instead of MarkList::Merge(), to be + // able to flag the MarkEntries. + const size_t nEdgeCnt = GetEdgesOfMarkedNodes().GetMarkCount(); + for (size_t nEdgeNum=0; nEdgeNum<nEdgeCnt; ++nEdgeNum) { + SdrMark aM(*GetEdgesOfMarkedNodes().GetMark(nEdgeNum)); + aM.SetUser(1); + aSourceObjectsForCopy.InsertEntry(aM); + } + aSourceObjectsForCopy.ForceSort(); + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + CloneList aCloneList; + + const bool bUndo = IsUndoEnabled(); + + GetMarkedObjectListWriteAccess().Clear(); + size_t nCloneErrCnt=0; + std::unordered_set<rtl::OUString> aNameSet; + const size_t nMarkCount=aSourceObjectsForCopy.GetMarkCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) { + SdrMark* pM=aSourceObjectsForCopy.GetMark(nm); + SdrObject* pSource(pM->GetMarkedSdrObj()); + rtl::Reference<SdrObject> pO(pSource->CloneSdrObject(pSource->getSdrModelFromSdrObject())); + if (pO!=nullptr) { + pM->GetPageView()->GetObjList()->InsertObjectThenMakeNameUnique(pO.get(), aNameSet); + + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoCopyObject(*pO)); + + SdrMark aME(*pM); + aME.SetMarkedSdrObj(pO.get()); + aCloneList.AddPair(pM->GetMarkedSdrObj(), pO.get()); + + if (pM->GetUser()==0) + { + // otherwise it is only an Edge we have to copy as well + GetMarkedObjectListWriteAccess().InsertEntry(aME); + } + } else { + nCloneErrCnt++; + } + } + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + aCloneList.CopyConnections(); + + if(nCloneErrCnt) + { +#ifdef DBG_UTIL + OStringBuffer aStr("SdrEditView::CopyMarkedObj(): Error when cloning "); + + if(nCloneErrCnt == 1) + { + aStr.append("a drawing object."); + } + else + { + aStr.append(OString::number(static_cast<sal_Int32>(nCloneErrCnt)) + + " drawing objects."); + } + + aStr.append(" This object's/These objects's connections will not be copied."); + OSL_FAIL(aStr.getStr()); +#endif + } + MarkListHasChanged(); +} + + +bool SdrEditView::InsertObjectAtView(SdrObject* pObj, SdrPageView& rPV, SdrInsertFlags nOptions) +{ + if (nOptions & SdrInsertFlags::SETDEFLAYER) { + SdrLayerID nLayer=rPV.GetPage()->GetLayerAdmin().GetLayerID(maActualLayer); + if (nLayer==SDRLAYER_NOTFOUND) nLayer=SdrLayerID(0); + if (rPV.GetLockedLayers().IsSet(nLayer) || !rPV.GetVisibleLayers().IsSet(nLayer)) { + return false; + } + pObj->NbcSetLayer(nLayer); + } + if (nOptions & SdrInsertFlags::SETDEFATTR) { + if (mpDefaultStyleSheet!=nullptr) pObj->NbcSetStyleSheet(mpDefaultStyleSheet, false); + pObj->SetMergedItemSet(maDefaultAttr); + } + if (!pObj->IsInserted()) { + rPV.GetObjList()->InsertObject(pObj, SAL_MAX_SIZE); + } + + css::uno::Reference<lang::XServiceInfo> xServices(GetModel().getUnoModel(), + css::uno::UNO_QUERY); + if (xServices.is() && (xServices->supportsService("com.sun.star.sheet.SpreadsheetDocument") || + xServices->supportsService("com.sun.star.text.TextDocument"))) + { + const bool bUndo(IsUndoEnabled()); + GetModel().EnableUndo(false); + pObj->MakeNameUnique(); + GetModel().EnableUndo(bUndo); + } + + if( IsUndoEnabled()) + { + bool bDontDeleteReally = true; + EndTextEditCurrentView(bDontDeleteReally); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pObj)); + } + + if (!(nOptions & SdrInsertFlags::DONTMARK)) { + if (!(nOptions & SdrInsertFlags::ADDMARK)) UnmarkAllObj(); + MarkObj(pObj,&rPV); + } + return true; +} + +void SdrEditView::ReplaceObjectAtView(SdrObject* pOldObj, SdrPageView& rPV, SdrObject* pNewObj, bool bMark) +{ + if(IsTextEdit()) + { +#ifdef DBG_UTIL + if(auto pTextObj = DynCastSdrTextObj(pOldObj)) + if (pTextObj->IsTextEditActive()) + OSL_ENSURE(false, "OldObject is in TextEdit mode, this has to be ended before replacing it using SdrEndTextEdit (!)"); + + if(auto pTextObj = DynCastSdrTextObj(pNewObj)) + if (pTextObj->IsTextEditActive()) + OSL_ENSURE(false, "NewObject is in TextEdit mode, this has to be ended before replacing it using SdrEndTextEdit (!)"); +#endif + + // #i123468# emergency repair situation, needs to cast up to a class derived from + // this one; (aw080 has a mechanism for that and the view hierarchy is secured to + // always be a SdrView) + SdrView *pSdrView = dynamic_cast<SdrView*>(this); + if (pSdrView) + pSdrView->SdrEndTextEdit(); + } + + SdrObjList* pOL=pOldObj->getParentSdrObjListFromSdrObject(); + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoReplaceObject(*pOldObj,*pNewObj)); + + if( IsObjMarked( pOldObj ) ) + MarkObj( pOldObj, &rPV, true /*unmark!*/ ); + + pOL->ReplaceObject(pNewObj,pOldObj->GetOrdNum()); + + if (bMark) MarkObj(pNewObj,&rPV); +} + + +bool SdrEditView::IsUndoEnabled() const +{ + return GetModel().IsUndoEnabled(); +} + +void SdrEditView::EndTextEditAllViews() const +{ + GetModel().ForAllListeners( + [](SfxListener* pListener) + { + SdrObjEditView* pView = dynamic_cast<SdrObjEditView*>(pListener); + if (pView && pView->IsTextEdit()) + pView->SdrEndTextEdit(); + return false; + }); +} + +void SdrEditView::EndTextEditCurrentView(bool bDontDeleteReally) +{ + if (IsTextEdit()) + { + SdrView* pSdrView = dynamic_cast<SdrView*>(this); + if (pSdrView) + pSdrView->SdrEndTextEdit(bDontDeleteReally); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdedtv1.cxx b/svx/source/svdraw/svdedtv1.cxx new file mode 100644 index 0000000000..ee8945a821 --- /dev/null +++ b/svx/source/svdraw/svdedtv1.cxx @@ -0,0 +1,2019 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <editeng/eeitem.hxx> +#include <math.h> +#include <svl/itemiter.hxx> +#include <svl/whiter.hxx> +#include <tools/bigint.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <getallcharpropids.hxx> +#include <svx/dialmgr.hxx> +#include <svx/svditer.hxx> +#include <svx/strings.hrc> + +#include <AffineMatrixItem.hxx> +#include <svx/e3dsceneupdater.hxx> +#include <svx/obj3d.hxx> +#include <svx/rectenum.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdooitm.hxx> +#include <svx/sderitm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/svdedtv.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdundo.hxx> +#include <svx/svxids.hrc> +#include <sxallitm.hxx> +#include <sxmovitm.hxx> +#include <sxreaitm.hxx> +#include <sxreoitm.hxx> +#include <sxroaitm.hxx> +#include <sxrooitm.hxx> +#include <sxsalitm.hxx> +#include <sxsoitm.hxx> +#include <sxtraitm.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xflclit.hxx> +#include <svx/xlntrit.hxx> +#include <svx/xfltrit.hxx> +#include <svx/sdprcitm.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <rtl/ustring.hxx> +#include <sfx2/viewsh.hxx> +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> + +// EditView + + +void SdrEditView::SetMarkedObjRect(const tools::Rectangle& rRect) +{ + DBG_ASSERT(!rRect.IsEmpty(),"SetMarkedObjRect() with an empty Rect does not make sense."); + if (rRect.IsEmpty()) return; + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) return; + tools::Rectangle aR0(GetMarkedObjRect()); + DBG_ASSERT(!aR0.IsEmpty(),"SetMarkedObjRect(): GetMarkedObjRect() is empty."); + if (aR0.IsEmpty()) return; + tools::Long x0=aR0.Left(); + tools::Long y0=aR0.Top(); + tools::Long w0=aR0.Right()-x0; + tools::Long h0=aR0.Bottom()-y0; + tools::Long x1=rRect.Left(); + tools::Long y1=rRect.Top(); + tools::Long w1=rRect.Right()-x1; + tools::Long h1=rRect.Bottom()-y1; + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + BegUndo(ImpGetDescriptionString(STR_EditPosSize)); + } + + for (size_t nm=0; nm<nCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + + tools::Rectangle aR1(pO->GetSnapRect()); + if (!aR1.IsEmpty()) + { + if (aR1==aR0) + { + aR1=rRect; + } + else + { // transform aR1 to aR0 after rRect + aR1.Move(-x0,-y0); + BigInt l(aR1.Left()); + BigInt r(aR1.Right()); + BigInt t(aR1.Top()); + BigInt b(aR1.Bottom()); + if (w0!=0) { + l*=w1; l/=w0; + r*=w1; r/=w0; + } else { + l=0; r=w1; + } + if (h0!=0) { + t*=h1; t/=h0; + b*=h1; b/=h0; + } else { + t=0; b=h1; + } + aR1.SetLeft(tools::Long(l) ); + aR1.SetRight(tools::Long(r) ); + aR1.SetTop(tools::Long(t) ); + aR1.SetBottom(tools::Long(b) ); + aR1.Move(x1,y1); + } + pO->SetSnapRect(aR1); + } else { + OSL_FAIL("SetMarkedObjRect(): pObj->GetSnapRect() returns empty Rect"); + } + } + if( bUndo ) + EndUndo(); +} + +std::vector< std::unique_ptr<SdrUndoAction> > SdrEditView::CreateConnectorUndo( const SdrObject& rO ) +{ + std::vector< std::unique_ptr<SdrUndoAction> > vUndoActions; + + if ( rO.GetBroadcaster() ) + { + const SdrPage* pPage = rO.getSdrPageFromSdrObject(); + if ( pPage ) + { + SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups); + while( aIter.IsMore() ) + { + SdrObject* pPartObj = aIter.Next(); + if ( dynamic_cast<const SdrEdgeObj*>( pPartObj) != nullptr ) + { + if ( ( pPartObj->GetConnectedNode( false ) == &rO ) || + ( pPartObj->GetConnectedNode( true ) == &rO ) ) + { + vUndoActions.push_back(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pPartObj)); + } + } + } + } + } + return vUndoActions; +} + +void SdrEditView::AddUndoActions( std::vector< std::unique_ptr<SdrUndoAction> > aUndoActions ) +{ + for (auto & rAction : aUndoActions) + AddUndo( std::move(rAction) ); +} + +void SdrEditView::MoveMarkedObj(const Size& rSiz, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr(SvxResId(STR_EditMove)); + if (bCopy) + aStr += SvxResId(STR_EditWithCopy); + // needs its own UndoGroup because of its parameters + BegUndo(aStr,GetDescriptionOfMarkedObjects(),SdrRepeatFunc::Move); + } + + if (bCopy) + CopyMarkedObj(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if( bUndo ) + { + AddUndoActions( CreateConnectorUndo( *pO ) ); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoMoveObject(*pO,rSiz)); + } + pO->Move(rSiz); + } + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::ResizeMarkedObj(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr {ImpGetDescriptionString(STR_EditResize)}; + if (bCopy) + aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if( bUndo ) + { + AddUndoActions( CreateConnectorUndo( *pO ) ); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + } + pO->Resize(rRef,xFact,yFact); + } + + if( bUndo ) + EndUndo(); +} +void SdrEditView::ResizeMultMarkedObj(const Point& rRef, + const Fraction& xFact, + const Fraction& yFact, + const bool bWdh, + const bool bHgt) +{ + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + BegUndo(ImpGetDescriptionString(STR_EditResize)); + } + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if( bUndo ) + { + AddUndoActions( CreateConnectorUndo( *pO ) ); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + } + + Fraction aFrac(1,1); + if (bWdh && xFact.IsValid() && bHgt && yFact.IsValid()) + pO->Resize(rRef, xFact, yFact); + else if (bWdh && xFact.IsValid()) + pO->Resize(rRef, xFact, aFrac); + else if (bHgt && yFact.IsValid()) + pO->Resize(rRef, aFrac, yFact); + } + if( bUndo ) + EndUndo(); +} + +Degree100 SdrEditView::GetMarkedObjRotate() const +{ + Degree100 nRetval(0); + + if(GetMarkedObjectCount()) + { + SdrMark* pM = GetSdrMarkByIndex(0); + SdrObject* pO = pM->GetMarkedSdrObj(); + + nRetval = pO->GetRotateAngle(); + } + + return nRetval; +} + +void SdrEditView::RotateMarkedObj(const Point& rRef, Degree100 nAngle, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr {ImpGetDescriptionString(STR_EditRotate)}; + if (bCopy) aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + double nSin = sin(toRadians(nAngle)); + double nCos = cos(toRadians(nAngle)); + const size_t nMarkCount(GetMarkedObjectCount()); + + if(nMarkCount) + { + std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters; + + for(size_t nm = 0; nm < nMarkCount; ++nm) + { + SdrMark* pM = GetSdrMarkByIndex(nm); + SdrObject* pO = pM->GetMarkedSdrObj(); + + if( bUndo ) + { + // extra undo actions for changed connector which now may hold its laid out path (SJ) + AddUndoActions( CreateConnectorUndo( *pO ) ); + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + } + + // set up a scene updater if object is a 3d object + if(DynCastE3dObject(pO)) + { + aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pO)); + } + + pO->Rotate(rRef,nAngle,nSin,nCos); + } + + // fire scene updaters + while(!aUpdaters.empty()) + { + delete aUpdaters.back(); + aUpdaters.pop_back(); + } + } + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::MirrorMarkedObj(const Point& rRef1, const Point& rRef2, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr; + Point aDif(rRef2-rRef1); + if (aDif.X()==0) + aStr = ImpGetDescriptionString(STR_EditMirrorHori); + else if (aDif.Y()==0) + aStr = ImpGetDescriptionString(STR_EditMirrorVert); + else if (std::abs(aDif.X()) == std::abs(aDif.Y())) + aStr = ImpGetDescriptionString(STR_EditMirrorDiag); + else + aStr = ImpGetDescriptionString(STR_EditMirrorFree); + if (bCopy) aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + const size_t nMarkCount(GetMarkedObjectCount()); + + if(nMarkCount) + { + std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters; + + for(size_t nm = 0; nm < nMarkCount; ++nm) + { + SdrMark* pM = GetSdrMarkByIndex(nm); + SdrObject* pO = pM->GetMarkedSdrObj(); + + if( bUndo ) + { + // extra undo actions for changed connector which now may hold its laid out path (SJ) + AddUndoActions( CreateConnectorUndo( *pO ) ); + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + } + + // set up a scene updater if object is a 3d object + if(DynCastE3dObject(pO)) + { + aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pO)); + } + + pO->Mirror(rRef1,rRef2); + } + + // fire scene updaters + while(!aUpdaters.empty()) + { + delete aUpdaters.back(); + aUpdaters.pop_back(); + } + } + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::MirrorMarkedObjHorizontal() +{ + Point aCenter(GetMarkedObjRect().Center()); + Point aPt2(aCenter); + aPt2.AdjustY( 1 ); + MirrorMarkedObj(aCenter,aPt2); +} + +void SdrEditView::MirrorMarkedObjVertical() +{ + Point aCenter(GetMarkedObjRect().Center()); + Point aPt2(aCenter); + aPt2.AdjustX( 1 ); + MirrorMarkedObj(aCenter,aPt2); +} + +Degree100 SdrEditView::GetMarkedObjShear() const +{ + bool b1st=true; + bool bOk=true; + Degree100 nAngle(0); + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount && bOk; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + Degree100 nAngle2=pO->GetShearAngle(); + if (b1st) nAngle=nAngle2; + else if (nAngle2!=nAngle) bOk=false; + b1st=false; + } + if (nAngle>SDRMAXSHEAR) nAngle=SDRMAXSHEAR; + if (nAngle<-SDRMAXSHEAR) nAngle=-SDRMAXSHEAR; + if (!bOk) nAngle=0_deg100; + return nAngle; +} + +void SdrEditView::ShearMarkedObj(const Point& rRef, Degree100 nAngle, bool bVShear, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr {ImpGetDescriptionString(STR_EditShear)}; + if (bCopy) + aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + double nTan = tan(toRadians(nAngle)); + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if( bUndo ) + { + AddUndoActions( CreateConnectorUndo( *pO ) ); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + } + pO->Shear(rRef,nAngle,nTan,bVShear); + } + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::ImpCrookObj(SdrObject* pO, const Point& rRef, const Point& rRad, + SdrCrookMode eMode, bool bVertical, bool bNoContortion, bool bRotate, const tools::Rectangle& rMarkRect) +{ + SdrPathObj* pPath=dynamic_cast<SdrPathObj*>( pO ); + bool bDone = false; + + if(pPath!=nullptr && !bNoContortion) + { + XPolyPolygon aXPP(pPath->GetPathPoly()); + switch (eMode) { + case SdrCrookMode::Rotate : CrookRotatePoly (aXPP,rRef,rRad,bVertical); break; + case SdrCrookMode::Slant : CrookSlantPoly (aXPP,rRef,rRad,bVertical); break; + case SdrCrookMode::Stretch: CrookStretchPoly(aXPP,rRef,rRad,bVertical,rMarkRect); break; + } // switch + pPath->SetPathPoly(aXPP.getB2DPolyPolygon()); + bDone = true; + } + + if(!bDone && !pPath && pO->IsPolyObj() && 0 != pO->GetPointCount()) + { + // for PolyObj's, but NOT for SdrPathObj's, e.g. the measurement object + sal_uInt32 nPointCount(pO->GetPointCount()); + XPolygon aXP(static_cast<sal_uInt16>(nPointCount)); + sal_uInt32 nPtNum; + + for(nPtNum = 0; nPtNum < nPointCount; nPtNum++) + { + Point aPt(pO->GetPoint(nPtNum)); + aXP[static_cast<sal_uInt16>(nPtNum)]=aPt; + } + + switch (eMode) + { + case SdrCrookMode::Rotate : CrookRotatePoly (aXP,rRef,rRad,bVertical); break; + case SdrCrookMode::Slant : CrookSlantPoly (aXP,rRef,rRad,bVertical); break; + case SdrCrookMode::Stretch: CrookStretchPoly(aXP,rRef,rRad,bVertical,rMarkRect); break; + } + + for(nPtNum = 0; nPtNum < nPointCount; nPtNum++) + { + // broadcasting could be optimized here, but for the + // current two points of the measurement object, it's fine + pO->SetPoint(aXP[static_cast<sal_uInt16>(nPtNum)],nPtNum); + } + + bDone = true; + } + + if(bDone) + return; + + // for all others or if bNoContortion + Point aCtr0(pO->GetSnapRect().Center()); + Point aCtr1(aCtr0); + bool bRotOk(false); + double nSin(0.0), nCos(1.0); + double nAngle(0.0); + + if(0 != rRad.X() && 0 != rRad.Y()) + { + bRotOk = bRotate; + + switch (eMode) + { + case SdrCrookMode::Rotate : nAngle=CrookRotateXPoint (aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical); bRotOk=bRotate; break; + case SdrCrookMode::Slant : nAngle=CrookSlantXPoint (aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical); break; + case SdrCrookMode::Stretch: nAngle=CrookStretchXPoint(aCtr1,nullptr,nullptr,rRef,rRad,nSin,nCos,bVertical,rMarkRect); break; + } + } + + aCtr1 -= aCtr0; + + if(bRotOk) + pO->Rotate(aCtr0, Degree100(FRound(basegfx::rad2deg<100>(nAngle))), nSin, nCos); + + pO->Move(Size(aCtr1.X(),aCtr1.Y())); +} + +void SdrEditView::CrookMarkedObj(const Point& rRef, const Point& rRad, SdrCrookMode eMode, + bool bVertical, bool bNoContortion, bool bCopy) +{ + tools::Rectangle aMarkRect(GetMarkedObjRect()); + const bool bUndo = IsUndoEnabled(); + + bool bRotate=bNoContortion && eMode==SdrCrookMode::Rotate && IsRotateAllowed(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr {ImpGetDescriptionString(bNoContortion ? STR_EditCrook : STR_EditCrookContortion)}; + if (bCopy) + aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + + const SdrObjList* pOL=pO->GetSubList(); + if (bNoContortion || pOL==nullptr) { + ImpCrookObj(pO,rRef,rRad,eMode,bVertical,bNoContortion,bRotate,aMarkRect); + } else { + SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups); + while (aIter.IsMore()) { + SdrObject* pO1=aIter.Next(); + ImpCrookObj(pO1,rRef,rRad,eMode,bVertical,bNoContortion,bRotate,aMarkRect); + } + } + } + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::ImpDistortObj(SdrObject* pO, const tools::Rectangle& rRef, const XPolygon& rDistortedRect, bool bNoContortion) +{ + SdrPathObj* pPath = dynamic_cast<SdrPathObj*>( pO ); + + if(!bNoContortion && pPath) + { + XPolyPolygon aXPP(pPath->GetPathPoly()); + aXPP.Distort(rRef, rDistortedRect); + pPath->SetPathPoly(aXPP.getB2DPolyPolygon()); + } + else if(pO->IsPolyObj()) + { + // e. g. for the measurement object + sal_uInt32 nPointCount(pO->GetPointCount()); + XPolygon aXP(static_cast<sal_uInt16>(nPointCount)); + sal_uInt32 nPtNum; + + for(nPtNum = 0; nPtNum < nPointCount; nPtNum++) + { + Point aPt(pO->GetPoint(nPtNum)); + aXP[static_cast<sal_uInt16>(nPtNum)]=aPt; + } + + aXP.Distort(rRef, rDistortedRect); + + for(nPtNum = 0; nPtNum < nPointCount; nPtNum++) + { + // broadcasting could be optimized here, but for the + // current two points of the measurement object it's fine + pO->SetPoint(aXP[static_cast<sal_uInt16>(nPtNum)],nPtNum); + } + } +} + +void SdrEditView::DistortMarkedObj(const tools::Rectangle& rRef, const XPolygon& rDistortedRect, bool bNoContortion, bool bCopy) +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr {ImpGetDescriptionString(STR_EditDistort)}; + if (bCopy) + aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr); + } + + if (bCopy) + CopyMarkedObj(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pO)); + + tools::Rectangle aRefRect(rRef); + const SdrObjList* pOL=pO->GetSubList(); + if (bNoContortion || pOL==nullptr) { + ImpDistortObj(pO,aRefRect,rDistortedRect,bNoContortion); + } else { + SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups); + while (aIter.IsMore()) { + SdrObject* pO1=aIter.Next(); + ImpDistortObj(pO1,aRefRect,rDistortedRect,bNoContortion); + } + } + } + if( bUndo ) + EndUndo(); +} + + +void SdrEditView::SetNotPersistAttrToMarked(const SfxItemSet& rAttr) +{ + // bReplaceAll has no effect here + tools::Rectangle aAllSnapRect(GetMarkedObjRect()); + if (const SdrTransformRef1XItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1X)) + { + tools::Long n = pPoolItem->GetValue(); + SetRef1(Point(n,GetRef1().Y())); + } + if (const SdrTransformRef1YItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1Y)) + { + tools::Long n = pPoolItem->GetValue(); + SetRef1(Point(GetRef1().X(),n)); + } + if (const SdrTransformRef2XItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF2X)) + { + tools::Long n = pPoolItem->GetValue(); + SetRef2(Point(n,GetRef2().Y())); + } + if (const SdrTransformRef2YItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF2Y)) + { + tools::Long n = pPoolItem->GetValue(); + SetRef2(Point(GetRef2().X(),n)); + } + tools::Long nAllPosX=0; bool bAllPosX=false; + tools::Long nAllPosY=0; bool bAllPosY=false; + tools::Long nAllWdt=0; bool bAllWdt=false; + tools::Long nAllHgt=0; bool bAllHgt=false; + bool bDoIt=false; + if (const SdrAllPositionXItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLPOSITIONX)) + { + nAllPosX = pPoolItem->GetValue(); + bAllPosX=true; bDoIt=true; + } + if (const SdrAllPositionYItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLPOSITIONY)) + { + nAllPosY = pPoolItem->GetValue(); + bAllPosY=true; bDoIt=true; + } + if (const SdrAllSizeWidthItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLSIZEWIDTH)) + { + nAllWdt = pPoolItem->GetValue(); + bAllWdt=true; bDoIt=true; + } + if (const SdrAllSizeHeightItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ALLSIZEHEIGHT)) + { + nAllHgt = pPoolItem->GetValue(); + bAllHgt=true; bDoIt=true; + } + if (bDoIt) { + tools::Rectangle aRect(aAllSnapRect); // TODO: change this for PolyPt's and GluePt's!!! + if (bAllPosX) aRect.Move(nAllPosX-aRect.Left(),0); + if (bAllPosY) aRect.Move(0,nAllPosY-aRect.Top()); + if (bAllWdt) aRect.SetRight(aAllSnapRect.Left()+nAllWdt ); + if (bAllHgt) aRect.SetBottom(aAllSnapRect.Top()+nAllHgt ); + SetMarkedObjRect(aRect); + } + if (const SdrResizeXAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEXALL)) + { + Fraction aXFact = pPoolItem->GetValue(); + ResizeMarkedObj(aAllSnapRect.TopLeft(),aXFact,Fraction(1,1)); + } + if (const SdrResizeYAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEYALL)) + { + Fraction aYFact = pPoolItem->GetValue(); + ResizeMarkedObj(aAllSnapRect.TopLeft(),Fraction(1,1),aYFact); + } + if (const SdrRotateAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ROTATEALL)) + { + Degree100 nAngle = pPoolItem->GetValue(); + RotateMarkedObj(aAllSnapRect.Center(),nAngle); + } + if (const SdrHorzShearAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_HORZSHEARALL)) + { + Degree100 nAngle = pPoolItem->GetValue(); + ShearMarkedObj(aAllSnapRect.Center(),nAngle); + } + if (const SdrVertShearAllItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_VERTSHEARALL)) + { + Degree100 nAngle = pPoolItem->GetValue(); + ShearMarkedObj(aAllSnapRect.Center(),nAngle,true); + } + + const bool bUndo = IsUndoEnabled(); + + // TODO: check if WhichRange is necessary. + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + const SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + pObj->ApplyNotPersistAttr(rAttr); + } +} + +void SdrEditView::MergeNotPersistAttrFromMarked(SfxItemSet& rAttr) const +{ + // TODO: Take into account the origin and PvPos. + tools::Rectangle aAllSnapRect(GetMarkedObjRect()); // TODO: change this for PolyPt's and GluePt's!!! + tools::Long nAllSnapPosX=aAllSnapRect.Left(); + tools::Long nAllSnapPosY=aAllSnapRect.Top(); + tools::Long nAllSnapWdt=aAllSnapRect.GetWidth()-1; + tools::Long nAllSnapHgt=aAllSnapRect.GetHeight()-1; + // TODO: could go into CheckPossibilities + bool bMovProtect = false, bMovProtectDC = false; + bool bSizProtect = false, bSizProtectDC = false; + bool bPrintable = true, bPrintableDC = false; + bool bVisible = true, bVisibleDC = false; + SdrLayerID nLayerId(0); + bool bLayerDC=false; + tools::Long nSnapPosX=0; bool bSnapPosXDC=false; + tools::Long nSnapPosY=0; bool bSnapPosYDC=false; + tools::Long nSnapWdt=0; bool bSnapWdtDC=false; + tools::Long nSnapHgt=0; bool bSnapHgtDC=false; + tools::Long nLogicWdt=0; bool bLogicWdtDC=false,bLogicWdtDiff=false; + tools::Long nLogicHgt=0; bool bLogicHgtDC=false,bLogicHgtDiff=false; + Degree100 nRotAngle(0); bool bRotAngleDC=false; + Degree100 nShrAngle(0); bool bShrAngleDC=false; + tools::Rectangle aSnapRect; + tools::Rectangle aLogicRect; + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) { + const SdrMark* pM=GetSdrMarkByIndex(nm); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + if (nm==0) { + nLayerId=pObj->GetLayer(); + bMovProtect=pObj->IsMoveProtect(); + bSizProtect=pObj->IsResizeProtect(); + bPrintable =pObj->IsPrintable(); + bVisible = pObj->IsVisible(); + tools::Rectangle aSnapRect2(pObj->GetSnapRect()); + tools::Rectangle aLogicRect2(pObj->GetLogicRect()); + nSnapPosX=aSnapRect2.Left(); + nSnapPosY=aSnapRect2.Top(); + nSnapWdt=aSnapRect2.GetWidth()-1; + nSnapHgt=aSnapRect2.GetHeight()-1; + nLogicWdt=aLogicRect2.GetWidth()-1; + nLogicHgt=aLogicRect2.GetHeight()-1; + bLogicWdtDiff=nLogicWdt!=nSnapWdt; + bLogicHgtDiff=nLogicHgt!=nSnapHgt; + nRotAngle=pObj->GetRotateAngle(); + nShrAngle=pObj->GetShearAngle(); + } else { + if (!bLayerDC && nLayerId !=pObj->GetLayer()) bLayerDC = true; + if (!bMovProtectDC && bMovProtect!=pObj->IsMoveProtect()) bMovProtectDC = true; + if (!bSizProtectDC && bSizProtect!=pObj->IsResizeProtect()) bSizProtectDC = true; + if (!bPrintableDC && bPrintable !=pObj->IsPrintable()) bPrintableDC = true; + if (!bVisibleDC && bVisible !=pObj->IsVisible()) bVisibleDC=true; + if (!bRotAngleDC && nRotAngle !=pObj->GetRotateAngle()) bRotAngleDC=true; + if (!bShrAngleDC && nShrAngle !=pObj->GetShearAngle()) bShrAngleDC=true; + if (!bSnapWdtDC || !bSnapHgtDC || !bSnapPosXDC || !bSnapPosYDC || !bLogicWdtDiff || !bLogicHgtDiff) { + aSnapRect=pObj->GetSnapRect(); + if (nSnapPosX!=aSnapRect.Left()) bSnapPosXDC=true; + if (nSnapPosY!=aSnapRect.Top()) bSnapPosYDC=true; + if (nSnapWdt!=aSnapRect.GetWidth()-1) bSnapWdtDC=true; + if (nSnapHgt!=aSnapRect.GetHeight()-1) bSnapHgtDC=true; + } + if (!bLogicWdtDC || !bLogicHgtDC || !bLogicWdtDiff || !bLogicHgtDiff) { + aLogicRect=pObj->GetLogicRect(); + if (nLogicWdt!=aLogicRect.GetWidth()-1) bLogicWdtDC=true; + if (nLogicHgt!=aLogicRect.GetHeight()-1) bLogicHgtDC=true; + if (!bLogicWdtDiff && aSnapRect.GetWidth()!=aLogicRect.GetWidth()) bLogicWdtDiff=true; + if (!bLogicHgtDiff && aSnapRect.GetHeight()!=aLogicRect.GetHeight()) bLogicHgtDiff=true; + } + } + } + + if (bSnapPosXDC || nAllSnapPosX!=nSnapPosX) rAttr.Put(SdrAllPositionXItem(nAllSnapPosX)); + if (bSnapPosYDC || nAllSnapPosY!=nSnapPosY) rAttr.Put(SdrAllPositionYItem(nAllSnapPosY)); + if (bSnapWdtDC || nAllSnapWdt !=nSnapWdt ) rAttr.Put(SdrAllSizeWidthItem(nAllSnapWdt)); + if (bSnapHgtDC || nAllSnapHgt !=nSnapHgt ) rAttr.Put(SdrAllSizeHeightItem(nAllSnapHgt)); + + // items for pure transformations + rAttr.Put(SdrMoveXItem()); + rAttr.Put(SdrMoveYItem()); + rAttr.Put(SdrResizeXOneItem()); + rAttr.Put(SdrResizeYOneItem()); + rAttr.Put(SdrRotateOneItem()); + rAttr.Put(SdrHorzShearOneItem()); + rAttr.Put(SdrVertShearOneItem()); + + if (nMarkCount>1) { + rAttr.Put(SdrResizeXAllItem()); + rAttr.Put(SdrResizeYAllItem()); + rAttr.Put(SdrRotateAllItem()); + rAttr.Put(SdrHorzShearAllItem()); + rAttr.Put(SdrVertShearAllItem()); + } + + if(meDragMode == SdrDragMode::Rotate || meDragMode == SdrDragMode::Mirror) + { + rAttr.Put(SdrTransformRef1XItem(GetRef1().X())); + rAttr.Put(SdrTransformRef1YItem(GetRef1().Y())); + } + + if(meDragMode == SdrDragMode::Mirror) + { + rAttr.Put(SdrTransformRef2XItem(GetRef2().X())); + rAttr.Put(SdrTransformRef2YItem(GetRef2().Y())); + } +} + +SfxItemSet SdrEditView::GetAttrFromMarked(bool bOnlyHardAttr) const +{ + SfxItemSet aSet(GetModel().GetItemPool()); + MergeAttrFromMarked(aSet,bOnlyHardAttr); + //the EE_FEATURE items should not be set with SetAttrToMarked (see error message there) + //so we do not set them here + // #i32448# + // Do not disable, but clear the items. + aSet.ClearItem(EE_FEATURE_TAB); + aSet.ClearItem(EE_FEATURE_LINEBR); + aSet.ClearItem(EE_FEATURE_NOTCONV); + aSet.ClearItem(EE_FEATURE_FIELD); + + return aSet; +} + +void SdrEditView::MergeAttrFromMarked(SfxItemSet& rAttr, bool bOnlyHardAttr) const +{ + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t a = 0; a < nMarkCount; ++a) + { + // #80277# merging was done wrong in the prev version + SdrObject *pObj = GetMarkedObjectByIndex(a); + if (!pObj) + { + continue; + } + + const SfxItemSet& rSet = pObj->GetMergedItemSet(); + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich(aIter.FirstWhich()); + + while(nWhich) + { + if(!bOnlyHardAttr) + { + if(SfxItemState::DONTCARE == aIter.GetItemState(false)) + rAttr.InvalidateItem(nWhich); + else + rAttr.MergeValue(rSet.Get(nWhich), true); + } + else if(SfxItemState::SET == aIter.GetItemState(false)) + { + const SfxPoolItem& rItem = rSet.Get(nWhich); + rAttr.MergeValue(rItem, true); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + OUString sPayload; + switch(nWhich) + { + case XATTR_LINECOLOR: + { + const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINECOLOR); + if (pItem) + { + Color aColor = static_cast<const XLineColorItem*>(pItem)->GetColorValue(); + sPayload = OUString::number(static_cast<sal_uInt32>(aColor)); + + sPayload = ".uno:XLineColor=" + sPayload; + } + break; + } + + case XATTR_FILLCOLOR: + { + const SfxPoolItem* pItem = rSet.GetItem(XATTR_FILLCOLOR); + if (pItem) + { + Color aColor = static_cast<const XFillColorItem*>(pItem)->GetColorValue(); + sPayload = OUString::number(static_cast<sal_uInt32>(aColor)); + + sPayload = ".uno:FillColor=" + sPayload; + } + break; + } + + case XATTR_FILLTRANSPARENCE: + { + const SfxPoolItem* pItem = rSet.GetItem(XATTR_FILLTRANSPARENCE); + if (pItem) + { + sal_uInt16 nTransparency = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + sPayload = OUString::number(nTransparency); + + sPayload = ".uno:FillTransparence=" + sPayload; + } + break; + } + + case XATTR_LINETRANSPARENCE: + { + const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINETRANSPARENCE); + if (pItem) + { + sal_uInt16 nTransparency = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + sPayload = OUString::number(nTransparency); + + sPayload = ".uno:LineTransparence=" + sPayload; + } + break; + } + + case XATTR_LINEWIDTH: + { + const SfxPoolItem* pItem = rSet.GetItem(XATTR_LINEWIDTH); + if (pItem) + { + sal_uInt32 nWidth = static_cast<const XLineWidthItem*>(pItem)->GetValue(); + sPayload = OUString::number(nWidth); + + sPayload = ".uno:LineWidth=" + sPayload; + } + break; + } + + case SDRATTR_SHADOWTRANSPARENCE: + { + const SfxPoolItem* pItem = rSet.GetItem(SDRATTR_SHADOWTRANSPARENCE); + if (pItem) + { + sal_uInt16 nWidth = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + sPayload = OUString::number(nWidth); + + sPayload = ".uno:FillShadowTransparency=" + sPayload; + } + break; + } + } + + if (!sPayload.isEmpty()) + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, + OUStringToOString(sPayload, RTL_TEXTENCODING_ASCII_US)); + } + + nWhich = aIter.NextWhich(); + } + } +} + +std::vector<sal_uInt16> GetAllCharPropIds(const SfxItemSet& rSet) +{ + std::vector<sal_uInt16> aCharWhichIds; + { + SfxItemIter aIter(rSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if (!IsInvalidItem(pItem)) + { + sal_uInt16 nWhich = pItem->Which(); + if (nWhich>=EE_CHAR_START && nWhich<=EE_CHAR_END) + aCharWhichIds.push_back( nWhich ); + } + } + } + return aCharWhichIds; +} + +std::vector<sal_uInt16> GetAllCharPropIds(std::span< const SfxPoolItem* const > aChangedItems) +{ + std::vector<sal_uInt16> aCharWhichIds; + for (const SfxPoolItem* pItem : aChangedItems) + { + sal_uInt16 nWhich = pItem->Which(); + if (nWhich>=EE_CHAR_START && nWhich<=EE_CHAR_END) + aCharWhichIds.push_back( nWhich ); + } + return aCharWhichIds; +} + +void SdrEditView::SetAttrToMarked(const SfxItemSet& rAttr, bool bReplaceAll) +{ + if (!AreObjectsMarked()) + return; + +#ifdef DBG_UTIL + { + bool bHasEEFeatureItems=false; + SfxItemIter aIter(rAttr); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem; + pItem = aIter.NextItem()) + { + if (!IsInvalidItem(pItem)) { + sal_uInt16 nW=pItem->Which(); + if (nW>=EE_FEATURE_START && nW<=EE_FEATURE_END) bHasEEFeatureItems=true; + } + } + if(bHasEEFeatureItems) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + "SdrEditView::SetAttrToMarked(): Setting EE_FEATURE items at the SdrView does not make sense! It only leads to overhead and unreadable documents.")); + xInfoBox->run(); + } + } +#endif + + // #103836# if the user sets character attributes to the complete shape, + // we want to remove all hard set character attributes with same + // which ids from the text. We do that later but here we remember + // all character attribute which id's that are set. + std::vector<sal_uInt16> aCharWhichIds(GetAllCharPropIds(rAttr)); + + // To make Undo reconstruct text attributes correctly after Format.Standard + bool bHasEEItems=SearchOutlinerItems(rAttr,bReplaceAll); + + // save additional geometry information when paragraph or character attributes + // are changed and the geometrical shape of the text object might be changed + bool bPossibleGeomChange(false); + SfxWhichIter aIter(rAttr); + sal_uInt16 nWhich = aIter.FirstWhich(); + while(!bPossibleGeomChange && nWhich) + { + SfxItemState eState = aIter.GetItemState(); + if(eState == SfxItemState::SET) + { + if((nWhich >= SDRATTR_TEXT_MINFRAMEHEIGHT && nWhich <= SDRATTR_TEXT_CONTOURFRAME) + || nWhich == SDRATTR_3DOBJ_PERCENT_DIAGONAL + || nWhich == SDRATTR_3DOBJ_BACKSCALE + || nWhich == SDRATTR_3DOBJ_DEPTH + || nWhich == SDRATTR_3DOBJ_END_ANGLE + || nWhich == SDRATTR_3DSCENE_DISTANCE) + { + bPossibleGeomChange = true; + } + } + nWhich = aIter.NextWhich(); + } + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + BegUndo(ImpGetDescriptionString(STR_EditSetAttributes)); + } + + const size_t nMarkCount(GetMarkedObjectCount()); + std::vector< E3DModifySceneSnapRectUpdater* > aUpdaters; + + // create ItemSet without SfxItemState::DONTCARE. Put() + // uses its second parameter (bInvalidAsDefault) to + // remove all such items to set them to default. + SfxItemSet aAttr(*rAttr.GetPool(), rAttr.GetRanges()); + aAttr.Put(rAttr); + + // #i38135# + bool bResetAnimationTimer(false); + + const bool bLineStartWidthExplicitChange(SfxItemState::SET + == aAttr.GetItemState(XATTR_LINESTARTWIDTH)); + const bool bLineEndWidthExplicitChange(SfxItemState::SET + == aAttr.GetItemState(XATTR_LINEENDWIDTH)); + // check if LineWidth is part of the change + const bool bAdaptStartEndWidths(!(bLineStartWidthExplicitChange && bLineEndWidthExplicitChange) + && SfxItemState::SET == aAttr.GetItemState(XATTR_LINEWIDTH)); + sal_Int32 nNewLineWidth(0); + + if(bAdaptStartEndWidths) + { + nNewLineWidth = aAttr.Get(XATTR_LINEWIDTH).GetValue(); + } + + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj = pM->GetMarkedSdrObj(); + + if( bUndo ) + { + SdrEdgeObj* pEdgeObj = dynamic_cast< SdrEdgeObj* >( pObj ); + if ( pEdgeObj ) + bPossibleGeomChange = true; + else + AddUndoActions( CreateConnectorUndo( *pObj ) ); + } + + // new geometry undo + if(bPossibleGeomChange && bUndo) + { + // save position and size of object, too + AddUndo( GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + } + + if( bUndo ) + { + // #i8508# + // If this is a text object also rescue the OutlinerParaObject since + // applying attributes to the object may change text layout when + // multiple portions exist with multiple formats. If an OutlinerParaObject + // really exists and needs to be rescued is evaluated in the undo + // implementation itself. + const bool bRescueText = DynCastSdrTextObj(pObj) != nullptr; + + // add attribute undo + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pObj,false,bHasEEItems || bPossibleGeomChange || bRescueText)); + } + + // set up a scene updater if object is a 3d object + if(DynCastE3dObject(pObj)) + { + aUpdaters.push_back(new E3DModifySceneSnapRectUpdater(pObj)); + } + + sal_Int32 nOldLineWidth(0); + if (bAdaptStartEndWidths) + { + nOldLineWidth = pObj->GetMergedItem(XATTR_LINEWIDTH).GetValue(); + } + + // set attributes at object + pObj->SetMergedItemSetAndBroadcast(aAttr, bReplaceAll); + + if(bAdaptStartEndWidths) + { + const SfxItemSet& rSet = pObj->GetMergedItemSet(); + + if(nOldLineWidth != nNewLineWidth) + { + if(SfxItemState::DONTCARE != rSet.GetItemState(XATTR_LINESTARTWIDTH)) + { + const sal_Int32 nValAct(rSet.Get(XATTR_LINESTARTWIDTH).GetValue()); + const sal_Int32 nValNewStart(std::max(sal_Int32(0), nValAct + (((nNewLineWidth - nOldLineWidth) * 15) / 10))); + + pObj->SetMergedItem(XLineStartWidthItem(nValNewStart)); + } + + if(SfxItemState::DONTCARE != rSet.GetItemState(XATTR_LINEENDWIDTH)) + { + const sal_Int32 nValAct(rSet.Get(XATTR_LINEENDWIDTH).GetValue()); + const sal_Int32 nValNewEnd(std::max(sal_Int32(0), nValAct + (((nNewLineWidth - nOldLineWidth) * 15) / 10))); + + pObj->SetMergedItem(XLineEndWidthItem(nValNewEnd)); + } + } + } + + if(auto pTextObj = DynCastSdrTextObj( pObj)) + { + if(!aCharWhichIds.empty()) + { + tools::Rectangle aOldBoundRect = pTextObj->GetLastBoundRect(); + + // #110094#-14 pTextObj->SendRepaintBroadcast(pTextObj->GetBoundRect()); + pTextObj->RemoveOutlinerCharacterAttribs( aCharWhichIds ); + + // object has changed, should be called from + // RemoveOutlinerCharacterAttribs. This will change when the text + // object implementation changes. + pTextObj->SetChanged(); + + pTextObj->BroadcastObjectChange(); + pTextObj->SendUserCall(SdrUserCallType::ChangeAttr, aOldBoundRect); + } + } + + // #i38495# + if(!bResetAnimationTimer) + { + if(pObj->GetViewContact().isAnimatedInAnyViewObjectContact()) + { + bResetAnimationTimer = true; + } + } + } + + // fire scene updaters + while(!aUpdaters.empty()) + { + delete aUpdaters.back(); + aUpdaters.pop_back(); + } + + // #i38135# + if(bResetAnimationTimer) + { + SetAnimationTimer(0); + } + + // better check before what to do: + // pObj->SetAttr() or SetNotPersistAttr() + // TODO: missing implementation! + SetNotPersistAttrToMarked(rAttr); + + if( bUndo ) + EndUndo(); +} + +SfxStyleSheet* SdrEditView::GetStyleSheetFromMarked() const +{ + SfxStyleSheet* pRet=nullptr; + bool b1st=true; + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SfxStyleSheet* pSS=pM->GetMarkedSdrObj()->GetStyleSheet(); + if (b1st) pRet=pSS; + else if (pRet!=pSS) return nullptr; // different stylesheets + b1st=false; + } + return pRet; +} + +void SdrEditView::SetStyleSheetToMarked(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (!AreObjectsMarked()) + return; + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr; + if (pStyleSheet!=nullptr) + aStr = ImpGetDescriptionString(STR_EditSetStylesheet); + else + aStr = ImpGetDescriptionString(STR_EditDelStylesheet); + BegUndo(aStr); + } + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + if( bUndo ) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pM->GetMarkedSdrObj())); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pM->GetMarkedSdrObj(),true,true)); + } + pM->GetMarkedSdrObj()->SetStyleSheet(pStyleSheet,bDontRemoveHardAttr); + } + + if( bUndo ) + EndUndo(); +} + + +void SdrEditView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if(GetMarkedObjectCount()) + { + rTargetSet.Put(GetAttrFromMarked(bOnlyHardAttr), false); + } + else + { + SdrMarkView::GetAttributes(rTargetSet, bOnlyHardAttr); + } +} + +void SdrEditView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + if (GetMarkedObjectCount()!=0) { + SetAttrToMarked(rSet,bReplaceAll); + } else { + SdrMarkView::SetAttributes(rSet,bReplaceAll); + } +} + +SfxStyleSheet* SdrEditView::GetStyleSheet() const +{ + if (GetMarkedObjectCount()!=0) { + return GetStyleSheetFromMarked(); + } else { + return SdrMarkView::GetStyleSheet(); + } +} + +void SdrEditView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (GetMarkedObjectCount()!=0) { + SetStyleSheetToMarked(pStyleSheet,bDontRemoveHardAttr); + } else { + SdrMarkView::SetStyleSheet(pStyleSheet,bDontRemoveHardAttr); + } +} + + +SfxItemSet SdrEditView::GetGeoAttrFromMarked() const +{ + SfxItemSet aRetSet( + GetModel().GetItemPool(), + svl::Items< // SID_ATTR_TRANSFORM_... from s:svxids.hrc + SDRATTR_CORNER_RADIUS, SDRATTR_CORNER_RADIUS, + SID_ATTR_TRANSFORM_POS_X, SID_ATTR_TRANSFORM_ANGLE, + SID_ATTR_TRANSFORM_PROTECT_POS, SID_ATTR_TRANSFORM_AUTOHEIGHT>); + + if (AreObjectsMarked()) + { + SfxItemSet aMarkAttr(GetAttrFromMarked(false)); // because of AutoGrowHeight and corner radius + tools::Rectangle aRect(GetMarkedObjRect()); + + if(GetSdrPageView()) + { + GetSdrPageView()->LogicToPagePos(aRect); + } + + // position + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_POS_X,aRect.Left())); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_POS_Y,aRect.Top())); + + // size + tools::Long nResizeRefX=aRect.Left(); + tools::Long nResizeRefY=aRect.Top(); + if (meDragMode==SdrDragMode::Rotate) { // use rotation axis as a reference for resizing, too + nResizeRefX=maRef1.X(); + nResizeRefY=maRef1.Y(); + } + aRetSet.Put(SfxUInt32Item(SID_ATTR_TRANSFORM_WIDTH,aRect.Right()-aRect.Left())); + aRetSet.Put(SfxUInt32Item(SID_ATTR_TRANSFORM_HEIGHT,aRect.Bottom()-aRect.Top())); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_RESIZE_REF_X,nResizeRefX)); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_RESIZE_REF_Y,nResizeRefY)); + + Point aRotateAxe(maRef1); + + if(GetSdrPageView()) + { + GetSdrPageView()->LogicToPagePos(aRotateAxe); + } + + // rotation + tools::Long nRotateRefX=aRect.Center().X(); + tools::Long nRotateRefY=aRect.Center().Y(); + if (meDragMode==SdrDragMode::Rotate) { + nRotateRefX=aRotateAxe.X(); + nRotateRefY=aRotateAxe.Y(); + } + aRetSet.Put(SdrAngleItem(SID_ATTR_TRANSFORM_ANGLE,GetMarkedObjRotate())); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_ROT_X,nRotateRefX)); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_ROT_Y,nRotateRefY)); + + // shearing + tools::Long nShearRefX=aRect.Left(); + tools::Long nShearRefY=aRect.Bottom(); + if (meDragMode==SdrDragMode::Rotate) { // use rotation axis as a reference for shearing, too + nShearRefX=aRotateAxe.X(); + nShearRefY=aRotateAxe.Y(); + } + aRetSet.Put(SdrAngleItem(SID_ATTR_TRANSFORM_SHEAR,GetMarkedObjShear())); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_SHEAR_X,nShearRefX)); + aRetSet.Put(SfxInt32Item(SID_ATTR_TRANSFORM_SHEAR_Y,nShearRefY)); + + // check every object whether it is protected + const SdrMarkList& rMarkList=GetMarkedObjectList(); + const size_t nMarkCount=rMarkList.GetMarkCount(); + SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj(); + bool bPosProt=pObj->IsMoveProtect(); + bool bSizProt=pObj->IsResizeProtect(); + bool bPosProtDontCare=false; + bool bSizProtDontCare=false; + for (size_t i=1; i<nMarkCount && (!bPosProtDontCare || !bSizProtDontCare); ++i) + { + pObj=rMarkList.GetMark(i)->GetMarkedSdrObj(); + if (bPosProt!=pObj->IsMoveProtect()) bPosProtDontCare=true; + if (bSizProt!=pObj->IsResizeProtect()) bSizProtDontCare=true; + } + + // InvalidateItem sets item to DONT_CARE + if (bPosProtDontCare) { + aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_PROTECT_POS); + } else { + aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_PROTECT_POS,bPosProt)); + } + if (bSizProtDontCare) { + aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_PROTECT_SIZE); + } else { + aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_PROTECT_SIZE,bSizProt)); + } + + SfxItemState eState=aMarkAttr.GetItemState(SDRATTR_TEXT_AUTOGROWWIDTH); + bool bAutoGrow=aMarkAttr.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue(); + if (eState==SfxItemState::DONTCARE) { + aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_AUTOWIDTH); + } else if (eState==SfxItemState::SET) { + aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_AUTOWIDTH,bAutoGrow)); + } + + eState=aMarkAttr.GetItemState(SDRATTR_TEXT_AUTOGROWHEIGHT); + bAutoGrow=aMarkAttr.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + if (eState==SfxItemState::DONTCARE) { + aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_AUTOHEIGHT); + } else if (eState==SfxItemState::SET) { + aRetSet.Put(SfxBoolItem(SID_ATTR_TRANSFORM_AUTOHEIGHT,bAutoGrow)); + } + + eState=aMarkAttr.GetItemState(SDRATTR_CORNER_RADIUS); + tools::Long nRadius=aMarkAttr.Get(SDRATTR_CORNER_RADIUS).GetValue(); + if (eState==SfxItemState::DONTCARE) { + aRetSet.InvalidateItem(SDRATTR_CORNER_RADIUS); + } else if (eState==SfxItemState::SET) { + aRetSet.Put(makeSdrEckenradiusItem(nRadius)); + } + + basegfx::B2DHomMatrix aTransformation; + + if(nMarkCount > 1) + { + // multiple objects, range is collected in aRect + aTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix( + aRect.Left(), aRect.Top(), + aRect.getOpenWidth(), aRect.getOpenHeight()); + } + else + { + // single object, get homogen transformation + basegfx::B2DPolyPolygon aPolyPolygon; + + pObj->TRGetBaseGeometry(aTransformation, aPolyPolygon); + } + + if(aTransformation.isIdentity()) + { + aRetSet.InvalidateItem(SID_ATTR_TRANSFORM_MATRIX); + } + else + { + css::geometry::AffineMatrix2D aAffineMatrix2D; + Point aPageOffset(0, 0); + + if(GetSdrPageView()) + { + aPageOffset = GetSdrPageView()->GetPageOrigin(); + } + + aAffineMatrix2D.m00 = aTransformation.get(0, 0); + aAffineMatrix2D.m01 = aTransformation.get(0, 1); + aAffineMatrix2D.m02 = aTransformation.get(0, 2) - aPageOffset.X(); + aAffineMatrix2D.m10 = aTransformation.get(1, 0); + aAffineMatrix2D.m11 = aTransformation.get(1, 1); + aAffineMatrix2D.m12 = aTransformation.get(1, 2) - aPageOffset.Y(); + + aRetSet.Put(AffineMatrixItem(&aAffineMatrix2D)); + } + } + + return aRetSet; +} + +static Point ImpGetPoint(const tools::Rectangle& rRect, RectPoint eRP) +{ + switch(eRP) { + case RectPoint::LT: return rRect.TopLeft(); + case RectPoint::MT: return rRect.TopCenter(); + case RectPoint::RT: return rRect.TopRight(); + case RectPoint::LM: return rRect.LeftCenter(); + case RectPoint::MM: return rRect.Center(); + case RectPoint::RM: return rRect.RightCenter(); + case RectPoint::LB: return rRect.BottomLeft(); + case RectPoint::MB: return rRect.BottomCenter(); + case RectPoint::RB: return rRect.BottomRight(); + } + return Point(); // Should not happen! +} + +void SdrEditView::SetGeoAttrToMarked(const SfxItemSet& rAttr, bool addPageMargin) +{ + const bool bTiledRendering = comphelper::LibreOfficeKit::isActive(); + + tools::Rectangle aRect(GetMarkedObjRect()); + + if(GetSdrPageView()) + { + if (addPageMargin) + { + SdrPage * pPage = GetSdrPageView()->GetPage(); + Point upperLeft(pPage->GetLeftBorder(), pPage->GetUpperBorder()); + aRect.Move(upperLeft.getX(), upperLeft.getY()); + } + GetSdrPageView()->LogicToPagePos(aRect); + } + + Degree100 nOldRotateAngle=GetMarkedObjRotate(); + Degree100 nOldShearAngle=GetMarkedObjShear(); + const SdrMarkList& rMarkList=GetMarkedObjectList(); + SdrObject* pObj=nullptr; + + RectPoint eSizePoint=RectPoint::MM; + tools::Long nPosDX=0; + tools::Long nPosDY=0; + tools::Long nSizX=0; + tools::Long nSizY=0; + Degree100 nRotateAngle(0); + + bool bModeIsRotate(meDragMode == SdrDragMode::Rotate); + tools::Long nRotateX(0); + tools::Long nRotateY(0); + tools::Long nOldRotateX(0); + tools::Long nOldRotateY(0); + if(bModeIsRotate) + { + Point aRotateAxe(maRef1); + + if(GetSdrPageView()) + { + GetSdrPageView()->LogicToPagePos(aRotateAxe); + } + + nRotateX = nOldRotateX = aRotateAxe.X(); + nRotateY = nOldRotateY = aRotateAxe.Y(); + } + + Degree100 nShearAngle(0); + tools::Long nShearX=0; + tools::Long nShearY=0; + bool bShearVert=false; + + bool bChgPos=false; + bool bChgSiz=false; + bool bChgWdh=false; + bool bChgHgt=false; + bool bRotate=false; + bool bShear =false; + + bool bSetAttr=false; + SfxItemSet aSetAttr(GetModel().GetItemPool()); + + // position + if (const SfxInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_POS_X)) + { + nPosDX = pPoolItem->GetValue() - aRect.Left(); + bChgPos=true; + } + if (const SfxInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_POS_Y)) + { + nPosDY = pPoolItem->GetValue() - aRect.Top(); + bChgPos=true; + } + // size + if (const SfxUInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_WIDTH)) + { + nSizX = pPoolItem->GetValue(); + bChgSiz=true; + bChgWdh=true; + } + if (const SfxUInt32Item *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_HEIGHT)) + { + nSizY = pPoolItem->GetValue(); + bChgSiz=true; + bChgHgt=true; + } + if (bChgSiz) { + if (bTiledRendering && SfxItemState::SET != rAttr.GetItemState(SID_ATTR_TRANSFORM_SIZE_POINT)) + eSizePoint = RectPoint::LT; + else + eSizePoint = static_cast<RectPoint>(rAttr.Get(SID_ATTR_TRANSFORM_SIZE_POINT).GetValue()); + } + + // rotation + if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_DELTA_ANGLE)) + { + nRotateAngle = pPoolItem->GetValue(); + bRotate = (nRotateAngle != 0_deg100); + } + + // rotation + if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ANGLE)) + { + nRotateAngle = pPoolItem->GetValue() - nOldRotateAngle; + bRotate = (nRotateAngle != 0_deg100); + } + + // position rotation point x + if(bRotate || rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ROT_X)) + nRotateX = rAttr.Get(SID_ATTR_TRANSFORM_ROT_X).GetValue(); + + // position rotation point y + if(bRotate || rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_ROT_Y)) + nRotateY = rAttr.Get(SID_ATTR_TRANSFORM_ROT_Y).GetValue(); + + // shearing + if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_SHEAR)) + { + Degree100 nNewShearAngle=pPoolItem->GetValue(); + if (nNewShearAngle>SDRMAXSHEAR) nNewShearAngle=SDRMAXSHEAR; + if (nNewShearAngle<-SDRMAXSHEAR) nNewShearAngle=-SDRMAXSHEAR; + if (nNewShearAngle!=nOldShearAngle) { + bShearVert = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_VERTICAL).GetValue(); + if (bShearVert) { + nShearAngle=nNewShearAngle; + } else { + if (nNewShearAngle!=0_deg100 && nOldShearAngle!=0_deg100) { + // bug fix + double nOld = tan(toRadians(nOldShearAngle)); + double nNew = tan(toRadians(nNewShearAngle)); + nNew-=nOld; + nNew = basegfx::rad2deg<100>(atan(nNew)); + nShearAngle=Degree100(FRound(nNew)); + } else { + nShearAngle=nNewShearAngle-nOldShearAngle; + } + } + bShear=nShearAngle!=0_deg100; + if (bShear) { + nShearX = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_X).GetValue(); + nShearY = rAttr.Get(SID_ATTR_TRANSFORM_SHEAR_Y).GetValue(); + } + } + } + + // AutoGrow + if (const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_AUTOWIDTH)) + { + bool bAutoGrow = pPoolItem->GetValue(); + aSetAttr.Put(makeSdrTextAutoGrowWidthItem(bAutoGrow)); + bSetAttr=true; + } + + if (const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_AUTOHEIGHT)) + { + bool bAutoGrow = pPoolItem->GetValue(); + aSetAttr.Put(makeSdrTextAutoGrowHeightItem(bAutoGrow)); + bSetAttr=true; + } + + // corner radius + if (m_bEdgeRadiusAllowed) + if (const SdrMetricItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_CORNER_RADIUS)) + { + tools::Long nRadius = pPoolItem->GetValue(); + aSetAttr.Put(makeSdrEckenradiusItem(nRadius)); + bSetAttr=true; + } + + ForcePossibilities(); + + BegUndo(SvxResId(STR_EditTransform),GetDescriptionOfMarkedObjects()); + + if (bSetAttr) { + SetAttrToMarked(aSetAttr,false); + } + + // change size and height + if (bChgSiz && (m_bResizeFreeAllowed || m_bResizePropAllowed)) { + Fraction aWdt(nSizX,aRect.Right()-aRect.Left()); + Fraction aHgt(nSizY,aRect.Bottom()-aRect.Top()); + Point aRef(ImpGetPoint(aRect,eSizePoint)); + + if(GetSdrPageView()) + { + GetSdrPageView()->PagePosToLogic(aRef); + } + + ResizeMultMarkedObj(aRef, aWdt, aHgt, bChgWdh, bChgHgt); + } + + // rotate + if (bRotate && (m_bRotateFreeAllowed || m_bRotate90Allowed)) { + Point aRef(nRotateX,nRotateY); + + if(GetSdrPageView()) + { + GetSdrPageView()->PagePosToLogic(aRef); + } + + RotateMarkedObj(aRef,nRotateAngle); + } + + // set rotation point position + if(bModeIsRotate && (nRotateX != nOldRotateX || nRotateY != nOldRotateY)) + { + Point aNewRef1(nRotateX, nRotateY); + + if(GetSdrPageView()) + { + GetSdrPageView()->PagePosToLogic(aNewRef1); + } + + SetRef1(aNewRef1); + } + + // shear + if (bShear && m_bShearAllowed) { + Point aRef(nShearX,nShearY); + + if(GetSdrPageView()) + { + GetSdrPageView()->PagePosToLogic(aRef); + } + + ShearMarkedObj(aRef,nShearAngle,bShearVert); + + // #i74358# + // ShearMarkedObj creates a linear combination of the existing transformation and + // the new shear to apply. If the object is already transformed (e.g. rotated) the + // linear combination will not decompose to the same start values again, but to a + // new combination. Thus it makes no sense to check if the wanted shear is reached + // or not. Taking out. + } + + // change position + if (bChgPos && m_bMoveAllowed) { + MoveMarkedObj(Size(nPosDX,nPosDY)); + } + + const size_t nMarkCount=rMarkList.GetMarkCount(); + // protect position + if(const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_PROTECT_POS)) + { + const bool bProtPos(pPoolItem->GetValue()); + bool bChanged(false); + + for(size_t i = 0; i < nMarkCount; ++i) + { + pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + + if(pObj->IsMoveProtect() != bProtPos) + { + bChanged = true; + pObj->SetMoveProtect(bProtPos); + + if(bProtPos) + { + pObj->SetResizeProtect(true); + } + } + } + + if(bChanged) + { + m_bMoveProtect = bProtPos; + + if(bProtPos) + { + m_bResizeProtect = true; + } + + // #i77187# there is no simple method to get the toolbars updated + // in the application. The App is listening to selection change and i + // will use it here (even if not true). It's acceptable since changing + // this model data is pretty rare and only possible using the F4 dialog + MarkListHasChanged(); + } + } + + if(!m_bMoveProtect) + { + // protect size + if(const SfxBoolItem *pPoolItem = rAttr.GetItemIfSet(SID_ATTR_TRANSFORM_PROTECT_SIZE)) + { + const bool bProtSize(pPoolItem->GetValue()); + bool bChanged(false); + + for(size_t i = 0; i < nMarkCount; ++i) + { + pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + + if(pObj->IsResizeProtect() != bProtSize) + { + bChanged = true; + pObj->SetResizeProtect(bProtSize); + } + } + + if(bChanged) + { + m_bResizeProtect = bProtSize; + + // #i77187# see above + MarkListHasChanged(); + } + } + } + + EndUndo(); +} + + +bool SdrEditView::IsAlignPossible() const +{ // at least two selected objects, at least one of them movable + ForcePossibilities(); + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) return false; // nothing selected! + if (nCount==1) return m_bMoveAllowed; // align single object to page + return m_bOneOrMoreMovable; // otherwise: MarkCount>=2 +} + +void SdrEditView::AlignMarkedObjects(SdrHorAlign eHor, SdrVertAlign eVert) +{ + if (eHor==SdrHorAlign::NONE && eVert==SdrVertAlign::NONE) + return; + + SortMarkedObjects(); + if (!GetMarkedObjectCount()) + return; + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + EndTextEditCurrentView(); + OUString aStr(GetDescriptionOfMarkedObjects()); + if (eHor==SdrHorAlign::NONE) + { + switch (eVert) + { + case SdrVertAlign::Top: + aStr = ImpGetDescriptionString(STR_EditAlignVTop); + break; + case SdrVertAlign::Bottom: + aStr = ImpGetDescriptionString(STR_EditAlignVBottom); + break; + case SdrVertAlign::Center: + aStr = ImpGetDescriptionString(STR_EditAlignVCenter); + break; + default: break; + } + } + else if (eVert==SdrVertAlign::NONE) + { + switch (eHor) + { + case SdrHorAlign::Left: + aStr = ImpGetDescriptionString(STR_EditAlignHLeft); + break; + case SdrHorAlign::Right: + aStr = ImpGetDescriptionString(STR_EditAlignHRight); + break; + case SdrHorAlign::Center: + aStr = ImpGetDescriptionString(STR_EditAlignHCenter); + break; + default: break; + } + } + else if (eHor==SdrHorAlign::Center && eVert==SdrVertAlign::Center) + { + aStr = ImpGetDescriptionString(STR_EditAlignCenter); + } + else + { + aStr = ImpGetDescriptionString(STR_EditAlign); + } + BegUndo(aStr); + } + + tools::Rectangle aBound; + const size_t nMarkCount=GetMarkedObjectCount(); + bool bHasFixed=false; + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + if (!aInfo.bMoveAllowed || pObj->IsMoveProtect()) + { + tools::Rectangle aObjRect(pObj->GetSnapRect()); + aBound.Union(aObjRect); + bHasFixed=true; + } + } + if (!bHasFixed) + { + if (nMarkCount==1) + { // align single object to page + const SdrObject* pObj=GetMarkedObjectByIndex(0); + const SdrPage* pPage=pObj->getSdrPageFromSdrObject(); + const SdrPageGridFrameList* pGFL=pPage->GetGridFrameList(GetSdrPageViewOfMarkedByIndex(0),&(pObj->GetSnapRect())); + const SdrPageGridFrame* pFrame=nullptr; + if (pGFL!=nullptr && pGFL->GetCount()!=0) + { // Writer + pFrame=&((*pGFL)[0]); + } + + if (pFrame!=nullptr) + { // Writer + aBound=pFrame->GetUserArea(); + } + else + { + aBound=tools::Rectangle(pPage->GetLeftBorder(),pPage->GetUpperBorder(), + pPage->GetWidth()-pPage->GetRightBorder(), + pPage->GetHeight()-pPage->GetLowerBorder()); + } + } + else + { + aBound=GetMarkedObjRect(); + } + } + Point aCenter(aBound.Center()); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + if (aInfo.bMoveAllowed && !pObj->IsMoveProtect()) + { + tools::Long nXMov=0; + tools::Long nYMov=0; + tools::Rectangle aObjRect(pObj->GetSnapRect()); + switch (eVert) + { + case SdrVertAlign::Top : nYMov=aBound.Top() -aObjRect.Top() ; break; + case SdrVertAlign::Bottom: nYMov=aBound.Bottom()-aObjRect.Bottom() ; break; + case SdrVertAlign::Center: nYMov=aCenter.Y() -aObjRect.Center().Y(); break; + default: break; + } + switch (eHor) + { + case SdrHorAlign::Left : nXMov=aBound.Left() -aObjRect.Left() ; break; + case SdrHorAlign::Right : nXMov=aBound.Right() -aObjRect.Right() ; break; + case SdrHorAlign::Center: nXMov=aCenter.X() -aObjRect.Center().X(); break; + default: break; + } + if (nXMov!=0 || nYMov!=0) + { + // SdrEdgeObj needs an extra SdrUndoGeoObj since the + // connections may need to be saved + if( bUndo ) + { + if( dynamic_cast<SdrEdgeObj*>(pObj) ) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + } + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoMoveObject(*pObj,Size(nXMov,nYMov))); + } + + pObj->Move(Size(nXMov,nYMov)); + } + } + } + + if( bUndo ) + EndUndo(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdedtv2.cxx b/svx/source/svdraw/svdedtv2.cxx new file mode 100644 index 0000000000..8ef7afa195 --- /dev/null +++ b/svx/source/svdraw/svdedtv2.cxx @@ -0,0 +1,2238 @@ +/* -*- 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/svdedtv.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svditer.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/dialmgr.hxx> +#include <svx/sdooitm.hxx> +#include <svx/sdshitm.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xtextit0.hxx> +#include "svdfmtf.hxx" +#include <svdpdf.hxx> +#include <svx/svdetc.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/eeitem.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svx/strings.hrc> +#include <svx/svdoashp.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <i18nutil/unicode.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <memory> +#include <vector> +#include <vcl/graph.hxx> +#include <svx/svxids.hrc> +#include <dstribut_enum.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +SdrObject* SdrEditView::GetMaxToTopObj(SdrObject* /*pObj*/) const +{ + return nullptr; +} + +SdrObject* SdrEditView::GetMaxToBtmObj(SdrObject* /*pObj*/) const +{ + return nullptr; +} + +void SdrEditView::ObjOrderChanged(SdrObject* /*pObj*/, size_t /*nOldPos*/, size_t /*nNewPos*/) +{ +} + +void SdrEditView::MovMarkedToTop() +{ + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) + return; + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(SvxResId(STR_EditMovToTop),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::MoveToTop); + + SortMarkedObjects(); + for (size_t nm=0; nm<nCount; ++nm) + { // All Ordnums have to be correct! + GetMarkedObjectByIndex(nm)->GetOrdNum(); + } + bool bChg=false; + SdrObjList* pOL0=nullptr; + size_t nNewPos=0; + for (size_t nm=nCount; nm>0;) + { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) + { + nNewPos = pOL->GetObjCount()-1; + pOL0=pOL; + } + const size_t nNowPos = pObj->GetOrdNumDirect(); + const tools::Rectangle& rBR=pObj->GetCurrentBoundRect(); + size_t nCmpPos = nNowPos+1; + SdrObject* pMaxObj=GetMaxToTopObj(pObj); + if (pMaxObj!=nullptr) + { + size_t nMaxPos=pMaxObj->GetOrdNum(); + if (nMaxPos!=0) + nMaxPos--; + if (nNewPos>nMaxPos) + nNewPos=nMaxPos; // neither go faster... + if (nNewPos<nNowPos) + nNewPos=nNowPos; // nor go in the other direction + } + bool bEnd=false; + while (nCmpPos<nNewPos && !bEnd) + { + SdrObject* pCmpObj=pOL->GetObj(nCmpPos); + if (pCmpObj==nullptr) + { + OSL_FAIL("MovMarkedToTop(): Reference object not found."); + bEnd=true; + } + else if (pCmpObj==pMaxObj) + { + nNewPos=nCmpPos; + nNewPos--; + bEnd=true; + } + else if (rBR.Overlaps(pCmpObj->GetCurrentBoundRect())) + { + nNewPos=nCmpPos; + bEnd=true; + } + else + { + nCmpPos++; + } + } + if (nNowPos!=nNewPos) + { + bChg=true; + pOL->SetObjectOrdNum(nNowPos,nNewPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos)); + ObjOrderChanged(pObj,nNowPos,nNewPos); + } + nNewPos--; + } + + if( bUndo ) + EndUndo(); + + if (bChg) + MarkListHasChanged(); +} + +void SdrEditView::MovMarkedToBtm() +{ + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) + return; + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(SvxResId(STR_EditMovToBtm),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::MoveToBottom); + + SortMarkedObjects(); + for (size_t nm=0; nm<nCount; ++nm) + { // All Ordnums have to be correct! + GetMarkedObjectByIndex(nm)->GetOrdNum(); + } + + bool bChg=false; + SdrObjList* pOL0=nullptr; + size_t nNewPos=0; + for (size_t nm=0; nm<nCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) + { + nNewPos=0; + pOL0=pOL; + } + const size_t nNowPos = pObj->GetOrdNumDirect(); + const tools::Rectangle& rBR=pObj->GetCurrentBoundRect(); + size_t nCmpPos = nNowPos; + if (nCmpPos>0) + --nCmpPos; + SdrObject* pMaxObj=GetMaxToBtmObj(pObj); + if (pMaxObj!=nullptr) + { + const size_t nMinPos=pMaxObj->GetOrdNum()+1; + if (nNewPos<nMinPos) + nNewPos=nMinPos; // neither go faster... + if (nNewPos>nNowPos) + nNewPos=nNowPos; // nor go in the other direction + } + bool bEnd=false; + // nNewPos in this case is the "maximum" position + // the object may reach without going faster than the object before + // it (multiple selection). + while (nCmpPos>nNewPos && !bEnd) + { + SdrObject* pCmpObj=pOL->GetObj(nCmpPos); + if (pCmpObj==nullptr) + { + OSL_FAIL("MovMarkedToBtm(): Reference object not found."); + bEnd=true; + } + else if (pCmpObj==pMaxObj) + { + nNewPos=nCmpPos; + nNewPos++; + bEnd=true; + } + else if (rBR.Overlaps(pCmpObj->GetCurrentBoundRect())) + { + nNewPos=nCmpPos; + bEnd=true; + } + else + { + nCmpPos--; + } + } + if (nNowPos!=nNewPos) + { + bChg=true; + pOL->SetObjectOrdNum(nNowPos,nNewPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos)); + ObjOrderChanged(pObj,nNowPos,nNewPos); + } + nNewPos++; + } + + if(bUndo) + EndUndo(); + + if(bChg) + MarkListHasChanged(); +} + +void SdrEditView::PutMarkedToTop() +{ + PutMarkedInFrontOfObj(nullptr); +} + +void SdrEditView::PutMarkedInFrontOfObj(const SdrObject* pRefObj) +{ + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) + return; + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditPutToTop),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::PutToTop); + + SortMarkedObjects(); + + if (pRefObj!=nullptr) + { + // Make "in front of the object" work, even if the + // selected objects are already in front of the other object + const size_t nRefMark=TryToFindMarkedObject(pRefObj); + SdrMark aRefMark; + if (nRefMark!=SAL_MAX_SIZE) + { + aRefMark=*GetSdrMarkByIndex(nRefMark); + GetMarkedObjectListWriteAccess().DeleteMark(nRefMark); + } + PutMarkedToBtm(); + if (nRefMark!=SAL_MAX_SIZE) + { + GetMarkedObjectListWriteAccess().InsertEntry(aRefMark); + SortMarkedObjects(); + } + } + for (size_t nm=0; nm<nCount; ++nm) + { // All Ordnums have to be correct! + GetMarkedObjectByIndex(nm)->GetOrdNum(); + } + bool bChg=false; + SdrObjList* pOL0=nullptr; + size_t nNewPos=0; + for (size_t nm=nCount; nm>0;) + { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (pObj!=pRefObj) + { + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) + { + nNewPos=pOL->GetObjCount()-1; + pOL0=pOL; + } + const size_t nNowPos=pObj->GetOrdNumDirect(); + SdrObject* pMaxObj=GetMaxToTopObj(pObj); + if (pMaxObj!=nullptr) + { + size_t nMaxOrd=pMaxObj->GetOrdNum(); // sadly doesn't work any other way + if (nMaxOrd>0) + nMaxOrd--; + if (nNewPos>nMaxOrd) + nNewPos=nMaxOrd; // neither go faster... + if (nNewPos<nNowPos) + nNewPos=nNowPos; // nor go into the other direction + } + if (pRefObj!=nullptr) + { + if (pRefObj->getParentSdrObjListFromSdrObject()==pObj->getParentSdrObjListFromSdrObject()) + { + const size_t nMaxOrd=pRefObj->GetOrdNum(); // sadly doesn't work any other way + if (nNewPos>nMaxOrd) + nNewPos=nMaxOrd; // neither go faster... + if (nNewPos<nNowPos) + nNewPos=nNowPos; // nor go into the other direction + } + else + { + nNewPos=nNowPos; // different PageView, so don't change + } + } + if (nNowPos!=nNewPos) + { + bChg=true; + pOL->SetObjectOrdNum(nNowPos,nNewPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos)); + ObjOrderChanged(pObj,nNowPos,nNewPos); + } + nNewPos--; + } // if (pObj!=pRefObj) + } // for loop over all selected objects + + if( bUndo ) + EndUndo(); + + if(bChg) + MarkListHasChanged(); +} + +void SdrEditView::PutMarkedToBtm() +{ + PutMarkedBehindObj(nullptr); +} + +void SdrEditView::PutMarkedBehindObj(const SdrObject* pRefObj) +{ + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) + return; + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(SvxResId(STR_EditPutToBtm),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::PutToBottom); + + SortMarkedObjects(); + if (pRefObj!=nullptr) + { + // Make "behind the object" work, even if the + // selected objects are already behind the other object + const size_t nRefMark=TryToFindMarkedObject(pRefObj); + SdrMark aRefMark; + if (nRefMark!=SAL_MAX_SIZE) + { + aRefMark=*GetSdrMarkByIndex(nRefMark); + GetMarkedObjectListWriteAccess().DeleteMark(nRefMark); + } + PutMarkedToTop(); + if (nRefMark!=SAL_MAX_SIZE) + { + GetMarkedObjectListWriteAccess().InsertEntry(aRefMark); + SortMarkedObjects(); + } + } + for (size_t nm=0; nm<nCount; ++nm) { // All Ordnums have to be correct! + GetMarkedObjectByIndex(nm)->GetOrdNum(); + } + bool bChg=false; + SdrObjList* pOL0=nullptr; + size_t nNewPos=0; + for (size_t nm=0; nm<nCount; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (pObj!=pRefObj) { + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) { + nNewPos=0; + pOL0=pOL; + } + const size_t nNowPos=pObj->GetOrdNumDirect(); + SdrObject* pMinObj=GetMaxToBtmObj(pObj); + if (pMinObj!=nullptr) { + const size_t nMinOrd=pMinObj->GetOrdNum()+1; // sadly doesn't work any differently + if (nNewPos<nMinOrd) nNewPos=nMinOrd; // neither go faster... + if (nNewPos>nNowPos) nNewPos=nNowPos; // nor go into the other direction + } + if (pRefObj!=nullptr) { + if (pRefObj->getParentSdrObjListFromSdrObject()==pObj->getParentSdrObjListFromSdrObject()) { + const size_t nMinOrd=pRefObj->GetOrdNum(); // sadly doesn't work any differently + if (nNewPos<nMinOrd) nNewPos=nMinOrd; // neither go faster... + if (nNewPos>nNowPos) nNewPos=nNowPos; // nor go into the other direction + } else { + nNewPos=nNowPos; // different PageView, so don't change + } + } + if (nNowPos!=nNewPos) { + bChg=true; + pOL->SetObjectOrdNum(nNowPos,nNewPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos)); + ObjOrderChanged(pObj,nNowPos,nNewPos); + } + nNewPos++; + } // if (pObj!=pRefObj) + } // for loop over all selected objects + + if(bUndo) + EndUndo(); + + if(bChg) + MarkListHasChanged(); + +} + +void SdrEditView::ReverseOrderOfMarked() +{ + SortMarkedObjects(); + const size_t nMarkCount=GetMarkedObjectCount(); + if (nMarkCount<=0) + return; + + bool bChg=false; + + bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditRevOrder),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::ReverseOrder); + + size_t a=0; + do { + // take into account selection across multiple PageViews + size_t b=a+1; + while (b<nMarkCount && GetSdrPageViewOfMarkedByIndex(b) == GetSdrPageViewOfMarkedByIndex(a)) ++b; + --b; + SdrObjList* pOL=GetSdrPageViewOfMarkedByIndex(a)->GetObjList(); + size_t c=b; + if (a<c) { // make sure OrdNums aren't dirty + GetMarkedObjectByIndex(a)->GetOrdNum(); + } + while (a<c) { + SdrObject* pObj1=GetMarkedObjectByIndex(a); + SdrObject* pObj2=GetMarkedObjectByIndex(c); + const size_t nOrd1=pObj1->GetOrdNumDirect(); + const size_t nOrd2=pObj2->GetOrdNumDirect(); + if( bUndo ) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj1,nOrd1,nOrd2)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj2,nOrd2-1,nOrd1)); + } + pOL->SetObjectOrdNum(nOrd1,nOrd2); + // Obj 2 has moved forward by one position, so now nOrd2-1 + pOL->SetObjectOrdNum(nOrd2-1,nOrd1); + // use Replace instead of SetOrdNum for performance reasons (recalculation of Ordnums) + ++a; + --c; + bChg=true; + } + a=b+1; + } while (a<nMarkCount); + + if(bUndo) + EndUndo(); + + if(bChg) + MarkListHasChanged(); +} + +void SdrEditView::ImpCheckToTopBtmPossible() +{ + const size_t nCount=GetMarkedObjectCount(); + if (nCount==0) + return; + if (nCount==1) + { // special-casing for single selection + SdrObject* pObj=GetMarkedObjectByIndex(0); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + SAL_WARN_IF(!pOL, "svx", "Object somehow has no ObjList"); + size_t nMax = pOL ? pOL->GetObjCount() : 0; + size_t nMin = 0; + const size_t nObjNum=pObj->GetOrdNum(); + SdrObject* pRestrict=GetMaxToTopObj(pObj); + if (pRestrict!=nullptr) { + const size_t nRestrict=pRestrict->GetOrdNum(); + if (nRestrict<nMax) nMax=nRestrict; + } + pRestrict=GetMaxToBtmObj(pObj); + if (pRestrict!=nullptr) { + const size_t nRestrict=pRestrict->GetOrdNum(); + if (nRestrict>nMin) nMin=nRestrict; + } + m_bToTopPossible=nObjNum<nMax-1; + m_bToBtmPossible=nObjNum>nMin; + } else { // multiple selection + SdrObjList* pOL0=nullptr; + size_t nPos0 = 0; + for (size_t nm = 0; !m_bToBtmPossible && nm<nCount; ++nm) { // check 'send to background' + SdrObject* pObj=GetMarkedObjectByIndex(nm); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) { + nPos0 = 0; + pOL0=pOL; + } + const size_t nPos = pObj->GetOrdNum(); + m_bToBtmPossible = nPos && (nPos-1 > nPos0); + nPos0 = nPos; + } + + pOL0=nullptr; + nPos0 = SAL_MAX_SIZE; + for (size_t nm=nCount; !m_bToTopPossible && nm>0; ) { // check 'bring to front' + --nm; + SdrObject* pObj=GetMarkedObjectByIndex(nm); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) { + nPos0=pOL->GetObjCount(); + pOL0=pOL; + } + const size_t nPos = pObj->GetOrdNum(); + m_bToTopPossible = nPos+1 < nPos0; + nPos0=nPos; + } + } +} + + +// Combine + + +void SdrEditView::ImpCopyAttributes(const SdrObject* pSource, SdrObject* pDest) const +{ + if (pSource!=nullptr) { + SdrObjList* pOL=pSource->GetSubList(); + if (pOL!=nullptr && !pSource->Is3DObj()) { // get first non-group object from group + SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups); + pSource=aIter.Next(); + } + } + + if(!(pSource && pDest)) + return; + + SfxItemSetFixed<SDRATTR_START, SDRATTR_NOTPERSIST_FIRST-1, + SDRATTR_NOTPERSIST_LAST+1, SDRATTR_END, + EE_ITEMS_START, EE_ITEMS_END> aSet(GetModel().GetItemPool()); + + aSet.Put(pSource->GetMergedItemSet()); + + pDest->ClearMergedItem(); + pDest->SetMergedItemSet(aSet); + + pDest->NbcSetLayer(pSource->GetLayer()); + pDest->NbcSetStyleSheet(pSource->GetStyleSheet(), true); +} + +bool SdrEditView::ImpCanConvertForCombine1(const SdrObject* pObj) +{ + // new condition IsLine() to be able to combine simple Lines + bool bIsLine(false); + + const SdrPathObj* pPath = dynamic_cast< const SdrPathObj*>( pObj ); + + if(pPath) + { + bIsLine = pPath->IsLine(); + } + + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + + return (aInfo.bCanConvToPath || aInfo.bCanConvToPoly || bIsLine); +} + +bool SdrEditView::ImpCanConvertForCombine(const SdrObject* pObj) +{ + SdrObjList* pOL = pObj->GetSubList(); + + if(pOL && !pObj->Is3DObj()) + { + SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups); + + while(aIter.IsMore()) + { + SdrObject* pObj1 = aIter.Next(); + + // all members of a group have to be convertible + if(!ImpCanConvertForCombine1(pObj1)) + { + return false; + } + } + } + else + { + if(!ImpCanConvertForCombine1(pObj)) + { + return false; + } + } + + return true; +} + +basegfx::B2DPolyPolygon SdrEditView::ImpGetPolyPolygon1(const SdrObject* pObj) +{ + basegfx::B2DPolyPolygon aRetval; + const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>( pObj ); + + if(pPath && !pObj->GetOutlinerParaObject()) + { + aRetval = pPath->GetPathPoly(); + } + else + { + rtl::Reference<SdrObject> pConvObj = pObj->ConvertToPolyObj(true/*bCombine*/, false); + + if(pConvObj) + { + SdrObjList* pOL = pConvObj->GetSubList(); + + if(pOL) + { + SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups); + + while(aIter.IsMore()) + { + SdrObject* pObj1 = aIter.Next(); + pPath = dynamic_cast<SdrPathObj*>( pObj1 ); + + if(pPath) + { + aRetval.append(pPath->GetPathPoly()); + } + } + } + else + { + pPath = dynamic_cast<SdrPathObj*>( pConvObj.get() ); + + if(pPath) + { + aRetval = pPath->GetPathPoly(); + } + } + } + } + + return aRetval; +} + +basegfx::B2DPolyPolygon SdrEditView::ImpGetPolyPolygon(const SdrObject* pObj) +{ + SdrObjList* pOL = pObj->GetSubList(); + + if(pOL && !pObj->Is3DObj()) + { + basegfx::B2DPolyPolygon aRetval; + SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups); + + while(aIter.IsMore()) + { + SdrObject* pObj1 = aIter.Next(); + aRetval.append(ImpGetPolyPolygon1(pObj1)); + } + + return aRetval; + } + else + { + return ImpGetPolyPolygon1(pObj); + } +} + +basegfx::B2DPolygon SdrEditView::ImpCombineToSinglePolygon(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + const sal_uInt32 nPolyCount(rPolyPolygon.count()); + + if(0 == nPolyCount) + { + return basegfx::B2DPolygon(); + } + else if(1 == nPolyCount) + { + return rPolyPolygon.getB2DPolygon(0); + } + else + { + basegfx::B2DPolygon aRetval(rPolyPolygon.getB2DPolygon(0)); + + for(sal_uInt32 a(1); a < nPolyCount; a++) + { + basegfx::B2DPolygon aCandidate(rPolyPolygon.getB2DPolygon(a)); + + if(aRetval.count()) + { + if(aCandidate.count()) + { + const basegfx::B2DPoint aCA(aCandidate.getB2DPoint(0)); + const basegfx::B2DPoint aCB(aCandidate.getB2DPoint(aCandidate.count() - 1)); + const basegfx::B2DPoint aRA(aRetval.getB2DPoint(0)); + const basegfx::B2DPoint aRB(aRetval.getB2DPoint(aRetval.count() - 1)); + + const double fRACA(basegfx::B2DVector(aCA - aRA).getLength()); + const double fRACB(basegfx::B2DVector(aCB - aRA).getLength()); + const double fRBCA(basegfx::B2DVector(aCA - aRB).getLength()); + const double fRBCB(basegfx::B2DVector(aCB - aRB).getLength()); + + const double fSmallestRA(std::min(fRACA, fRACB)); + const double fSmallestRB(std::min(fRBCA, fRBCB)); + + if(fSmallestRA < fSmallestRB) + { + // flip result + aRetval.flip(); + } + + const double fSmallestCA(std::min(fRACA, fRBCA)); + const double fSmallestCB(std::min(fRACB, fRBCB)); + + if(fSmallestCB < fSmallestCA) + { + // flip candidate + aCandidate.flip(); + } + + // append candidate to retval + aRetval.append(aCandidate); + } + } + else + { + aRetval = aCandidate; + } + } + + return aRetval; + } +} + +namespace { + +// for distribution dialog function +struct ImpDistributeEntry +{ + SdrObject* mpObj; + sal_Int32 mnPos; + sal_Int32 mnLength; +}; + +} + +typedef std::vector<ImpDistributeEntry> ImpDistributeEntryList; + +void SdrEditView::DistributeMarkedObjects(sal_uInt16 SlotID) +{ + const size_t nMark(GetMarkedObjectCount()); + + if(nMark <= 2) + return; + + SvxDistributeHorizontal eHor = SvxDistributeHorizontal::NONE; + SvxDistributeVertical eVer = SvxDistributeVertical::NONE; + + switch (SlotID) + { + case SID_DISTRIBUTE_HLEFT: eHor = SvxDistributeHorizontal::Left; break; + case SID_DISTRIBUTE_HCENTER: eHor = SvxDistributeHorizontal::Center; break; + case SID_DISTRIBUTE_HDISTANCE: eHor = SvxDistributeHorizontal::Distance; break; + case SID_DISTRIBUTE_HRIGHT: eHor = SvxDistributeHorizontal::Right; break; + case SID_DISTRIBUTE_VTOP: eVer = SvxDistributeVertical::Top; break; + case SID_DISTRIBUTE_VCENTER: eVer = SvxDistributeVertical::Center; break; + case SID_DISTRIBUTE_VDISTANCE: eVer = SvxDistributeVertical::Distance; break; + case SID_DISTRIBUTE_VBOTTOM: eVer = SvxDistributeVertical::Bottom; break; + } + + ImpDistributeEntryList aEntryList; + ImpDistributeEntryList::iterator itEntryList; + sal_uInt32 nFullLength; + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(); + + if(eHor != SvxDistributeHorizontal::NONE) + { + // build sorted entry list + nFullLength = 0; + + for( size_t a = 0; a < nMark; ++a ) + { + SdrMark* pMark = GetSdrMarkByIndex(a); + ImpDistributeEntry aNew; + + aNew.mpObj = pMark->GetMarkedSdrObj(); + + switch(eHor) + { + case SvxDistributeHorizontal::Left: + { + aNew.mnPos = aNew.mpObj->GetSnapRect().Left(); + break; + } + case SvxDistributeHorizontal::Center: + { + aNew.mnPos = (aNew.mpObj->GetSnapRect().Right() + aNew.mpObj->GetSnapRect().Left()) / 2; + break; + } + case SvxDistributeHorizontal::Distance: + { + aNew.mnLength = aNew.mpObj->GetSnapRect().GetWidth() + 1; + nFullLength += aNew.mnLength; + aNew.mnPos = (aNew.mpObj->GetSnapRect().Right() + aNew.mpObj->GetSnapRect().Left()) / 2; + break; + } + case SvxDistributeHorizontal::Right: + { + aNew.mnPos = aNew.mpObj->GetSnapRect().Right(); + break; + } + default: break; + } + + itEntryList = std::find_if(aEntryList.begin(), aEntryList.end(), + [&aNew](const ImpDistributeEntry& rEntry) { return rEntry.mnPos >= aNew.mnPos; }); + if ( itEntryList < aEntryList.end() ) + aEntryList.insert( itEntryList, aNew ); + else + aEntryList.push_back( aNew ); + } + + if(eHor == SvxDistributeHorizontal::Distance) + { + // calculate room in-between + sal_Int32 nWidth = GetAllMarkedBoundRect().GetWidth() + 1; + double fStepWidth = (static_cast<double>(nWidth) - static_cast<double>(nFullLength)) / static_cast<double>(aEntryList.size() - 1); + double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos); + fStepStart += fStepWidth + static_cast<double>((aEntryList[ 0 ].mnLength + aEntryList[ 1 ].mnLength) / 2); + + // move entries 1..n-1 + for( size_t i = 1, n = aEntryList.size()-1; i < n; ++i ) + { + ImpDistributeEntry& rCurr = aEntryList[ i ]; + ImpDistributeEntry& rNext = aEntryList[ i + 1]; + sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos; + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj)); + rCurr.mpObj->Move(Size(nDelta, 0)); + fStepStart += fStepWidth + static_cast<double>((rCurr.mnLength + rNext.mnLength) / 2); + } + } + else + { + // calculate distances + sal_Int32 nWidth = aEntryList[ aEntryList.size() - 1 ].mnPos - aEntryList[ 0 ].mnPos; + double fStepWidth = static_cast<double>(nWidth) / static_cast<double>(aEntryList.size() - 1); + double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos); + fStepStart += fStepWidth; + + // move entries 1..n-1 + for( size_t i = 1 ; i < aEntryList.size()-1 ; ++i ) + { + ImpDistributeEntry& rCurr = aEntryList[ i ]; + sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos; + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj)); + rCurr.mpObj->Move(Size(nDelta, 0)); + fStepStart += fStepWidth; + } + } + + // clear list + aEntryList.clear(); + } + + if(eVer != SvxDistributeVertical::NONE) + { + // build sorted entry list + nFullLength = 0; + + for( size_t a = 0; a < nMark; ++a ) + { + SdrMark* pMark = GetSdrMarkByIndex(a); + ImpDistributeEntry aNew; + + aNew.mpObj = pMark->GetMarkedSdrObj(); + + switch(eVer) + { + case SvxDistributeVertical::Top: + { + aNew.mnPos = aNew.mpObj->GetSnapRect().Top(); + break; + } + case SvxDistributeVertical::Center: + { + aNew.mnPos = (aNew.mpObj->GetSnapRect().Bottom() + aNew.mpObj->GetSnapRect().Top()) / 2; + break; + } + case SvxDistributeVertical::Distance: + { + aNew.mnLength = aNew.mpObj->GetSnapRect().GetHeight() + 1; + nFullLength += aNew.mnLength; + aNew.mnPos = (aNew.mpObj->GetSnapRect().Bottom() + aNew.mpObj->GetSnapRect().Top()) / 2; + break; + } + case SvxDistributeVertical::Bottom: + { + aNew.mnPos = aNew.mpObj->GetSnapRect().Bottom(); + break; + } + default: break; + } + + itEntryList = std::find_if(aEntryList.begin(), aEntryList.end(), + [&aNew](const ImpDistributeEntry& rEntry) { return rEntry.mnPos >= aNew.mnPos; }); + if ( itEntryList < aEntryList.end() ) + aEntryList.insert( itEntryList, aNew ); + else + aEntryList.push_back( aNew ); + } + + if(eVer == SvxDistributeVertical::Distance) + { + // calculate room in-between + sal_Int32 nHeight = GetAllMarkedBoundRect().GetHeight() + 1; + double fStepWidth = (static_cast<double>(nHeight) - static_cast<double>(nFullLength)) / static_cast<double>(aEntryList.size() - 1); + double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos); + fStepStart += fStepWidth + static_cast<double>((aEntryList[ 0 ].mnLength + aEntryList[ 1 ].mnLength) / 2); + + // move entries 1..n-1 + for( size_t i = 1, n = aEntryList.size()-1; i < n; ++i) + { + ImpDistributeEntry& rCurr = aEntryList[ i ]; + ImpDistributeEntry& rNext = aEntryList[ i + 1 ]; + sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos; + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj)); + rCurr.mpObj->Move(Size(0, nDelta)); + fStepStart += fStepWidth + static_cast<double>((rCurr.mnLength + rNext.mnLength) / 2); + } + } + else + { + // calculate distances + sal_Int32 nHeight = aEntryList[ aEntryList.size() - 1 ].mnPos - aEntryList[ 0 ].mnPos; + double fStepWidth = static_cast<double>(nHeight) / static_cast<double>(aEntryList.size() - 1); + double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos); + fStepStart += fStepWidth; + + // move entries 1..n-1 + for(size_t i = 1, n = aEntryList.size()-1; i < n; ++i) + { + ImpDistributeEntry& rCurr = aEntryList[ i ]; + sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos; + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj)); + rCurr.mpObj->Move(Size(0, nDelta)); + fStepStart += fStepWidth; + } + } + + // clear list + aEntryList.clear(); + } + + // UNDO-Comment and end of UNDO + GetModel().SetUndoComment(SvxResId(STR_DistributeMarkedObjects)); + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::MergeMarkedObjects(SdrMergeMode eMode) +{ + // #i73441# check content + if(!AreObjectsMarked()) + return; + + SdrMarkList aRemove; + SortMarkedObjects(); + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(); + + size_t nInsPos = SAL_MAX_SIZE; + const SdrObject* pAttrObj = nullptr; + basegfx::B2DPolyPolygon aMergePolyPolygonA; + basegfx::B2DPolyPolygon aMergePolyPolygonB; + + SdrObjList* pInsOL = nullptr; + SdrPageView* pInsPV = nullptr; + bool bFirstObjectComplete(false); + + // make sure selected objects are contour objects + // since now basegfx::utils::adaptiveSubdivide() is used, it is no longer + // necessary to use ConvertMarkedToPolyObj which will subdivide curves using the old + // mechanisms. In a next step the polygon clipper will even be able to clip curves... + // ConvertMarkedToPolyObj(true); + ConvertMarkedToPathObj(true); + OSL_ENSURE(AreObjectsMarked(), "no more objects selected after preparations (!)"); + + for(size_t a=0; a<GetMarkedObjectCount(); ++a) + { + SdrMark* pM = GetSdrMarkByIndex(a); + SdrObject* pObj = pM->GetMarkedSdrObj(); + + if(ImpCanConvertForCombine(pObj)) + { + if(!pAttrObj) + pAttrObj = pObj; + + nInsPos = pObj->GetOrdNum() + 1; + pInsPV = pM->GetPageView(); + pInsOL = pObj->getParentSdrObjListFromSdrObject(); + + // #i76891# use single iteration from SJ here which works on SdrObjects and takes + // groups into account by itself + SdrObjListIter aIter(*pObj, SdrIterMode::DeepWithGroups); + + while(aIter.IsMore()) + { + SdrObject* pCandidate = aIter.Next(); + SdrPathObj* pPathObj = dynamic_cast<SdrPathObj*>( pCandidate ); + if(pPathObj) + { + basegfx::B2DPolyPolygon aTmpPoly(pPathObj->GetPathPoly()); + + // #i76891# unfortunately ConvertMarkedToPathObj has converted all + // involved polygon data to curve segments, even if not necessary. + // It is better to try to reduce to more simple polygons. + aTmpPoly = basegfx::utils::simplifyCurveSegments(aTmpPoly); + + // for each part polygon as preparation, remove self-intersections + // correct orientations and get rid of possible neutral polygons. + aTmpPoly = basegfx::utils::prepareForPolygonOperation(aTmpPoly); + + if(!bFirstObjectComplete) + { + // #i111987# Also need to collect ORed source shape when more than + // a single polygon is involved + if(aMergePolyPolygonA.count()) + { + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aTmpPoly); + } + else + { + aMergePolyPolygonA = aTmpPoly; + } + } + else + { + if(aMergePolyPolygonB.count()) + { + // to topologically correctly collect the 2nd polygon + // group it is necessary to OR the parts (each is seen as + // XOR-FillRule polygon and they are drawn over each-other) + aMergePolyPolygonB = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonB, aTmpPoly); + } + else + { + aMergePolyPolygonB = aTmpPoly; + } + } + } + } + + // was there something added to the first polygon? + if(!bFirstObjectComplete && aMergePolyPolygonA.count()) + { + bFirstObjectComplete = true; + } + + // move object to temporary delete list + aRemove.InsertEntry(SdrMark(pObj, pM->GetPageView())); + } + } + + switch(eMode) + { + case SdrMergeMode::Merge: + { + // merge all contained parts (OR) + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB); + break; + } + case SdrMergeMode::Subtract: + { + // Subtract B from A + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationDiff(aMergePolyPolygonA, aMergePolyPolygonB); + break; + } + case SdrMergeMode::Intersect: + { + // AND B and A + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB); + break; + } + } + + // #i73441# check insert list before taking actions + if(pInsOL) + { + rtl::Reference<SdrPathObj> pPath = new SdrPathObj(pAttrObj->getSdrModelFromSdrObject(), SdrObjKind::PathFill, std::move(aMergePolyPolygonA)); + ImpCopyAttributes(pAttrObj, pPath.get()); + pInsOL->InsertObject(pPath.get(), nInsPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath)); + + // #i124760# To have a correct selection with only the new object it is necessary to + // unmark all objects first. If not doing so, there may remain invalid pointers to objects + // TTTT:Not needed for aw080 (!) + UnmarkAllObj(pInsPV); + + MarkObj(pPath.get(), pInsPV, false, true); + } + + aRemove.ForceSort(); + switch(eMode) + { + case SdrMergeMode::Merge: + { + SetUndoComment( + SvxResId(STR_EditMergeMergePoly), + aRemove.GetMarkDescription()); + break; + } + case SdrMergeMode::Subtract: + { + SetUndoComment( + SvxResId(STR_EditMergeSubstractPoly), + aRemove.GetMarkDescription()); + break; + } + case SdrMergeMode::Intersect: + { + SetUndoComment( + SvxResId(STR_EditMergeIntersectPoly), + aRemove.GetMarkDescription()); + break; + } + } + DeleteMarkedList(aRemove); + + if( bUndo ) + EndUndo(); +} + +void SdrEditView::EqualizeMarkedObjects(bool bWidth) +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + size_t nMarked = rMarkList.GetMarkCount(); + + if (nMarked < 2) + return; + + size_t nLastSelected = 0; + sal_Int64 nLastSelectedTime = rMarkList.GetMark(0)->getTimeStamp(); + for (size_t a = 1; a < nMarked; ++a) + { + sal_Int64 nCandidateTime = rMarkList.GetMark(a)->getTimeStamp(); + if (nCandidateTime > nLastSelectedTime) + { + nLastSelectedTime = nCandidateTime; + nLastSelected = a; + } + } + + SdrObject* pLastSelectedObj = rMarkList.GetMark(nLastSelected)->GetMarkedSdrObj(); + Size aLastRectSize(pLastSelectedObj->GetLogicRect().GetSize()); + + const bool bUndo = IsUndoEnabled(); + + if (bUndo) + BegUndo(); + + for (size_t a = 0; a < nMarked; ++a) + { + if (a == nLastSelected) + continue; + SdrMark* pM = rMarkList.GetMark(a); + SdrObject* pObj = pM->GetMarkedSdrObj(); + tools::Rectangle aLogicRect(pObj->GetLogicRect()); + Size aLogicRectSize(aLogicRect.GetSize()); + if (bWidth) + aLogicRectSize.setWidth( aLastRectSize.Width() ); + else + aLogicRectSize.setHeight( aLastRectSize.Height() ); + aLogicRect.SetSize(aLogicRectSize); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + pObj->SetLogicRect(aLogicRect); + } + + SetUndoComment( + SvxResId(bWidth ? STR_EqualizeWidthMarkedObjects : STR_EqualizeHeightMarkedObjects), + rMarkList.GetMarkDescription()); + + if (bUndo) + EndUndo(); +} + +void SdrEditView::CombineMarkedTextObjects() +{ + SdrPageView* pPageView = GetSdrPageView(); + if ( !pPageView || pPageView->IsLayerLocked( GetActiveLayer() ) ) + return; + + bool bUndo = IsUndoEnabled(); + + // Undo-String will be set later + if ( bUndo ) + BegUndo(); + + SdrOutliner& rDrawOutliner = getSdrModelFromSdrView().GetDrawOutliner(); + + SdrObjListIter aIter( GetMarkedObjectList(), SdrIterMode::Flat); + while ( aIter.IsMore() ) + { + SdrObject* pObj = aIter.Next(); + SdrTextObj* pTextObj = DynCastSdrTextObj( pObj ); + const OutlinerParaObject* pOPO = pTextObj ? pTextObj->GetOutlinerParaObject() : nullptr; + if ( pOPO && pTextObj->IsTextFrame() + && pTextObj->GetObjIdentifier() == SdrObjKind::Text // not callouts (OBJ_CAPTION) + && !pTextObj->IsOutlText() // not impress presentation objects + && pTextObj->GetMergedItem(XATTR_FORMTXTSTYLE).GetValue() == XFormTextStyle::NONE // not Fontwork + ) + { + // if the last paragraph does not end in paragraph-end punctuation (ignoring whitespace), + // assume this text should be added to the end of the last paragraph, instead of starting a new paragraph. + const sal_Int32 nPara = rDrawOutliner.GetParagraphCount(); + const OUString sLastPara = nPara ? rDrawOutliner.GetText( rDrawOutliner.GetParagraph( nPara - 1 ) ) : ""; + sal_Int32 n = sLastPara.getLength(); + while ( n && unicode::isWhiteSpace( sLastPara[--n] ) ) + ; + //TODO: find way to use Locale to identify sentence final punctuation. Copied IsSentenceAtEnd() from autofmt.cxx + const bool bAppend = !n || ( sLastPara[n] != '.' && sLastPara[n] != '?' && sLastPara[n] != '!' ); + rDrawOutliner.AddText( *pOPO, bAppend ); + } + else + { + // Unmark non-textboxes, because all marked objects are deleted at the end. AdjustMarkHdl later. + MarkObj(pObj, pPageView, /*bUnmark=*/true, /*bImpNoSetMarkHdl=*/true); + } + } + + MarkListHasChanged(); + AdjustMarkHdl(); + + if ( GetMarkedObjectCount() > 1 ) + { + rtl::Reference<SdrRectObj> pReplacement = new SdrRectObj( getSdrModelFromSdrView(), SdrObjKind::Text ); + pReplacement->SetOutlinerParaObject( rDrawOutliner.CreateParaObject() ); + pReplacement->SetSnapRect( GetMarkedObjRect() ); + + const SdrInsertFlags nFlags = SdrInsertFlags::DONTMARK | SdrInsertFlags::SETDEFLAYER; + if ( InsertObjectAtView( pReplacement.get(), *pPageView, nFlags ) ) + DeleteMarkedObj(); + } + + if ( bUndo ) + EndUndo(); + + return; +} + +void SdrEditView::CombineMarkedObjects(bool bNoPolyPoly) +{ + // #105899# Start of Combine-Undo put to front, else ConvertMarkedToPolyObj would + // create a 2nd Undo-action and Undo-Comment. + + bool bUndo = IsUndoEnabled(); + + // Undo-String will be set later + if( bUndo ) + BegUndo("", "", bNoPolyPoly ? SdrRepeatFunc::CombineOnePoly : SdrRepeatFunc::CombinePolyPoly); + + // #105899# First, guarantee that all objects are converted to polyobjects, + // especially for SdrGrafObj with bitmap filling this is necessary to not + // lose the bitmap filling. + + // #i12392# + // ConvertMarkedToPolyObj was too strong here, it will lose quality and + // information when curve objects are combined. This can be replaced by + // using ConvertMarkedToPathObj without changing the previous fix. + + // #i21250# + // Instead of simply passing true as LineToArea, use bNoPolyPoly as info + // if this command is a 'Combine' or a 'Connect' command. On Connect it's true. + // To not concert line segments with a set line width to polygons in that case, + // use this info. Do not convert LineToArea on Connect commands. + // ConvertMarkedToPathObj(!bNoPolyPoly); + + // This is used for Combine and Connect. In no case it is necessary to force + // the content to curve, but it is also not good to force to polygons. Thus, + // curve is the less information losing one. Remember: This place is not + // used for merge. + // LineToArea is never necessary, both commands are able to take over the + // set line style and to display it correctly. Thus, i will use a + // ConvertMarkedToPathObj with a false in any case. Only drawback is that + // simple polygons will be changed to curves, but with no information loss. + ConvertMarkedToPathObj(false /* bLineToArea */); + + // continue as before + basegfx::B2DPolyPolygon aPolyPolygon; + SdrObjList* pCurrentOL = nullptr; + SdrMarkList aRemoveBuffer; + + SortMarkedObjects(); + size_t nInsPos = SAL_MAX_SIZE; + SdrObjList* pInsOL = nullptr; + SdrPageView* pInsPV = nullptr; + const SdrObject* pAttrObj = nullptr; + + for(size_t a = GetMarkedObjectCount(); a; ) + { + --a; + SdrMark* pM = GetSdrMarkByIndex(a); + SdrObject* pObj = pM->GetMarkedSdrObj(); + SdrObjList* pThisOL = pObj->getParentSdrObjListFromSdrObject(); + + if(pCurrentOL != pThisOL) + { + pCurrentOL = pThisOL; + } + + if(ImpCanConvertForCombine(pObj)) + { + // remember objects to be able to copy attributes + pAttrObj = pObj; + + // unfortunately ConvertMarkedToPathObj has converted all + // involved polygon data to curve segments, even if not necessary. + // It is better to try to reduce to more simple polygons. + basegfx::B2DPolyPolygon aTmpPoly(basegfx::utils::simplifyCurveSegments(ImpGetPolyPolygon(pObj))); + aPolyPolygon.insert(0, aTmpPoly); + + if(!pInsOL) + { + nInsPos = pObj->GetOrdNum() + 1; + pInsPV = pM->GetPageView(); + pInsOL = pObj->getParentSdrObjListFromSdrObject(); + } + + aRemoveBuffer.InsertEntry(SdrMark(pObj, pM->GetPageView())); + } + } + + if(bNoPolyPoly) + { + basegfx::B2DPolygon aCombinedPolygon(ImpCombineToSinglePolygon(aPolyPolygon)); + aPolyPolygon.clear(); + aPolyPolygon.append(aCombinedPolygon); + } + + const sal_uInt32 nPolyCount(aPolyPolygon.count()); + + if (nPolyCount && pAttrObj) + { + SdrObjKind eKind = SdrObjKind::PathFill; + + if(nPolyCount > 1) + { + aPolyPolygon.setClosed(true); + } + else + { + // check for Polyline + const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0)); + const sal_uInt32 nPointCount(aPolygon.count()); + + if(nPointCount <= 2) + { + eKind = SdrObjKind::PathLine; + } + else + { + if(!aPolygon.isClosed()) + { + const basegfx::B2DPoint aPointA(aPolygon.getB2DPoint(0)); + const basegfx::B2DPoint aPointB(aPolygon.getB2DPoint(nPointCount - 1)); + const double fDistance(basegfx::B2DVector(aPointB - aPointA).getLength()); + const double fJoinTolerance(10.0); + + if(fDistance < fJoinTolerance) + { + aPolyPolygon.setClosed(true); + } + else + { + eKind = SdrObjKind::PathLine; + } + } + } + } + + rtl::Reference<SdrPathObj> pPath = new SdrPathObj(pAttrObj->getSdrModelFromSdrObject(), eKind, std::move(aPolyPolygon)); + + // attributes of the lowest object + ImpCopyAttributes(pAttrObj, pPath.get()); + + // If LineStyle of pAttrObj is drawing::LineStyle_NONE force to drawing::LineStyle_SOLID to make visible. + const drawing::LineStyle eLineStyle = pAttrObj->GetMergedItem(XATTR_LINESTYLE).GetValue(); + const drawing::FillStyle eFillStyle = pAttrObj->GetMergedItem(XATTR_FILLSTYLE).GetValue(); + + // Take fill style/closed state of pAttrObj in account when deciding to change the line style + bool bIsClosedPathObj = false; + if (auto pPathObj = dynamic_cast<const SdrPathObj*>(pAttrObj)) + if (pPathObj->IsClosed()) + bIsClosedPathObj = true; + + if(drawing::LineStyle_NONE == eLineStyle && (drawing::FillStyle_NONE == eFillStyle || !bIsClosedPathObj)) + { + pPath->SetMergedItem(XLineStyleItem(drawing::LineStyle_SOLID)); + } + + pInsOL->InsertObject(pPath.get(),nInsPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath)); + + // Here was a severe error: Without UnmarkAllObj, the new object was marked + // additionally to the two ones which are deleted below. As long as those are + // in the UNDO there is no problem, but as soon as they get deleted, the + // MarkList will contain deleted objects -> GPF. + UnmarkAllObj(pInsPV); + MarkObj(pPath.get(), pInsPV, false, true); + } + + // build an UndoComment from the objects actually used + aRemoveBuffer.ForceSort(); // important for remove (see below) + if( bUndo ) + SetUndoComment(SvxResId(bNoPolyPoly?STR_EditCombine_OnePoly:STR_EditCombine_PolyPoly),aRemoveBuffer.GetMarkDescription()); + + // remove objects actually used from the list + DeleteMarkedList(aRemoveBuffer); + if( bUndo ) + EndUndo(); +} + + +// Dismantle + + +bool SdrEditView::ImpCanDismantle(const basegfx::B2DPolyPolygon& rPpolyPolygon, bool bMakeLines) +{ + bool bCan(false); + const sal_uInt32 nPolygonCount(rPpolyPolygon.count()); + + if(nPolygonCount >= 2) + { + // #i69172# dismantle makes sense with 2 or more polygons in a polyPolygon + bCan = true; + } + else if(bMakeLines && 1 == nPolygonCount) + { + // #i69172# ..or with at least 2 edges (curves or lines) + const basegfx::B2DPolygon& aPolygon(rPpolyPolygon.getB2DPolygon(0)); + const sal_uInt32 nPointCount(aPolygon.count()); + + if(nPointCount > 2) + { + bCan = true; + } + } + + return bCan; +} + +bool SdrEditView::ImpCanDismantle(const SdrObject* pObj, bool bMakeLines) +{ + bool bOtherObjs(false); // true=objects other than PathObj's existent + bool bMin1PolyPoly(false); // true=at least 1 tools::PolyPolygon with more than one Polygon existent + SdrObjList* pOL = pObj->GetSubList(); + + if(pOL) + { + // group object -- check all members if they're PathObjs + SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups); + + while(aIter.IsMore() && !bOtherObjs) + { + const SdrObject* pObj1 = aIter.Next(); + const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>( pObj1 ); + + if(pPath) + { + if(ImpCanDismantle(pPath->GetPathPoly(), bMakeLines)) + { + bMin1PolyPoly = true; + } + + SdrObjTransformInfoRec aInfo; + pObj1->TakeObjInfo(aInfo); + + if(!aInfo.bCanConvToPath) + { + // happens e. g. in the case of FontWork + bOtherObjs = true; + } + } + else + { + bOtherObjs = true; + } + } + } + else + { + const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>(pObj); + const SdrObjCustomShape* pCustomShape = dynamic_cast<const SdrObjCustomShape*>(pObj); + + // #i37011# + if(pPath) + { + if(ImpCanDismantle(pPath->GetPathPoly(),bMakeLines)) + { + bMin1PolyPoly = true; + } + + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + + // new condition IsLine() to be able to break simple Lines + if(!(aInfo.bCanConvToPath || aInfo.bCanConvToPoly) && !pPath->IsLine()) + { + // happens e. g. in the case of FontWork + bOtherObjs = true; + } + } + else if(pCustomShape) + { + if(bMakeLines) + { + // allow break command + bMin1PolyPoly = true; + } + } + else + { + bOtherObjs = true; + } + } + return bMin1PolyPoly && !bOtherObjs; +} + +void SdrEditView::ImpDismantleOneObject(const SdrObject* pObj, SdrObjList& rOL, size_t& rPos, SdrPageView* pPV, bool bMakeLines) +{ + const SdrPathObj* pSrcPath = dynamic_cast<const SdrPathObj*>( pObj ); + const SdrObjCustomShape* pCustomShape = dynamic_cast<const SdrObjCustomShape*>( pObj ); + + const bool bUndo = IsUndoEnabled(); + + if(pSrcPath) + { + // #i74631# redesigned due to XpolyPolygon removal and explicit constructors + SdrObject* pLast = nullptr; // to be able to apply OutlinerParaObject + const basegfx::B2DPolyPolygon& rPolyPolygon(pSrcPath->GetPathPoly()); + const sal_uInt32 nPolyCount(rPolyPolygon.count()); + + for(sal_uInt32 a(0); a < nPolyCount; a++) + { + const basegfx::B2DPolygon& rCandidate(rPolyPolygon.getB2DPolygon(a)); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(!bMakeLines || nPointCount < 2) + { + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + pSrcPath->getSdrModelFromSdrObject(), + pSrcPath->GetObjIdentifier(), + basegfx::B2DPolyPolygon(rCandidate)); + ImpCopyAttributes(pSrcPath, pPath.get()); + pLast = pPath.get(); + rOL.InsertObject(pPath.get(), rPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath, true)); + MarkObj(pPath.get(), pPV, false, true); + rPos++; + } + else + { + const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + + for(sal_uInt32 b(0); b < nLoopCount; b++) + { + SdrObjKind eKind(SdrObjKind::PolyLine); + basegfx::B2DPolygon aNewPolygon; + const sal_uInt32 nNextIndex((b + 1) % nPointCount); + + aNewPolygon.append(rCandidate.getB2DPoint(b)); + + if(rCandidate.areControlPointsUsed()) + { + aNewPolygon.appendBezierSegment( + rCandidate.getNextControlPoint(b), + rCandidate.getPrevControlPoint(nNextIndex), + rCandidate.getB2DPoint(nNextIndex)); + eKind = SdrObjKind::PathLine; + } + else + { + aNewPolygon.append(rCandidate.getB2DPoint(nNextIndex)); + } + + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + pSrcPath->getSdrModelFromSdrObject(), + eKind, + basegfx::B2DPolyPolygon(aNewPolygon)); + ImpCopyAttributes(pSrcPath, pPath.get()); + pLast = pPath.get(); + rOL.InsertObject(pPath.get(), rPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath, true)); + MarkObj(pPath.get(), pPV, false, true); + rPos++; + } + } + } + + if(pLast && pSrcPath->GetOutlinerParaObject()) + { + pLast->SetOutlinerParaObject(*pSrcPath->GetOutlinerParaObject()); + } + } + else if(pCustomShape) + { + if(bMakeLines) + { + // break up custom shape + const SdrObject* pReplacement = pCustomShape->GetSdrObjectFromCustomShape(); + + if(pReplacement) + { + rtl::Reference<SdrObject> pCandidate(pReplacement->CloneSdrObject(pReplacement->getSdrModelFromSdrObject())); + DBG_ASSERT(pCandidate, "SdrEditView::ImpDismantleOneObject: Could not clone SdrObject (!)"); + + if(pCustomShape->GetMergedItem(SDRATTR_SHADOW).GetValue()) + { + if(dynamic_cast<const SdrObjGroup*>( pReplacement) != nullptr) + { + pCandidate->SetMergedItem(makeSdrShadowItem(true)); + } + } + + rOL.InsertObject(pCandidate.get(), rPos); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pCandidate, true)); + MarkObj(pCandidate.get(), pPV, false, true); + + if(pCustomShape->HasText() && !pCustomShape->IsTextPath()) + { + // #i37011# also create a text object and add at rPos + 1 + rtl::Reference<SdrObject> pTextObj = SdrObjFactory::MakeNewObject( + pCustomShape->getSdrModelFromSdrObject(), + pCustomShape->GetObjInventor(), + SdrObjKind::Text); + + // Copy text content + OutlinerParaObject* pParaObj = pCustomShape->GetOutlinerParaObject(); + if(pParaObj) + { + pTextObj->NbcSetOutlinerParaObject(*pParaObj); + } + + // copy all attributes + SfxItemSet aTargetItemSet(pCustomShape->GetMergedItemSet()); + + // clear fill and line style + aTargetItemSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); + aTargetItemSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + + // get the text bounds and set at text object + tools::Rectangle aTextBounds = pCustomShape->GetSnapRect(); + if(pCustomShape->GetTextBounds(aTextBounds)) + { + pTextObj->SetSnapRect(aTextBounds); + } + + // if rotated, copy GeoStat, too. + const GeoStat& rSourceGeo = pCustomShape->GetGeoStat(); + if(rSourceGeo.m_nRotationAngle) + { + pTextObj->NbcRotate( + pCustomShape->GetSnapRect().Center(), rSourceGeo.m_nRotationAngle, + rSourceGeo.mfSinRotationAngle, rSourceGeo.mfCosRotationAngle); + } + + // set modified ItemSet at text object + pTextObj->SetMergedItemSet(aTargetItemSet); + + // insert object + rOL.InsertObject(pTextObj.get(), rPos + 1); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pTextObj, true)); + MarkObj(pTextObj.get(), pPV, false, true); + } + } + } + } +} + +void SdrEditView::DismantleMarkedObjects(bool bMakeLines) +{ + // temporary MarkList + SdrMarkList aRemoveBuffer; + + SortMarkedObjects(); + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + { + // comment is constructed later + BegUndo("", "", bMakeLines ? SdrRepeatFunc::DismantleLines : SdrRepeatFunc::DismantlePolys); + } + + SdrObjList* pOL0=nullptr; + const bool bWasLocked = GetModel().isLocked(); + GetModel().setLock(true); + for (size_t nm=GetMarkedObjectCount(); nm>0;) { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPageView* pPV=pM->GetPageView(); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + if (pOL!=pOL0) { pOL0=pOL; pObj->GetOrdNum(); } // make sure OrdNums are correct! + if (ImpCanDismantle(pObj,bMakeLines)) { + aRemoveBuffer.InsertEntry(SdrMark(pObj,pM->GetPageView())); + const size_t nPos0=pObj->GetOrdNumDirect(); + size_t nPos=nPos0+1; + SdrObjList* pSubList=pObj->GetSubList(); + if (pSubList!=nullptr && !pObj->Is3DObj()) { + SdrObjListIter aIter(pSubList,SdrIterMode::DeepNoGroups); + while (aIter.IsMore()) { + const SdrObject* pObj1=aIter.Next(); + ImpDismantleOneObject(pObj1,*pOL,nPos,pPV,bMakeLines); + } + } else { + ImpDismantleOneObject(pObj,*pOL,nPos,pPV,bMakeLines); + } + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj,true)); + pOL->RemoveObject(nPos0); + } + } + GetModel().setLock(bWasLocked); + + if( bUndo ) + { + // construct UndoComment from objects actually used + SetUndoComment(SvxResId(bMakeLines?STR_EditDismantle_Lines:STR_EditDismantle_Polys),aRemoveBuffer.GetMarkDescription()); + // remove objects actually used from the list + EndUndo(); + } +} + + +// Group + + +void SdrEditView::GroupMarked() +{ + if (!AreObjectsMarked()) + return; + + SortMarkedObjects(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + BegUndo(SvxResId(STR_EditGroup),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::Group); + + for(size_t nm = GetMarkedObjectCount(); nm>0; ) + { + // add UndoActions for all affected objects + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj = pM->GetMarkedSdrObj(); + AddUndoActions( CreateConnectorUndo( *pObj ) ); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoRemoveObject( *pObj )); + } + } + + SdrMarkList aNewMark; + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + SdrObjList* pCurrentLst=pPV->GetObjList(); + SdrObjList* pSrcLst=pCurrentLst; + SdrObjList* pSrcLst0=pSrcLst; + // make sure OrdNums are correct + if (pSrcLst->IsObjOrdNumsDirty()) + pSrcLst->RecalcObjOrdNums(); + rtl::Reference<SdrObject> pGrp; + SdrObjList* pDstLst=nullptr; + // if all selected objects come from foreign object lists. + // the group object is the last one in the list. + size_t nInsPos=pSrcLst->GetObjCount(); + bool bNeedInsPos=true; + for (size_t nm=GetMarkedObjectCount(); nm>0;) + { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + if (pM->GetPageView()==pPV) + { + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (!pGrp) + { + pGrp = new SdrObjGroup(pObj->getSdrModelFromSdrObject()); + pDstLst=pGrp->GetSubList(); + assert(pDstLst && "Alleged group object doesn't return object list."); + } + pSrcLst=pObj->getParentSdrObjListFromSdrObject(); + if (pSrcLst!=pSrcLst0) + { + if (pSrcLst->IsObjOrdNumsDirty()) + pSrcLst->RecalcObjOrdNums(); + } + bool bForeignList=pSrcLst!=pCurrentLst; + if (!bForeignList && bNeedInsPos) + { + nInsPos=pObj->GetOrdNum(); // this way, all ObjOrdNum of the page are set + nInsPos++; + bNeedInsPos=false; + } + pSrcLst->RemoveObject(pObj->GetOrdNumDirect()); + if (!bForeignList) + nInsPos--; // correct InsertPos + pDstLst->InsertObject(pObj,0); + GetMarkedObjectListWriteAccess().DeleteMark(nm); + pSrcLst0=pSrcLst; + } + } + if (pGrp!=nullptr) + { + assert(pDstLst); // keep coverity happy + aNewMark.InsertEntry(SdrMark(pGrp.get(),pPV)); + const size_t nCount=pDstLst->GetObjCount(); + pCurrentLst->InsertObject(pGrp.get(),nInsPos); + if( bUndo ) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pGrp,true)); // no recalculation! + for (size_t no=0; no<nCount; ++no) + { + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoInsertObject(*pDstLst->GetObj(no))); + } + } + } + } + GetMarkedObjectListWriteAccess().Merge(aNewMark); + MarkListHasChanged(); + + if( bUndo ) + EndUndo(); +} + + +// Ungroup + + +void SdrEditView::UnGroupMarked() +{ + SdrMarkList aNewMark; + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo("", "", SdrRepeatFunc::Ungroup); + + size_t nCount=0; + OUString aName1; + OUString aName; + bool bNameOk=false; + for (size_t nm=GetMarkedObjectCount(); nm>0;) { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pGrp=pM->GetMarkedSdrObj(); + SdrObjList* pSrcLst=pGrp->GetSubList(); + if (pSrcLst!=nullptr) { + nCount++; + if (nCount==1) { + aName = pGrp->TakeObjNameSingul(); // retrieve name of group + aName1 = pGrp->TakeObjNamePlural(); // retrieve name of group + bNameOk=true; + } else { + if (nCount==2) aName=aName1; // set plural name + if (bNameOk) { + OUString aStr(pGrp->TakeObjNamePlural()); // retrieve name of group + + if (aStr != aName) + bNameOk = false; + } + } + size_t nDstCnt=pGrp->GetOrdNum(); + SdrObjList* pDstLst=pM->GetPageView()->GetObjList(); + size_t nObjCount=pSrcLst->GetObjCount(); + const bool bIsDiagram(pGrp->isDiagram()); + + // If the Group is a Diagram, it has a filler BG object to guarantee + // the Diagam's dimensions. Identify that shape + if(bIsDiagram && nObjCount) + { + SdrObject* pObj(pSrcLst->GetObj(0)); + + if(nullptr != pObj && !pObj->IsGroupObject() && + !pObj->HasLineStyle() && + pObj->IsMoveProtect() && pObj->IsResizeProtect()) + { + if(pObj->HasFillStyle()) + { + // If it has FillStyle it is a useful object representing that possible + // defined fill from oox import. In this case, we should remove the + // Move/Resize protection to allow seamless further processing. + + // Undo of these is handled by SdrUndoGeoObj which holds a SdrObjGeoData, + // create one + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + pObj->SetMoveProtect(false); + pObj->SetResizeProtect(false); + } + else + { + // If it has no FillStyle it is not useful for any further processing + // but only was used as a placeholder, get directly rid of it + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj)); + + pSrcLst->RemoveObject(0); + + nObjCount = pSrcLst->GetObjCount(); + } + } + } + + // FIRST move contained objects to parent of group, so that + // the contained objects are NOT migrated to the UNDO-ItemPool + // when AddUndo(new SdrUndoDelObj(*pGrp)) is called. + if( bUndo ) + { + for (size_t no=nObjCount; no>0;) + { + no--; + SdrObject* pObj=pSrcLst->GetObj(no); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoRemoveObject(*pObj)); + } + } + + for (size_t no=0; no<nObjCount; ++no) + { + rtl::Reference<SdrObject> pObj=pSrcLst->RemoveObject(0); + pDstLst->InsertObject(pObj.get(),nDstCnt); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoInsertObject(*pObj,true)); + nDstCnt++; + // No SortCheck when inserting into MarkList, because that would + // provoke a RecalcOrdNums() each time because of pObj->GetOrdNum(): + aNewMark.InsertEntry(SdrMark(pObj.get(),pM->GetPageView()),false); + } + + if( bUndo ) + { + // Now it is safe to add the delete-UNDO which triggers the + // MigrateItemPool now only for itself, not for the sub-objects. + // nDstCnt is right, because previous inserts move group + // object deeper and increase nDstCnt. + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pGrp)); + } + pDstLst->RemoveObject(nDstCnt); + + GetMarkedObjectListWriteAccess().DeleteMark(nm); + } + } + if (nCount!=0) + { + if (!bNameOk) + aName=SvxResId(STR_ObjNamePluralGRUP); // Use the term "Group Objects," if different objects are grouped. + SetUndoComment(SvxResId(STR_EditUngroup),aName); + } + + if( bUndo ) + EndUndo(); + + if (nCount!=0) + { + GetMarkedObjectListWriteAccess().Merge(aNewMark,true); // Because of the sorting above, aNewMark is reversed + MarkListHasChanged(); + } +} + + +// ConvertToPoly + + +rtl::Reference<SdrObject> SdrEditView::ImpConvertOneObj(SdrObject* pObj, bool bPath, bool bLineToArea) +{ + rtl::Reference<SdrObject> pNewObj = pObj->ConvertToPolyObj(bPath, bLineToArea); + if (pNewObj) + { + SdrObjList* pOL = pObj->getParentSdrObjListFromSdrObject(); + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoReplaceObject(*pObj,*pNewObj)); + + pOL->ReplaceObject(pNewObj.get(), pObj->GetOrdNum()); + } + return pNewObj; +} + +void SdrEditView::ImpConvertTo(bool bPath, bool bLineToArea) +{ + if (!AreObjectsMarked()) return; + + bool bMrkChg = false; + const size_t nMarkCount=GetMarkedObjectCount(); + TranslateId pDscrID; + if(bLineToArea) + { + if(nMarkCount == 1) + pDscrID = STR_EditConvToContour; + else + pDscrID = STR_EditConvToContours; + + BegUndo(SvxResId(pDscrID), GetDescriptionOfMarkedObjects()); + } + else + { + if (bPath) { + if (nMarkCount==1) pDscrID=STR_EditConvToCurve; + else pDscrID=STR_EditConvToCurves; + BegUndo(SvxResId(pDscrID),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::ConvertToPath); + } else { + if (nMarkCount==1) pDscrID=STR_EditConvToPoly; + else pDscrID=STR_EditConvToPolys; + BegUndo(SvxResId(pDscrID),GetDescriptionOfMarkedObjects(),SdrRepeatFunc::ConvertToPoly); + } + } + for (size_t nm=nMarkCount; nm>0;) { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPageView* pPV=pM->GetPageView(); + if (pObj->IsGroupObject() && !pObj->Is3DObj()) { + SdrObject* pGrp=pObj; + SdrObjListIter aIter(*pGrp, SdrIterMode::DeepNoGroups); + while (aIter.IsMore()) { + pObj=aIter.Next(); + ImpConvertOneObj(pObj,bPath,bLineToArea); + } + } else { + rtl::Reference<SdrObject> pNewObj=ImpConvertOneObj(pObj,bPath,bLineToArea); + if (pNewObj) { + bMrkChg=true; + GetMarkedObjectListWriteAccess().ReplaceMark(SdrMark(pNewObj.get(),pPV),nm); + } + } + } + EndUndo(); + if (bMrkChg) + { + AdjustMarkHdl(); + MarkListHasChanged(); + } +} + +void SdrEditView::ConvertMarkedToPathObj(bool bLineToArea) +{ + ImpConvertTo(true, bLineToArea); +} + +void SdrEditView::ConvertMarkedToPolyObj() +{ + ImpConvertTo(false, false/*bLineToArea*/); +} + +namespace +{ + GDIMetaFile GetMetaFile(SdrGrafObj const * pGraf) + { + if (pGraf->HasGDIMetaFile()) + return pGraf->GetTransformedGraphic(SdrGrafObjTransformsAttrs::MIRROR).GetGDIMetaFile(); + assert(pGraf->isEmbeddedVectorGraphicData()); + return pGraf->getMetafileFromEmbeddedVectorGraphicData(); + } +} + +// Metafile Import +void SdrEditView::DoImportMarkedMtf(SvdProgressInfo *pProgrInfo) +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo("", "", SdrRepeatFunc::ImportMtf); + + SortMarkedObjects(); + SdrMarkList aForTheDescription; + SdrMarkList aNewMarked; + for (size_t nm =GetMarkedObjectCount(); nm > 0; ) + { + // create Undo objects for all new objects + // check for cancellation between the metafiles + if (pProgrInfo != nullptr) + { + pProgrInfo->SetNextObject(); + if (!pProgrInfo->ReportActions(0)) + break; + } + + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPageView* pPV=pM->GetPageView(); + SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject(); + const size_t nInsPos=pObj->GetOrdNum()+1; + size_t nInsCnt=0; + tools::Rectangle aLogicRect; + + SdrGrafObj* pGraf = dynamic_cast<SdrGrafObj*>( pObj ); + if (pGraf != nullptr) + { + Graphic aGraphic = pGraf->GetGraphic(); + auto const & pVectorGraphicData = aGraphic.getVectorGraphicData(); + + if (pVectorGraphicData && pVectorGraphicData->getType() == VectorGraphicDataType::Pdf) + { + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (pPdfium) + { + aLogicRect = pGraf->GetLogicRect(); + ImpSdrPdfImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect, aGraphic); + if (aGraphic.getPageNumber() < aFilter.GetPageCount()) + { + nInsCnt = aFilter.DoImport(*pOL, nInsPos, aGraphic.getPageNumber(), pProgrInfo); + } + } + } + else if (pGraf->HasGDIMetaFile() || pGraf->isEmbeddedVectorGraphicData() ) + { + GDIMetaFile aMetaFile(GetMetaFile(pGraf)); + if (aMetaFile.GetActionSize()) + { + aLogicRect = pGraf->GetLogicRect(); + ImpSdrGDIMetaFileImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect); + nInsCnt = aFilter.DoImport(aMetaFile, *pOL, nInsPos, pProgrInfo); + } + } + } + + SdrOle2Obj* pOle2 = dynamic_cast<SdrOle2Obj*>(pObj); + if (pOle2 != nullptr && pOle2->GetGraphic()) + { + aLogicRect = pOle2->GetLogicRect(); + ImpSdrGDIMetaFileImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect); + nInsCnt = aFilter.DoImport(pOle2->GetGraphic()->GetGDIMetaFile(), *pOL, nInsPos, pProgrInfo); + } + + if (nInsCnt != 0) + { + // transformation + GeoStat aGeoStat(pGraf ? pGraf->GetGeoStat() : pOle2->GetGeoStat()); + size_t nObj = nInsPos; + + if (aGeoStat.m_nShearAngle) + aGeoStat.RecalcTan(); + + if (aGeoStat.m_nRotationAngle) + aGeoStat.RecalcSinCos(); + + for (size_t i = 0; i < nInsCnt; i++) + { + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pOL->GetObj(nObj))); + + // update new MarkList + SdrObject* pCandidate = pOL->GetObj(nObj); + + // apply original transformation + if (aGeoStat.m_nShearAngle) + pCandidate->NbcShear(aLogicRect.TopLeft(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false); + + if (aGeoStat.m_nRotationAngle) + pCandidate->NbcRotate(aLogicRect.TopLeft(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle); + + SdrMark aNewMark(pCandidate, pPV); + aNewMarked.InsertEntry(aNewMark); + + nObj++; + } + + aForTheDescription.InsertEntry(*pM); + + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj)); + + // remove object from selection and delete + GetMarkedObjectListWriteAccess().DeleteMark(TryToFindMarkedObject(pObj)); + pOL->RemoveObject(nInsPos-1); + } + } + + if (aNewMarked.GetMarkCount()) + { + // create new selection + for (size_t a = 0; a < aNewMarked.GetMarkCount(); ++a) + { + GetMarkedObjectListWriteAccess().InsertEntry(*aNewMarked.GetMark(a)); + } + + SortMarkedObjects(); + } + + if (bUndo) + { + SetUndoComment(SvxResId(STR_EditImportMtf),aForTheDescription.GetMarkDescription()); + EndUndo(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdedxv.cxx b/svx/source/svdraw/svdedxv.cxx new file mode 100644 index 0000000000..c2d35cc75b --- /dev/null +++ b/svx/source/svdraw/svdedxv.cxx @@ -0,0 +1,3002 @@ +/* -*- 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 <com/sun/star/i18n/WordType.hpp> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/unotext.hxx> +#include <o3tl/deleter.hxx> +#include <svl/itemiter.hxx> +#include <svl/style.hxx> +#include <svl/whiter.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svx/sdtfchim.hxx> +#include <svx/selectioncontroller.hxx> +#include <svx/svdedxv.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdundo.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/cursor.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#include <comphelper/lok.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <editeng/outliner.hxx> +#include <sal/log.hxx> +#include <sdr/overlay/overlaytools.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/dialmgr.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdr/overlay/overlayselection.hxx> +#include <svx/sdr/table/tablecontroller.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdrundomanager.hxx> +#include <svx/strings.hrc> +#include <svx/svdviter.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <textchain.hxx> +#include <textchaincursor.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> + +#include <memory> + +SdrObjEditView::SdrObjEditView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrGlueEditView(rSdrModel, pOut) + , mpTextEditPV(nullptr) + , mpTextEditOutlinerView(nullptr) + , mpTextEditWin(nullptr) + , pTextEditCursorBuffer(nullptr) + , pMacroObj(nullptr) + , pMacroPV(nullptr) + , pMacroWin(nullptr) + , nMacroTol(0) + , mbTextEditDontDelete(false) + , mbTextEditOnlyOneView(false) + , mbTextEditNewObj(false) + , mbQuickTextEditMode(true) + , mbMacroDown(false) + , mpOldTextEditUndoManager(nullptr) +{ +} + +SdrObjEditView::~SdrObjEditView() +{ + mpTextEditWin = nullptr; // so there's no ShowCursor in SdrEndTextEdit + assert(!IsTextEdit()); + if (IsTextEdit()) + suppress_fun_call_w_exception(SdrEndTextEdit()); + mpTextEditOutliner.reset(); + assert(nullptr == mpOldTextEditUndoManager); // should have been reset +} + +bool SdrObjEditView::IsAction() const { return IsMacroObj() || SdrGlueEditView::IsAction(); } + +void SdrObjEditView::MovAction(const Point& rPnt) +{ + if (IsMacroObj()) + MovMacroObj(rPnt); + SdrGlueEditView::MovAction(rPnt); +} + +void SdrObjEditView::EndAction() +{ + if (IsMacroObj()) + EndMacroObj(); + SdrGlueEditView::EndAction(); +} + +void SdrObjEditView::BckAction() +{ + BrkMacroObj(); + SdrGlueEditView::BckAction(); +} + +void SdrObjEditView::BrkAction() +{ + BrkMacroObj(); + SdrGlueEditView::BrkAction(); +} + +SdrPageView* SdrObjEditView::ShowSdrPage(SdrPage* pPage) +{ + SdrPageView* pPageView = SdrGlueEditView::ShowSdrPage(pPage); + + if (comphelper::LibreOfficeKit::isActive() && pPageView) + { + // Check if other views have an active text edit on the same page as + // this one. + SdrViewIter::ForAllViews(pPageView->GetPage(), [this](SdrView* pView) { + if (pView == this || !pView->IsTextEdit()) + return; + + OutputDevice* pOutDev = GetFirstOutputDevice(); + if (!pOutDev || pOutDev->GetOutDevType() != OUTDEV_WINDOW) + return; + + // Found one, so create an outliner view, to get invalidations when + // the text edit changes. + // Call GetSfxViewShell() to make sure ImpMakeOutlinerView() + // registers the view shell of this draw view, and not the view + // shell of pView. + OutlinerView* pOutlinerView + = pView->ImpMakeOutlinerView(pOutDev->GetOwnerWindow(), nullptr, GetSfxViewShell()); + pOutlinerView->HideCursor(); + pView->GetTextEditOutliner()->InsertView(pOutlinerView); + }); + } + + return pPageView; +} + +namespace +{ +/// Removes outliner views registered in other draw views that use pOutputDevice. +void lcl_RemoveTextEditOutlinerViews(SdrObjEditView const* pThis, SdrPageView const* pPageView, + OutputDevice const* pOutputDevice) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if (!pPageView) + return; + + if (!pOutputDevice || pOutputDevice->GetOutDevType() != OUTDEV_WINDOW) + return; + + SdrViewIter::ForAllViews(pPageView->GetPage(), [&pThis, &pOutputDevice](SdrView* pView) { + if (pView == pThis || !pView->IsTextEdit()) + return; + + SdrOutliner* pOutliner = pView->GetTextEditOutliner(); + for (size_t nView = 0; nView < pOutliner->GetViewCount(); ++nView) + { + OutlinerView* pOutlinerView = pOutliner->GetView(nView); + if (pOutlinerView->GetWindow()->GetOutDev() != pOutputDevice) + continue; + + pOutliner->RemoveView(pOutlinerView); + delete pOutlinerView; + } + }); +} +} + +void SdrObjEditView::HideSdrPage() +{ + lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), GetFirstOutputDevice()); + + if (mpTextEditPV == GetSdrPageView()) + { + // HideSdrPage() will clear mpPageView, avoid a dangling pointer. + mpTextEditPV = nullptr; + } + + SdrGlueEditView::HideSdrPage(); +} + +void SdrObjEditView::TakeActionRect(tools::Rectangle& rRect) const +{ + if (IsMacroObj()) + { + rRect = pMacroObj->GetCurrentBoundRect(); + } + else + { + SdrGlueEditView::TakeActionRect(rRect); + } +} + +void SdrObjEditView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + SdrGlueEditView::Notify(rBC, rHint); + if (mpTextEditOutliner == nullptr) + return; + + // change of printer while editing + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + SdrHintKind eKind = pSdrHint->GetKind(); + if (eKind == SdrHintKind::RefDeviceChange) + { + mpTextEditOutliner->SetRefDevice(GetModel().GetRefDevice()); + } + if (eKind == SdrHintKind::DefaultTabChange) + { + mpTextEditOutliner->SetDefTab(GetModel().GetDefaultTabulator()); + } +} + +void SdrObjEditView::ModelHasChanged() +{ + SdrGlueEditView::ModelHasChanged(); + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj && !pTextObj->IsInserted()) + SdrEndTextEdit(); // object deleted + // TextEditObj changed? + if (!IsTextEdit()) + return; + + if (pTextObj != nullptr) + { + size_t nOutlViewCnt = mpTextEditOutliner->GetViewCount(); + bool bAreaChg = false; + bool bAnchorChg = false; + bool bColorChg = false; + bool bContourFrame = pTextObj->IsContourTextFrame(); + EEAnchorMode eNewAnchor(EEAnchorMode::VCenterHCenter); + tools::Rectangle aOldArea(aMinTextEditArea); + aOldArea.Union(aTextEditArea); + Color aNewColor; + { // check area + Size aPaperMin1; + Size aPaperMax1; + tools::Rectangle aEditArea1; + tools::Rectangle aMinArea1; + pTextObj->TakeTextEditArea(&aPaperMin1, &aPaperMax1, &aEditArea1, &aMinArea1); + Point aPvOfs(pTextObj->GetTextEditOffset()); + + // add possible GridOffset to up-to-now view-independent EditAreas + basegfx::B2DVector aGridOffset(0.0, 0.0); + if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj.get(), GetSdrPageView())) + { + const Point aOffset(basegfx::fround(aGridOffset.getX()), + basegfx::fround(aGridOffset.getY())); + + aEditArea1 += aOffset; + aMinArea1 += aOffset; + } + + aEditArea1.Move(aPvOfs.X(), aPvOfs.Y()); + aMinArea1.Move(aPvOfs.X(), aPvOfs.Y()); + tools::Rectangle aNewArea(aMinArea1); + aNewArea.Union(aEditArea1); + + if (aNewArea != aOldArea || aEditArea1 != aTextEditArea || aMinArea1 != aMinTextEditArea + || mpTextEditOutliner->GetMinAutoPaperSize() != aPaperMin1 + || mpTextEditOutliner->GetMaxAutoPaperSize() != aPaperMax1) + { + aTextEditArea = aEditArea1; + aMinTextEditArea = aMinArea1; + + const bool bPrevUpdateLayout = mpTextEditOutliner->SetUpdateLayout(false); + mpTextEditOutliner->SetMinAutoPaperSize(aPaperMin1); + mpTextEditOutliner->SetMaxAutoPaperSize(aPaperMax1); + mpTextEditOutliner->SetPaperSize(Size(0, 0)); // re-format Outliner + + if (!bContourFrame) + { + mpTextEditOutliner->ClearPolygon(); + EEControlBits nStat = mpTextEditOutliner->GetControlWord(); + nStat |= EEControlBits::AUTOPAGESIZE; + mpTextEditOutliner->SetControlWord(nStat); + } + else + { + EEControlBits nStat = mpTextEditOutliner->GetControlWord(); + nStat &= ~EEControlBits::AUTOPAGESIZE; + mpTextEditOutliner->SetControlWord(nStat); + tools::Rectangle aAnchorRect; + pTextObj->TakeTextAnchorRect(aAnchorRect); + pTextObj->ImpSetContourPolygon(*mpTextEditOutliner, aAnchorRect, true); + } + for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV); + EVControlBits nStat0 = pOLV->GetControlWord(); + EVControlBits nStat = nStat0; + // AutoViewSize only if not ContourFrame. + if (!bContourFrame) + nStat |= EVControlBits::AUTOSIZE; + else + nStat &= ~EVControlBits::AUTOSIZE; + if (nStat != nStat0) + pOLV->SetControlWord(nStat); + } + + mpTextEditOutliner->SetUpdateLayout(bPrevUpdateLayout); + bAreaChg = true; + } + } + if (mpTextEditOutlinerView != nullptr) + { // check fill and anchor + EEAnchorMode eOldAnchor = mpTextEditOutlinerView->GetAnchorMode(); + eNewAnchor = pTextObj->GetOutlinerViewAnchorMode(); + bAnchorChg = eOldAnchor != eNewAnchor; + Color aOldColor(mpTextEditOutlinerView->GetBackgroundColor()); + aNewColor = GetTextEditBackgroundColor(*this); + bColorChg = aOldColor != aNewColor; + } + // refresh always when it's a contour frame. That + // refresh is necessary since it triggers the repaint + // which makes the Handles visible. Changes at TakeTextRect() + // seem to have resulted in a case where no refresh is executed. + // Before that, a refresh must have been always executed + // (else this error would have happened earlier), thus I + // even think here a refresh should be done always. + // Since follow-up problems cannot even be guessed I only + // add this one more case to the if below. + // BTW: It's VERY bad style that here, inside ModelHasChanged() + // the outliner is again massively changed for the text object + // in text edit mode. Normally, all necessary data should be + // set at SdrBeginTextEdit(). Some changes and value assigns in + // SdrBeginTextEdit() are completely useless since they are set here + // again on ModelHasChanged(). + if (bContourFrame || bAreaChg || bAnchorChg || bColorChg) + { + for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV); + { // invalidate old OutlinerView area + vcl::Window* pWin = pOLV->GetWindow(); + tools::Rectangle aTmpRect(aOldArea); + sal_uInt16 nPixSiz = pOLV->GetInvalidateMore() + 1; + Size aMore(pWin->PixelToLogic(Size(nPixSiz, nPixSiz))); + aTmpRect.AdjustLeft(-(aMore.Width())); + aTmpRect.AdjustRight(aMore.Width()); + aTmpRect.AdjustTop(-(aMore.Height())); + aTmpRect.AdjustBottom(aMore.Height()); + InvalidateOneWin(*pWin->GetOutDev(), aTmpRect); + } + if (bAnchorChg) + pOLV->SetAnchorMode(eNewAnchor); + if (bColorChg) + pOLV->SetBackgroundColor(aNewColor); + + pOLV->SetOutputArea( + aTextEditArea); // because otherwise, we're not re-anchoring correctly + ImpInvalidateOutlinerView(*pOLV); + } + mpTextEditOutlinerView->ShowCursor(); + } + } + ImpMakeTextCursorAreaVisible(); +} + +namespace +{ +class TextEditFrameOverlayObject; +class TextEditHighContrastOverlaySelection; + +/** + Helper class to visualize the content of an active EditView as an + OverlayObject. These objects work with Primitives and are handled + from the OverlayManager(s) in place as needed. + + It allows complete visualization of the content of the active + EditView without the need of Invalidates triggered by the EditView + and thus avoiding potentially expensive repaints by using the + automatically buffered Overlay mechanism. + + It buffers as much as possible locally and *only* triggers a real + change (see call to objectChange()) when really needed. + */ +class TextEditOverlayObject : public sdr::overlay::OverlayObject +{ +protected: + /// local access to associated sdr::overlay::OverlaySelection + std::unique_ptr<sdr::overlay::OverlaySelection> mxOverlayTransparentSelection; + std::unique_ptr<TextEditHighContrastOverlaySelection> mxOverlayHighContrastSelection; + std::unique_ptr<TextEditFrameOverlayObject> mxOverlayFrame; + + /// local definition depends on active OutlinerView + OutlinerView& mrOutlinerView; + + /// geometry definitions with buffering + basegfx::B2DRange maLastRange; + basegfx::B2DRange maRange; + + /// text content definitions with buffering + drawinglayer::primitive2d::Primitive2DContainer maTextPrimitives; + drawinglayer::primitive2d::Primitive2DContainer maLastTextPrimitives; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView); + virtual ~TextEditOverlayObject() override; + + sdr::overlay::OverlayObject* getOverlaySelection(); + sdr::overlay::OverlayObject* getOverlayFrame(); + + const OutlinerView& getOutlinerView() const { return mrOutlinerView; } + + /// override to check conditions for last createOverlayObjectPrimitive2DSequence + virtual drawinglayer::primitive2d::Primitive2DContainer + getOverlayObjectPrimitive2DSequence() const override; + + // data write access. In this OverlayObject we only have the + // callback that triggers detecting if something *has* changed + void checkDataChange(const basegfx::B2DRange& rMinTextEditArea); + void checkSelectionChange(); + + const basegfx::B2DRange& getRange() const { return maRange; } + const drawinglayer::primitive2d::Primitive2DContainer& getTextPrimitives() const + { + return maTextPrimitives; + } +}; + +class TextEditFrameOverlayObject : public sdr::overlay::OverlayObject +{ +private: + const TextEditOverlayObject& mrTextEditOverlayObject; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditFrameOverlayObject(const TextEditOverlayObject& rTextEditOverlayObject); + using sdr::overlay::OverlayObject::objectChange; + virtual ~TextEditFrameOverlayObject() override; +}; + +class TextEditHighContrastOverlaySelection : public sdr::overlay::OverlayObject +{ +private: + const TextEditOverlayObject& mrTextEditOverlayObject; + std::vector<basegfx::B2DRange> maRanges; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditHighContrastOverlaySelection(const TextEditOverlayObject& rTextEditOverlayObject); + void setRanges(std::vector<basegfx::B2DRange>&& rNew); + virtual ~TextEditHighContrastOverlaySelection() override; +}; + +TextEditHighContrastOverlaySelection::TextEditHighContrastOverlaySelection( + const TextEditOverlayObject& rTextEditOverlayObject) + : OverlayObject(rTextEditOverlayObject.getBaseColor()) + , mrTextEditOverlayObject(rTextEditOverlayObject) +{ + allowAntiAliase(rTextEditOverlayObject.allowsAntiAliase()); + // use selection colors in HighContrast mode + mbHighContrastSelection = true; +} + +void TextEditHighContrastOverlaySelection::setRanges(std::vector<basegfx::B2DRange>&& rNew) +{ + if (rNew != maRanges) + { + maRanges = std::move(rNew); + objectChange(); + } +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditHighContrastOverlaySelection::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + size_t nCount = maRanges.size(); + + if (nCount) + { + basegfx::B2DPolyPolygon aClipPolyPolygon; + + basegfx::BColor aRGBColor(getBaseColor().getBColor()); + + for (size_t a = 0; a < nCount; ++a) + aClipPolyPolygon.append(basegfx::utils::createPolygonFromRect(maRanges[a])); + + // This is used in high contrast mode, we will render the selection + // with the bg forced to the selection Highlight color and the fg color + // forced to the HighlightText color + aRetval.append(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(aClipPolyPolygon.getB2DRange())), + aRGBColor))); + aRetval.append(mrTextEditOverlayObject.getTextPrimitives()); + aRetval.append(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::MaskPrimitive2D(aClipPolyPolygon, std::move(aRetval)))); + } + + return aRetval; +} + +TextEditHighContrastOverlaySelection::~TextEditHighContrastOverlaySelection() +{ + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +sdr::overlay::OverlayObject* TextEditOverlayObject::getOverlaySelection() +{ + if (mxOverlayTransparentSelection) + return mxOverlayTransparentSelection.get(); + return mxOverlayHighContrastSelection.get(); +} + +sdr::overlay::OverlayObject* TextEditOverlayObject::getOverlayFrame() +{ + if (!mxOverlayFrame) + mxOverlayFrame.reset(new TextEditFrameOverlayObject(*this)); + return mxOverlayFrame.get(); +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + // add buffered TextPrimitives + aRetval.append(maTextPrimitives); + + return aRetval; +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditFrameOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + /// outer frame visualization + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + const sal_uInt16 nPixSiz(mrTextEditOverlayObject.getOutlinerView().GetInvalidateMore() - 1); + + aRetval.push_back(new drawinglayer::primitive2d::OverlayRectanglePrimitive( + mrTextEditOverlayObject.getRange(), getBaseColor().getBColor(), fTransparence, + std::max(6, nPixSiz - 2), // grow + 0.0, // shrink + 0.0)); + + return aRetval; +} + +TextEditOverlayObject::TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView) + : OverlayObject(rColor) + , mrOutlinerView(rOutlinerView) +{ + // no AA for TextEdit overlay + allowAntiAliase(false); + + // create local OverlaySelection - this is an integral part of EditText + // visualization + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + mxOverlayHighContrastSelection.reset(new TextEditHighContrastOverlaySelection(*this)); + } + else + { + std::vector<basegfx::B2DRange> aEmptySelection{}; + mxOverlayTransparentSelection.reset(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, rColor, std::move(aEmptySelection), true)); + } +} + +TextEditOverlayObject::~TextEditOverlayObject() +{ + mxOverlayTransparentSelection.reset(); + mxOverlayHighContrastSelection.reset(); + + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +TextEditFrameOverlayObject::TextEditFrameOverlayObject( + const TextEditOverlayObject& rTextEditOverlayObject) + : OverlayObject(rTextEditOverlayObject.getBaseColor()) + , mrTextEditOverlayObject(rTextEditOverlayObject) +{ + allowAntiAliase(rTextEditOverlayObject.allowsAntiAliase()); + // use selection colors in HighContrast mode + mbHighContrastSelection = true; +} + +TextEditFrameOverlayObject::~TextEditFrameOverlayObject() +{ + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditOverlayObject::getOverlayObjectPrimitive2DSequence() const +{ + if (!getPrimitive2DSequence().empty()) + { + if (!maRange.equal(maLastRange) || maLastTextPrimitives != maTextPrimitives) + { + // conditions of last local decomposition have changed, delete to force new evaluation + const_cast<TextEditOverlayObject*>(this)->resetPrimitive2DSequence(); + } + } + + if (getPrimitive2DSequence().empty()) + { + // remember new buffered values + const_cast<TextEditOverlayObject*>(this)->maLastRange = maRange; + const_cast<TextEditOverlayObject*>(this)->maLastTextPrimitives = maTextPrimitives; + } + + // call base implementation + return OverlayObject::getOverlayObjectPrimitive2DSequence(); +} + +void TextEditOverlayObject::checkDataChange(const basegfx::B2DRange& rMinTextEditArea) +{ + bool bObjectChange(false); + + // check current range + const tools::Rectangle aOutArea(mrOutlinerView.GetOutputArea()); + basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aOutArea); + aNewRange.expand(rMinTextEditArea); + + if (aNewRange != maRange) + { + maRange = aNewRange; + bObjectChange = true; + } + + // check if text primitives did change + SdrOutliner* pSdrOutliner = dynamic_cast<SdrOutliner*>(getOutlinerView().GetOutliner()); + + if (pSdrOutliner) + { + // get TextPrimitives directly from active Outliner + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + basegfx::B2DRange aClipRange; + drawinglayer::primitive2d::Primitive2DContainer aNewTextPrimitives; + + // active Outliner is always in unified oriented coordinate system (currently) + // so just translate to TopLeft of visible Range. Keep in mind that top-left + // depends on vertical text and top-to-bottom text attributes + const tools::Rectangle aVisArea(mrOutlinerView.GetVisArea()); + const bool bVerticalWriting(pSdrOutliner->IsVertical()); + const bool bTopToBottom(pSdrOutliner->IsTopToBottom()); + const double fStartInX(bVerticalWriting && bTopToBottom + ? aOutArea.Right() - aVisArea.Left() + : aOutArea.Left() - aVisArea.Left()); + const double fStartInY(bVerticalWriting && !bTopToBottom + ? aOutArea.Bottom() - aVisArea.Top() + : aOutArea.Top() - aVisArea.Top()); + + aNewTransformB.translate(fStartInX, fStartInY); + + // get the current TextPrimitives. This is the most expensive part + // of this mechanism, it *may* be possible to buffer layouted + // primitives per ParaPortion with/in/dependent on the EditEngine + // content if needed. For now, get and compare + SdrTextObj::impDecomposeBlockTextPrimitiveDirect( + aNewTextPrimitives, *pSdrOutliner, aNewTransformA, aNewTransformB, aClipRange); + + if (aNewTextPrimitives != maTextPrimitives) + { + maTextPrimitives = std::move(aNewTextPrimitives); + bObjectChange = true; + } + } + + if (bObjectChange) + { + // if there really *was* a change signal the OverlayManager to + // refresh this object's visualization + objectChange(); + + if (mxOverlayFrame) + mxOverlayFrame->objectChange(); + + // on data change, always do a SelectionChange, too + // since the selection is an integral part of text visualization + checkSelectionChange(); + } +} + +void TextEditOverlayObject::checkSelectionChange() +{ + if (!(getOverlaySelection() && getOverlayManager())) + return; + + std::vector<tools::Rectangle> aLogicRects; + std::vector<basegfx::B2DRange> aLogicRanges; + const Size aLogicPixel(getOverlayManager()->getOutputDevice().PixelToLogic(Size(1, 1))); + + // get logic selection + getOutlinerView().GetSelectionRectangles(aLogicRects); + + aLogicRanges.reserve(aLogicRects.size()); + for (const auto& aRect : aLogicRects) + { + // convert from logic Rectangles to logic Ranges, do not forget to add + // one Unit (in this case logical units for one pixel, pre-calculated) + aLogicRanges.emplace_back( + aRect.Left() - aLogicPixel.Width(), aRect.Top() - aLogicPixel.Height(), + aRect.Right() + aLogicPixel.Width(), aRect.Bottom() + aLogicPixel.Height()); + } + + if (mxOverlayTransparentSelection) + mxOverlayTransparentSelection->setRanges(std::move(aLogicRanges)); + else + mxOverlayHighContrastSelection->setRanges(std::move(aLogicRanges)); +} +} // end of anonymous namespace + +// TextEdit + +// callback from the active EditView, forward to evtl. existing instances of the +// TextEditOverlayObject(s). This will additionally update the selection which +// is an integral part of the text visualization +void SdrObjEditView::EditViewInvalidate(const tools::Rectangle&) +{ + if (!IsTextEdit()) + return; + + // MinTextRange may have changed. Forward it, too + const basegfx::B2DRange aMinTextRange + = vcl::unotools::b2DRectangleFromRectangle(aMinTextEditArea); + + for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++) + { + TextEditOverlayObject* pCandidate + = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a)); + + if (pCandidate) + { + pCandidate->checkDataChange(aMinTextRange); + } + } +} + +// callback from the active EditView, forward to evtl. existing instances of the +// TextEditOverlayObject(s). This cvall *only* updates the selection visualization +// which is e.g. used when only the selection is changed, but not the text +void SdrObjEditView::EditViewSelectionChange() +{ + if (!IsTextEdit()) + return; + + for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++) + { + TextEditOverlayObject* pCandidate + = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a)); + + if (pCandidate) + { + pCandidate->checkSelectionChange(); + } + } +} + +OutputDevice& SdrObjEditView::EditViewOutputDevice() const { return *mpTextEditWin->GetOutDev(); } + +Point SdrObjEditView::EditViewPointerPosPixel() const +{ + return mpTextEditWin->GetPointerPosPixel(); +} + +css::uno::Reference<css::datatransfer::clipboard::XClipboard> SdrObjEditView::GetClipboard() const +{ + if (!mpTextEditWin) + return nullptr; + return mpTextEditWin->GetClipboard(); +} + +css::uno::Reference<css::datatransfer::dnd::XDropTarget> SdrObjEditView::GetDropTarget() +{ + if (!mpTextEditWin) + return nullptr; + return mpTextEditWin->GetDropTarget(); +} + +void SdrObjEditView::EditViewInputContext(const InputContext& rInputContext) +{ + if (!mpTextEditWin) + return; + mpTextEditWin->SetInputContext(rInputContext); +} + +void SdrObjEditView::EditViewCursorRect(const tools::Rectangle& rRect, int nExtTextInputWidth) +{ + if (!mpTextEditWin) + return; + mpTextEditWin->SetCursorRect(&rRect, nExtTextInputWidth); +} + +void SdrObjEditView::TextEditDrawing(SdrPaintWindow& rPaintWindow) +{ + if (!comphelper::LibreOfficeKit::isActive()) + { + // adapt all TextEditOverlayObject(s), so call EditViewInvalidate() + // to update accordingly (will update selection, too). Suppress new + // stuff when LibreOfficeKit is active + EditViewInvalidate(tools::Rectangle()); + } + else + { + // draw old text edit stuff + if (IsTextEdit()) + { + const SdrOutliner* pActiveOutliner = GetTextEditOutliner(); + + if (pActiveOutliner) + { + const sal_uInt32 nViewCount(pActiveOutliner->GetViewCount()); + + if (nViewCount) + { + const vcl::Region& rRedrawRegion = rPaintWindow.GetRedrawRegion(); + const tools::Rectangle aCheckRect(rRedrawRegion.GetBoundRect()); + + for (sal_uInt32 i(0); i < nViewCount; i++) + { + OutlinerView* pOLV = pActiveOutliner->GetView(i); + + // If rPaintWindow knows that the output device is a render + // context and is aware of the underlying vcl::Window, + // compare against that; that's how double-buffering can + // still find the matching OutlinerView. + OutputDevice* pOutputDevice = rPaintWindow.GetWindow() + ? rPaintWindow.GetWindow()->GetOutDev() + : &rPaintWindow.GetOutputDevice(); + if (pOLV->GetWindow()->GetOutDev() == pOutputDevice + || comphelper::LibreOfficeKit::isActive()) + { + ImpPaintOutlinerView(*pOLV, aCheckRect, + rPaintWindow.GetTargetOutputDevice()); + return; + } + } + } + } + } + } +} + +void SdrObjEditView::ImpPaintOutlinerView(OutlinerView& rOutlView, const tools::Rectangle& rRect, + OutputDevice& rTargetDevice) const +{ + const SdrTextObj* pText = GetTextEditObject(); + bool bTextFrame(pText && pText->IsTextFrame()); + bool bFitToSize(mpTextEditOutliner->GetControlWord() & EEControlBits::STRETCHING); + bool bModified(mpTextEditOutliner->IsModified()); + tools::Rectangle aBlankRect(rOutlView.GetOutputArea()); + aBlankRect.Union(aMinTextEditArea); + tools::Rectangle aPixRect(rTargetDevice.LogicToPixel(aBlankRect)); + + // in the tiled rendering case, the setup is incomplete, and we very + // easily get an empty rRect on input - that will cause that everything is + // clipped; happens in case of editing text inside a shape in Calc. + // FIXME would be better to complete the setup so that we don't get an + // empty rRect here + if (!comphelper::LibreOfficeKit::isActive() || !rRect.IsEmpty()) + aBlankRect.Intersection(rRect); + + rOutlView.GetOutliner()->SetUpdateLayout(true); // Bugfix #22596# + rOutlView.Paint(aBlankRect, &rTargetDevice); + + if (!bModified) + { + mpTextEditOutliner->ClearModifyFlag(); + } + + if (bTextFrame && !bFitToSize) + { + // completely reworked to use primitives; this ensures same look and functionality + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> xProcessor( + drawinglayer::processor2d::createProcessor2DFromOutputDevice(rTargetDevice, + aViewInformation2D)); + + const bool bMapModeEnabled(rTargetDevice.IsMapModeEnabled()); + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aPixRect); + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + const sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1); + const drawinglayer::primitive2d::Primitive2DReference xReference( + new drawinglayer::primitive2d::OverlayRectanglePrimitive( + aRange, aHilightColor.getBColor(), fTransparence, std::max(6, nPixSiz - 2), // grow + 0.0, // shrink + 0.0)); + const drawinglayer::primitive2d::Primitive2DContainer aSequence{ xReference }; + + rTargetDevice.EnableMapMode(false); + xProcessor->process(aSequence); + rTargetDevice.EnableMapMode(bMapModeEnabled); + } + + rOutlView.ShowCursor(/*bGotoCursor=*/true, /*bActivate=*/true); +} + +void SdrObjEditView::ImpInvalidateOutlinerView(OutlinerView const& rOutlView) const +{ + vcl::Window* pWin = rOutlView.GetWindow(); + + if (!pWin) + return; + + const SdrTextObj* pText = GetTextEditObject(); + bool bTextFrame(pText && pText->IsTextFrame()); + bool bFitToSize(pText && pText->IsFitToSize()); + + if (!bTextFrame || bFitToSize) + return; + + tools::Rectangle aBlankRect(rOutlView.GetOutputArea()); + aBlankRect.Union(aMinTextEditArea); + tools::Rectangle aPixRect(pWin->LogicToPixel(aBlankRect)); + sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1); + + aPixRect.AdjustLeft(-1); + aPixRect.AdjustTop(-1); + aPixRect.AdjustRight(1); + aPixRect.AdjustBottom(1); + + { + // limit xPixRect because of driver problems when pixel coordinates are too far out + Size aMaxXY(pWin->GetOutputSizePixel()); + tools::Long a(2 * nPixSiz); + tools::Long nMaxX(aMaxXY.Width() + a); + tools::Long nMaxY(aMaxXY.Height() + a); + + if (aPixRect.Left() < -a) + aPixRect.SetLeft(-a); + if (aPixRect.Top() < -a) + aPixRect.SetTop(-a); + if (aPixRect.Right() > nMaxX) + aPixRect.SetRight(nMaxX); + if (aPixRect.Bottom() > nMaxY) + aPixRect.SetBottom(nMaxY); + } + + tools::Rectangle aOuterPix(aPixRect); + aOuterPix.AdjustLeft(-nPixSiz); + aOuterPix.AdjustTop(-nPixSiz); + aOuterPix.AdjustRight(nPixSiz); + aOuterPix.AdjustBottom(nPixSiz); + + bool bMapModeEnabled(pWin->IsMapModeEnabled()); + pWin->EnableMapMode(false); + pWin->Invalidate(aOuterPix); + pWin->EnableMapMode(bMapModeEnabled); +} + +OutlinerView* SdrObjEditView::ImpMakeOutlinerView(vcl::Window* pWin, OutlinerView* pGivenView, + SfxViewShell* pViewShell) const +{ + // background + Color aBackground(GetTextEditBackgroundColor(*this)); + rtl::Reference<SdrTextObj> pText = mxWeakTextEditObj.get(); + bool bTextFrame = pText != nullptr && pText->IsTextFrame(); + bool bContourFrame = pText != nullptr && pText->IsContourTextFrame(); + // create OutlinerView + OutlinerView* pOutlView = pGivenView; + mpTextEditOutliner->SetUpdateLayout(false); + + if (pOutlView == nullptr) + { + pOutlView = new OutlinerView(mpTextEditOutliner.get(), pWin); + } + else + { + pOutlView->SetWindow(pWin); + } + + if (mbNegativeX) + pOutlView->GetEditView().SetNegativeX(mbNegativeX); + + // disallow scrolling + EVControlBits nStat = pOutlView->GetControlWord(); + nStat &= ~EVControlBits::AUTOSCROLL; + // AutoViewSize only if not ContourFrame. + if (!bContourFrame) + nStat |= EVControlBits::AUTOSIZE; + if (bTextFrame) + { + sal_uInt16 nPixSiz = maHdlList.GetHdlSize() * 2 + 1; + nStat |= EVControlBits::INVONEMORE; + pOutlView->SetInvalidateMore(nPixSiz); + } + pOutlView->SetControlWord(nStat); + pOutlView->SetBackgroundColor(aBackground); + + // In case we're in the process of constructing a new view shell, + // SfxViewShell::Current() may still point to the old one. So if possible, + // depend on the application owning this draw view to provide the view + // shell. + SfxViewShell* pSfxViewShell = pViewShell ? pViewShell : GetSfxViewShell(); + pOutlView->RegisterViewShell(pSfxViewShell ? pSfxViewShell : SfxViewShell::Current()); + + if (pText != nullptr) + { + pOutlView->SetAnchorMode(pText->GetOutlinerViewAnchorMode()); + mpTextEditOutliner->SetFixedCellHeight( + pText->GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + // do update before setting output area so that aTextEditArea can be recalculated + mpTextEditOutliner->SetUpdateLayout(true); + pOutlView->SetOutputArea(aTextEditArea); + ImpInvalidateOutlinerView(*pOutlView); + return pOutlView; +} + +IMPL_LINK(SdrObjEditView, ImpOutlinerStatusEventHdl, EditStatus&, rEditStat, void) +{ + if (mpTextEditOutliner) + { + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj) + { + pTextObj->onEditOutlinerStatusEvent(&rEditStat); + } + } +} + +void SdrObjEditView::ImpChainingEventHdl() +{ + if (!mpTextEditOutliner) + return; + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + OutlinerView* pOLV = GetTextEditOutlinerView(); + if (pTextObj && pOLV) + { + TextChain* pTextChain = pTextObj->GetTextChain(); + + // XXX: IsChainable and GetNilChainingEvent are a bit mixed up atm + if (!pTextObj->IsChainable()) + { + return; + } + // This is true during an underflow-caused overflow (with pEdtOutl->SetText()) + if (pTextChain->GetNilChainingEvent(pTextObj.get())) + { + return; + } + + // We prevent to trigger further handling of overflow/underflow for pTextObj + pTextChain->SetNilChainingEvent(pTextObj.get(), true); // XXX + + // Save previous selection pos // NOTE: It must be done to have the right CursorEvent in KeyInput + pTextChain->SetPreChainingSel(pTextObj.get(), pOLV->GetSelection()); + //maPreChainingSel = new ESelection(pOLV->GetSelection()); + + // Handling Undo + const int nText = 0; // XXX: hardcoded index (SdrTextObj::getText handles only 0) + + const bool bUndoEnabled = IsUndoEnabled(); + std::unique_ptr<SdrUndoObjSetText> pTxtUndo; + if (bUndoEnabled) + pTxtUndo.reset( + dynamic_cast<SdrUndoObjSetText*>(GetModel() + .GetSdrUndoFactory() + .CreateUndoObjectSetText(*pTextObj, nText) + .release())); + + // trigger actual chaining + pTextObj->onChainingEvent(); + + if (pTxtUndo) + { + pTxtUndo->AfterSetText(); + if (!pTxtUndo->IsDifferent()) + { + pTxtUndo.reset(); + } + } + + if (pTxtUndo) + AddUndo(std::move(pTxtUndo)); + + //maCursorEvent = new CursorChainingEvent(pTextChain->GetCursorEvent(pTextObj)); + //SdrTextObj *pNextLink = pTextObj->GetNextLinkInChain(); + + // NOTE: Must be called. Don't let the function return if you set it to true and not reset it + pTextChain->SetNilChainingEvent(pTextObj.get(), false); + } + else + { + // XXX + SAL_INFO("svx.chaining", "[OnChaining] No Edit Outliner View"); + } +} + +IMPL_LINK_NOARG(SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl, LinkParamNone*, void) +{ + SdrTextObj* pTextObj = GetTextEditObject(); + if (!pTextObj) + return; + ImpChainingEventHdl(); + TextChainCursorManager aCursorManager(this, pTextObj); + ImpMoveCursorAfterChainingEvent(&aCursorManager); +} + +void SdrObjEditView::ImpMoveCursorAfterChainingEvent(TextChainCursorManager* pCursorManager) +{ + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + + if (!pTextObj || !pCursorManager) + return; + + // Check if it has links to move it to + if (!pTextObj || !pTextObj->IsChainable()) + return; + + TextChain* pTextChain = pTextObj->GetTextChain(); + ESelection aNewSel = pTextChain->GetPostChainingSel(pTextObj.get()); + + pCursorManager->HandleCursorEventAfterChaining(pTextChain->GetCursorEvent(pTextObj.get()), + aNewSel); + + // Reset event + pTextChain->SetCursorEvent(pTextObj.get(), CursorChainingEvent::NULL_EVENT); +} + +IMPL_LINK(SdrObjEditView, ImpOutlinerCalcFieldValueHdl, EditFieldInfo*, pFI, void) +{ + bool bOk = false; + OUString& rStr = pFI->GetRepresentation(); + rStr.clear(); + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj != nullptr) + { + std::optional<Color> pTxtCol; + std::optional<Color> pFldCol; + std::optional<FontLineStyle> pFldLineStyle; + bOk = pTextObj->CalcFieldValue(pFI->GetField(), pFI->GetPara(), pFI->GetPos(), true, + pTxtCol, pFldCol, pFldLineStyle, rStr); + if (bOk) + { + if (pTxtCol) + { + pFI->SetTextColor(*pTxtCol); + } + if (pFldLineStyle) + { + pFI->SetFontLineStyle(*pFldLineStyle); + } + if (pFldCol) + { + pFI->SetFieldColor(*pFldCol); + } + else + { + pFI->SetFieldColor(COL_LIGHTGRAY); // TODO: remove this later on (357) + } + } + } + Outliner& rDrawOutl = GetModel().GetDrawOutliner(pTextObj.get()); + Link<EditFieldInfo*, void> aDrawOutlLink = rDrawOutl.GetCalcFieldValueHdl(); + if (!bOk && aDrawOutlLink.IsSet()) + { + aDrawOutlLink.Call(pFI); + bOk = !rStr.isEmpty(); + } + if (!bOk) + { + aOldCalcFieldValueLink.Call(pFI); + } +} + +IMPL_LINK_NOARG(SdrObjEditView, EndTextEditHdl, SdrUndoManager*, void) { SdrEndTextEdit(); } + +// Default implementation - null UndoManager +std::unique_ptr<SdrUndoManager> SdrObjEditView::createLocalTextUndoManager() +{ + SAL_WARN("svx", "SdrObjEditView::createLocalTextUndoManager needs to be overridden"); + return std::unique_ptr<SdrUndoManager>(); +} + +bool SdrObjEditView::SdrBeginTextEdit(SdrObject* pObj_, SdrPageView* pPV, vcl::Window* pWin, + bool bIsNewObj, SdrOutliner* pGivenOutliner, + OutlinerView* pGivenOutlinerView, bool bDontDeleteOutliner, + bool bOnlyOneView, bool bGrabFocus) +{ + // FIXME cannot be an assert() yet, the code is not ready for that; + // eg. press F7 in Impress when you are inside a text object with spelling + // mistakes => boom; and it is unclear how to avoid that + SAL_WARN_IF(IsTextEdit(), "svx", "SdrBeginTextEdit called when IsTextEdit() is already true."); + // FIXME this encourages all sorts of bad habits and should be removed + SdrEndTextEdit(); + + SdrTextObj* pObj = DynCastSdrTextObj(pObj_); + if (!pObj) + return false; // currently only possible with text objects + + if (bGrabFocus && pWin) + { + // attention, this call may cause an EndTextEdit() call to this view + pWin->GrabFocus(); // to force the cursor into the edit view + } + + mbTextEditDontDelete = bDontDeleteOutliner && pGivenOutliner != nullptr; + mbTextEditOnlyOneView = bOnlyOneView; + mbTextEditNewObj = bIsNewObj; + const sal_uInt32 nWinCount(PaintWindowCount()); + + bool bBrk(false); + + if (!pWin) + { + for (sal_uInt32 i = 0; i < nWinCount && !pWin; i++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(i); + + if (OUTDEV_WINDOW == pPaintWindow->GetOutputDevice().GetOutDevType()) + { + pWin = pPaintWindow->GetOutputDevice().GetOwnerWindow(); + } + } + + // break, when no window exists + if (!pWin) + { + bBrk = true; + } + } + + if (!bBrk && !pPV) + { + pPV = GetSdrPageView(); + + // break, when no PageView for the object exists + if (!pPV) + { + bBrk = true; + } + } + + // no TextEdit on objects in locked Layer + if (pPV && pPV->GetLockedLayers().IsSet(pObj->GetLayer())) + { + bBrk = true; + } + + if (mpTextEditOutliner) + { + OSL_FAIL("SdrObjEditView::SdrBeginTextEdit(): Old Outliner still exists."); + mpTextEditOutliner.reset(); + } + + if (!bBrk) + { + mpTextEditWin = pWin; + mpTextEditPV = pPV; + mxWeakTextEditObj = pObj; + if (pGivenOutliner) + { + mpTextEditOutliner.reset(pGivenOutliner); + pGivenOutliner = nullptr; // so we don't delete it on the error path + } + else + mpTextEditOutliner + = SdrMakeOutliner(OutlinerMode::TextObject, pObj->getSdrModelFromSdrObject()); + + { + mpTextEditOutliner->ForceAutoColor(SvtAccessibilityOptions::GetIsAutomaticFontColor()); + } + + aOldCalcFieldValueLink = mpTextEditOutliner->GetCalcFieldValueHdl(); + // FieldHdl has to be set by SdrBeginTextEdit, because this call an UpdateFields + mpTextEditOutliner->SetCalcFieldValueHdl( + LINK(this, SdrObjEditView, ImpOutlinerCalcFieldValueHdl)); + mpTextEditOutliner->SetBeginPasteOrDropHdl(LINK(this, SdrObjEditView, BeginPasteOrDropHdl)); + mpTextEditOutliner->SetEndPasteOrDropHdl(LINK(this, SdrObjEditView, EndPasteOrDropHdl)); + + // It is just necessary to make the visualized page known. Set it. + mpTextEditOutliner->setVisualizedPage(pPV->GetPage()); + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + mpTextEditOutliner->SetTextObjNoInit(pTextObj.get()); + + if (pTextObj->BegTextEdit(*mpTextEditOutliner)) + { + // switch off any running TextAnimations + pTextObj->SetTextAnimationAllowed(false); + + // remember old cursor + if (mpTextEditOutliner->GetViewCount() != 0) + { + mpTextEditOutliner->RemoveView(static_cast<size_t>(0)); + } + + // Determine EditArea via TakeTextEditArea. + // TODO: This could theoretically be left out, because TakeTextRect() calculates the aTextEditArea, + // but aMinTextEditArea has to happen, too (therefore leaving this in right now) + pTextObj->TakeTextEditArea(nullptr, nullptr, &aTextEditArea, &aMinTextEditArea); + + tools::Rectangle aTextRect; + tools::Rectangle aAnchorRect; + pTextObj->TakeTextRect(*mpTextEditOutliner, aTextRect, true, + &aAnchorRect /* Give true here, not false */); + + if (!pTextObj->IsContourTextFrame()) + { + // FitToSize not together with ContourFrame, for now + if (pTextObj->IsFitToSize()) + aTextRect = aAnchorRect; + } + + aTextEditArea = aTextRect; + + // add possible GridOffset to up-to-now view-independent EditAreas + basegfx::B2DVector aGridOffset(0.0, 0.0); + if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj.get(), pPV)) + { + const Point aOffset(basegfx::fround(aGridOffset.getX()), + basegfx::fround(aGridOffset.getY())); + + aTextEditArea += aOffset; + aMinTextEditArea += aOffset; + } + + Point aPvOfs(pTextObj->GetTextEditOffset()); + aTextEditArea.Move(aPvOfs.X(), aPvOfs.Y()); + aMinTextEditArea.Move(aPvOfs.X(), aPvOfs.Y()); + pTextEditCursorBuffer = pWin->GetCursor(); + + maHdlList.SetMoveOutside(true); + + // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary + // to call AdjustMarkHdl() always. + AdjustMarkHdl(); + + mpTextEditOutlinerView = ImpMakeOutlinerView(pWin, pGivenOutlinerView); + + if (!comphelper::LibreOfficeKit::isActive() && mpTextEditOutlinerView) + { + // activate visualization of EditView on Overlay, suppress when + // LibreOfficeKit is active + mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(this); + + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const SdrTextObj* pText = GetTextEditObject(); + // show for cases like tdf#94223 but not for table cells like tdf#151311 + const bool bVisualizeSurroundingFrame( + pText && pText->GetObjIdentifier() != SdrObjKind::Table); + SdrPageView* pPageView = GetSdrPageView(); + + if (pPageView) + { + 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()) + { + std::unique_ptr<TextEditOverlayObject> pNewTextEditOverlayObject( + new TextEditOverlayObject(aHilightColor, + *mpTextEditOutlinerView)); + + xManager->add(*pNewTextEditOverlayObject); + if (bVisualizeSurroundingFrame) + xManager->add(*pNewTextEditOverlayObject->getOverlayFrame()); + xManager->add(*pNewTextEditOverlayObject->getOverlaySelection()); + + maTEOverlayGroup.append(std::move(pNewTextEditOverlayObject)); + } + } + } + } + } + + // check if this view is already inserted + size_t i2, nCount = mpTextEditOutliner->GetViewCount(); + for (i2 = 0; i2 < nCount; i2++) + { + if (mpTextEditOutliner->GetView(i2) == mpTextEditOutlinerView) + break; + } + + if (i2 == nCount) + mpTextEditOutliner->InsertView(mpTextEditOutlinerView, 0); + + maHdlList.SetMoveOutside(false); + maHdlList.SetMoveOutside(true); + + // register all windows as OutlinerViews with the Outliner + if (!bOnlyOneView) + { + for (sal_uInt32 i = 0; i < nWinCount; i++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(i); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if (&rOutDev != pWin->GetOutDev() && OUTDEV_WINDOW == rOutDev.GetOutDevType()) + { + OutlinerView* pOutlView + = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr); + mpTextEditOutliner->InsertView(pOutlView, static_cast<sal_uInt16>(i)); + } + } + + if (comphelper::LibreOfficeKit::isActive()) + { + // Register an outliner view for all other sdr views that + // show the same page, so that when the text edit changes, + // all interested windows get an invalidation. + SdrViewIter::ForAllViews(pObj->getSdrPageFromSdrObject(), [this, &pWin]( + SdrView* pView) { + if (pView == this) + return; + + for (sal_uInt32 nViewPaintWindow = 0; + nViewPaintWindow < pView->PaintWindowCount(); ++nViewPaintWindow) + { + SdrPaintWindow* pPaintWindow = pView->GetPaintWindow(nViewPaintWindow); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if (&rOutDev != pWin->GetOutDev() + && OUTDEV_WINDOW == rOutDev.GetOutDevType()) + { + OutlinerView* pOutlView + = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr); + pOutlView->HideCursor(); + rOutDev.GetOwnerWindow()->SetCursor(nullptr); + mpTextEditOutliner->InsertView(pOutlView); + } + } + }); + } + } + + mpTextEditOutlinerView->ShowCursor(); + mpTextEditOutliner->SetStatusEventHdl( + LINK(this, SdrObjEditView, ImpOutlinerStatusEventHdl)); + if (pTextObj->IsChainable()) + { + mpTextEditOutlinerView->SetEndCutPasteLinkHdl( + LINK(this, SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl)); + } + + mpTextEditOutliner->ClearModifyFlag(); + + if (pTextObj->IsFitToSize()) + { + pWin->Invalidate(aTextEditArea); + } + + SdrHint aHint(SdrHintKind::BeginEdit, *pTextObj); + GetModel().Broadcast(aHint); + + mpTextEditOutliner->setVisualizedPage(nullptr); + + if (mxSelectionController.is()) + mxSelectionController->onSelectionHasChanged(); + + if (IsUndoEnabled() && !GetModel().GetDisableTextEditUsesCommonUndoManager()) + { + SdrUndoManager* pSdrUndoManager = nullptr; + mpLocalTextEditUndoManager = createLocalTextUndoManager(); + + if (mpLocalTextEditUndoManager) + pSdrUndoManager = mpLocalTextEditUndoManager.get(); + + if (pSdrUndoManager) + { + // we have an outliner, undo manager and it's an EditUndoManager, exchange + // the document undo manager and the default one from the outliner and tell + // it that text edit starts by setting a callback if it needs to end text edit mode. + assert(nullptr == mpOldTextEditUndoManager); + + mpOldTextEditUndoManager = mpTextEditOutliner->SetUndoManager(pSdrUndoManager); + pSdrUndoManager->SetEndTextEditHdl(LINK(this, SdrObjEditView, EndTextEditHdl)); + } + else + { + OSL_ENSURE(false, + "The document undo manager is not derived from SdrUndoManager (!)"); + } + } + + return true; // ran fine, let TextEdit run now + } + else + { + mpTextEditOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink); + mpTextEditOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + mpTextEditOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + } + } + if (mpTextEditOutliner != nullptr) + { + mpTextEditOutliner->setVisualizedPage(nullptr); + } + + // something went wrong... + if (!bDontDeleteOutliner) + { + delete pGivenOutliner; + if (pGivenOutlinerView != nullptr) + { + delete pGivenOutlinerView; + pGivenOutlinerView = nullptr; + } + } + mpTextEditOutliner.reset(); + + mpTextEditOutlinerView = nullptr; + mxWeakTextEditObj.clear(); + mpTextEditPV = nullptr; + mpTextEditWin = nullptr; + maHdlList.SetMoveOutside(false); + + return false; +} + +SdrEndTextEditKind SdrObjEditView::SdrEndTextEdit(bool bDontDeleteReally) +{ + SdrEndTextEditKind eRet = SdrEndTextEditKind::Unchanged; + rtl::Reference<SdrTextObj> pTEObj = mxWeakTextEditObj.get(); + vcl::Window* pTEWin = mpTextEditWin; + OutlinerView* pTEOutlinerView = mpTextEditOutlinerView; + vcl::Cursor* pTECursorBuffer = pTextEditCursorBuffer; + SdrUndoManager* pUndoEditUndoManager = nullptr; + bool bNeedToUndoSavedRedoTextEdit(false); + + if (IsUndoEnabled() && pTEObj && mpTextEditOutliner + && !GetModel().GetDisableTextEditUsesCommonUndoManager()) + { + // change back the UndoManager to the remembered original one + SfxUndoManager* pOriginal = mpTextEditOutliner->SetUndoManager(mpOldTextEditUndoManager); + mpOldTextEditUndoManager = nullptr; + + if (pOriginal) + { + // check if we got back our document undo manager + SdrUndoManager* pSdrUndoManager = mpLocalTextEditUndoManager.get(); + + if (pSdrUndoManager && dynamic_cast<SdrUndoManager*>(pOriginal) == pSdrUndoManager) + { + if (pSdrUndoManager->isEndTextEditTriggeredFromUndo()) + { + // remember the UndoManager where missing Undos have to be triggered after end + // text edit. When the undo had triggered the end text edit, the original action + // which had to be undone originally is not yet undone. + pUndoEditUndoManager = pSdrUndoManager; + + // We are ending text edit; if text edit was triggered from undo, execute all redos + // to create a complete text change undo action for the redo buffer. Also mark this + // state when at least one redo was executed; the created extra TextChange needs to + // be undone in addition to the first real undo outside the text edit changes + while (pSdrUndoManager->GetRedoActionCount() + > pSdrUndoManager->GetRedoActionCountBeforeTextEdit()) + { + bNeedToUndoSavedRedoTextEdit = true; + pSdrUndoManager->Redo(); + } + } + + // reset the callback link and let the undo manager cleanup all text edit + // undo actions to get the stack back to the form before the text edit + pSdrUndoManager->SetEndTextEditHdl(Link<SdrUndoManager*, void>()); + } + else + { + OSL_ENSURE(false, "Got UndoManager back in SdrEndTextEdit which is NOT the " + "expected document UndoManager (!)"); + delete pOriginal; + } + + // cid#1493241 - Wrapper object use after free + if (pUndoEditUndoManager == mpLocalTextEditUndoManager.get()) + pUndoEditUndoManager = nullptr; + mpLocalTextEditUndoManager.reset(); + } + } + else + { + assert(nullptr == mpOldTextEditUndoManager); // cannot be restored! + } + + if (auto pTextEditObj = mxWeakTextEditObj.get()) + { + SdrHint aHint(SdrHintKind::EndEdit, *pTextEditObj); + GetModel().Broadcast(aHint); + } + + // if new mechanism was used, clean it up. At cleanup no need to check + // for LibreOfficeKit + if (mpTextEditOutlinerView) + { + mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(nullptr); + maTEOverlayGroup.clear(); + } + + mxWeakTextEditObj.clear(); + mpTextEditPV = nullptr; + mpTextEditWin = nullptr; + mpTextEditOutlinerView = nullptr; + pTextEditCursorBuffer = nullptr; + aTextEditArea = tools::Rectangle(); + + if (SdrOutliner* pTEOutliner = mpTextEditOutliner.release()) + { + bool bModified = pTEOutliner->IsModified(); + if (pTEOutlinerView != nullptr) + { + pTEOutlinerView->HideCursor(); + } + if (pTEObj != nullptr) + { + pTEOutliner->CompleteOnlineSpelling(); + + std::unique_ptr<SdrUndoObjSetText> pTxtUndo; + + if (bModified) + { + sal_Int32 nText; + for (nText = 0; nText < pTEObj->getTextCount(); ++nText) + if (pTEObj->getText(nText) == pTEObj->getActiveText()) + break; + + pTxtUndo.reset( + dynamic_cast<SdrUndoObjSetText*>(GetModel() + .GetSdrUndoFactory() + .CreateUndoObjectSetText(*pTEObj, nText) + .release())); + } + DBG_ASSERT(!bModified || pTxtUndo, + "svx::SdrObjEditView::EndTextEdit(), could not create undo action!"); + // Set old CalcFieldValue-Handler again, this + // has to happen before Obj::EndTextEdit(), as this does UpdateFields(). + pTEOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink); + pTEOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + pTEOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + + const bool bUndo = IsUndoEnabled(); + if (bUndo) + { + OUString aObjName(pTEObj->TakeObjNameSingul()); + BegUndo(SvxResId(STR_UndoObjSetText), aObjName); + } + + pTEObj->EndTextEdit(*pTEOutliner); + + if ((pTEObj->GetRotateAngle() != 0_deg100) || (pTEObj && pTEObj->IsFontwork())) + { + pTEObj->ActionChanged(); + } + + if (pTxtUndo != nullptr) + { + pTxtUndo->AfterSetText(); + if (!pTxtUndo->IsDifferent()) + { + pTxtUndo.reset(); + } + } + // check deletion of entire TextObj + std::unique_ptr<SdrUndoAction> pDelUndo; + bool bDelObj = false; + if (mbTextEditNewObj) + { + bDelObj = pTEObj->IsTextFrame() && !pTEObj->HasText() && !pTEObj->IsEmptyPresObj() + && !pTEObj->HasFill() && !pTEObj->HasLine(); + + if (pTEObj->IsInserted() && bDelObj + && pTEObj->GetObjInventor() == SdrInventor::Default && !bDontDeleteReally) + { + SdrObjKind eIdent = pTEObj->GetObjIdentifier(); + if (eIdent == SdrObjKind::Text) + { + pDelUndo = GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pTEObj); + } + } + } + if (pTxtUndo) + { + if (bUndo) + AddUndo(std::move(pTxtUndo)); + eRet = SdrEndTextEditKind::Changed; + } + if (pDelUndo != nullptr) + { + if (bUndo) + { + AddUndo(std::move(pDelUndo)); + } + eRet = SdrEndTextEditKind::Deleted; + DBG_ASSERT(pTEObj->getParentSdrObjListFromSdrObject() != nullptr, + "SdrObjEditView::SdrEndTextEdit(): Fatal: Object edited doesn't have an " + "ObjList!"); + if (pTEObj->getParentSdrObjListFromSdrObject() != nullptr) + { + pTEObj->getParentSdrObjListFromSdrObject()->RemoveObject(pTEObj->GetOrdNum()); + CheckMarked(); // remove selection immediately... + } + } + else if (bDelObj) + { // for Writer: the app has to do the deletion itself. + eRet = SdrEndTextEditKind::ShouldBeDeleted; + } + + if (bUndo) + EndUndo(); // EndUndo after Remove, in case UndoStack is deleted immediately + + // Switch on any TextAnimation again after TextEdit + if (pTEObj) + { + pTEObj->SetTextAnimationAllowed(true); + } + + // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary + // to call AdjustMarkHdl() always. + AdjustMarkHdl(); + } + // delete all OutlinerViews + for (size_t i = pTEOutliner->GetViewCount(); i > 0;) + { + i--; + OutlinerView* pOLV = pTEOutliner->GetView(i); + sal_uInt16 nMorePix = pOLV->GetInvalidateMore() + 10; + vcl::Window* pWin = pOLV->GetWindow(); + tools::Rectangle aRect(pOLV->GetOutputArea()); + pTEOutliner->RemoveView(i); + if (!mbTextEditDontDelete || i != 0) + { + // may not own the zeroth one + delete pOLV; + } + aRect.Union(aTextEditArea); + aRect.Union(aMinTextEditArea); + aRect = pWin->LogicToPixel(aRect); + aRect.AdjustLeft(-nMorePix); + aRect.AdjustTop(-nMorePix); + aRect.AdjustRight(nMorePix); + aRect.AdjustBottom(nMorePix); + aRect = pWin->PixelToLogic(aRect); + InvalidateOneWin(*pWin->GetOutDev(), aRect); + pWin->GetOutDev()->SetFillColor(); + pWin->GetOutDev()->SetLineColor(COL_BLACK); + } + // and now the Outliner itself + if (!mbTextEditDontDelete) + delete pTEOutliner; + else + pTEOutliner->Clear(); + if (pTEWin != nullptr) + { + pTEWin->SetCursor(pTECursorBuffer); + } + maHdlList.SetMoveOutside(false); + if (eRet != SdrEndTextEditKind::Unchanged) + { + GetMarkedObjectListWriteAccess().SetNameDirty(); + } + // coverity[leaked_storage] - if pTEOutliner wasn't deleted it didn't really belong to us + } + + if (pTEObj && !pTEObj->getSdrModelFromSdrObject().isLocked() && pTEObj->GetBroadcaster()) + { + SdrHint aHint(SdrHintKind::EndEdit, *pTEObj); + const_cast<SfxBroadcaster*>(pTEObj->GetBroadcaster())->Broadcast(aHint); + } + + if (pUndoEditUndoManager) + { + if (bNeedToUndoSavedRedoTextEdit) + { + // undo the text edit action since it was created as part of an EndTextEdit + // callback from undo itself. This needs to be done after the call to + // FmFormView::SdrEndTextEdit since it gets created there + pUndoEditUndoManager->Undo(); + } + + // trigger the Undo which was not executed, but lead to this + // end text edit + pUndoEditUndoManager->Undo(); + } + + return eRet; +} + +// info about TextEdit. Default is false. +bool SdrObjEditView::IsTextEdit() const { return mxWeakTextEditObj.get().is(); } + +// info about TextEditPageView. Default is 0L. +SdrPageView* SdrObjEditView::GetTextEditPageView() const { return mpTextEditPV; } + +OutlinerView* SdrObjEditView::ImpFindOutlinerView(vcl::Window const* pWin) const +{ + if (pWin == nullptr) + return nullptr; + if (mpTextEditOutliner == nullptr) + return nullptr; + OutlinerView* pNewView = nullptr; + size_t nWinCount = mpTextEditOutliner->GetViewCount(); + for (size_t i = 0; i < nWinCount && pNewView == nullptr; i++) + { + OutlinerView* pView = mpTextEditOutliner->GetView(i); + if (pView->GetWindow() == pWin) + pNewView = pView; + } + return pNewView; +} + +void SdrObjEditView::SetTextEditWin(vcl::Window* pWin) +{ + if (!(mxWeakTextEditObj.get() && pWin != nullptr && pWin != mpTextEditWin)) + return; + + OutlinerView* pNewView = ImpFindOutlinerView(pWin); + if (pNewView != nullptr && pNewView != mpTextEditOutlinerView) + { + if (mpTextEditOutlinerView != nullptr) + { + mpTextEditOutlinerView->HideCursor(); + } + mpTextEditOutlinerView = pNewView; + mpTextEditWin = pWin; + pWin->GrabFocus(); // Make the cursor blink here as well + pNewView->ShowCursor(); + ImpMakeTextCursorAreaVisible(); + } +} + +bool SdrObjEditView::IsTextEditHit(const Point& rHit) const +{ + bool bOk = false; + if (mxWeakTextEditObj.get()) + { + tools::Rectangle aEditArea; + if (OutlinerView* pOLV = mpTextEditOutliner->GetView(0)) + aEditArea.Union(pOLV->GetOutputArea()); + + if (aEditArea.Contains(rHit)) + { // check if any characters were actually hit + const Point aPnt(rHit - aEditArea.TopLeft()); + tools::Long nHitTol = 2000; + if (OutputDevice* pRef = mpTextEditOutliner->GetRefDevice()) + nHitTol = OutputDevice::LogicToLogic(nHitTol, MapUnit::Map100thMM, + pRef->GetMapMode().GetMapUnit()); + + bOk = mpTextEditOutliner->IsTextPos(aPnt, static_cast<sal_uInt16>(nHitTol)); + } + } + return bOk; +} + +bool SdrObjEditView::IsTextEditFrameHit(const Point& rHit) const +{ + bool bOk = false; + if (rtl::Reference<SdrTextObj> pText = mxWeakTextEditObj.get()) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(0); + if (pOLV) + { + vcl::Window* pWin = pOLV->GetWindow(); + if (pText != nullptr && pText->IsTextFrame() && pWin != nullptr) + { + sal_uInt16 nPixSiz = pOLV->GetInvalidateMore(); + tools::Rectangle aEditArea(aMinTextEditArea); + aEditArea.Union(pOLV->GetOutputArea()); + if (!aEditArea.Contains(rHit)) + { + Size aSiz(pWin->PixelToLogic(Size(nPixSiz, nPixSiz))); + aEditArea.AdjustLeft(-(aSiz.Width())); + aEditArea.AdjustTop(-(aSiz.Height())); + aEditArea.AdjustRight(aSiz.Width()); + aEditArea.AdjustBottom(aSiz.Height()); + bOk = aEditArea.Contains(rHit); + } + } + } + } + return bOk; +} + +std::unique_ptr<TextChainCursorManager> +SdrObjEditView::ImpHandleMotionThroughBoxesKeyInput(const KeyEvent& rKEvt, bool* bOutHandled) +{ + *bOutHandled = false; + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (!pTextObj) + return nullptr; + + if (!pTextObj->GetNextLinkInChain() && !pTextObj->GetPrevLinkInChain()) + return nullptr; + + std::unique_ptr<TextChainCursorManager> pCursorManager( + new TextChainCursorManager(this, pTextObj.get())); + if (pCursorManager->HandleKeyEvent(rKEvt)) + { + // Possibly do other stuff here if necessary... + // XXX: Careful with the checks below (in KeyInput) for pWin and co. You should do them here I guess. + *bOutHandled = true; + } + + return pCursorManager; +} + +bool SdrObjEditView::KeyInput(const KeyEvent& rKEvt, vcl::Window* pWin) +{ + if (mpTextEditOutlinerView) + { + /* Start special handling of keys within a chain */ + // We possibly move to another box before any handling + bool bHandled = false; + std::unique_ptr<TextChainCursorManager> xCursorManager( + ImpHandleMotionThroughBoxesKeyInput(rKEvt, &bHandled)); + if (bHandled) + return true; + /* End special handling of keys within a chain */ + + if (mpTextEditOutlinerView->PostKeyEvent(rKEvt, pWin)) + { + if (mpTextEditOutliner && mpTextEditOutliner->IsModified()) + GetModel().SetChanged(); + + /* Start chaining processing */ + ImpChainingEventHdl(); + ImpMoveCursorAfterChainingEvent(xCursorManager.get()); + /* End chaining processing */ + + if (pWin != nullptr && pWin != mpTextEditWin) + SetTextEditWin(pWin); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + return SdrGlueEditView::KeyInput(rKEvt, pWin); +} + +bool SdrObjEditView::MouseButtonDown(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode(); + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rMEvt.GetPosPixel()); + if (pWin) + { + tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + } + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseButtonDown(aMEvt)) + { + if (pWin != nullptr && pWin != mpTextEditWin->GetOutDev() + && pWin->GetOutDevType() == OUTDEV_WINDOW) + SetTextEditWin(pWin->GetOwnerWindow()); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseButtonDown(rMEvt, pWin); +} + +bool SdrObjEditView::MouseButtonUp(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode(); + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt && pWin) + { + Point aPixPos(rMEvt.GetPosPixel()); + tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseButtonUp(aMEvt)) + { + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseButtonUp(rMEvt, pWin); +} + +bool SdrObjEditView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bSelMode = mpTextEditOutliner->IsInSelectionMode(); + bool bPostIt = bSelMode; + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rMEvt.GetPosPixel()); + tools::Rectangle aR(mpTextEditOutlinerView->GetOutputArea()); + if (pWin) + aR = pWin->LogicToPixel(aR); + else if (mpTextEditWin) + aR = mpTextEditWin->LogicToPixel(aR); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseMove(aMEvt) && bSelMode) + { + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseMove(rMEvt, pWin); +} + +bool SdrObjEditView::Command(const CommandEvent& rCEvt, vcl::Window* pWin) +{ + // as long as OutlinerView returns a sal_Bool, it only gets CommandEventId::StartDrag + if (mpTextEditOutlinerView != nullptr) + { + if (rCEvt.GetCommand() == CommandEventId::StartDrag) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode() || !rCEvt.IsMouseEvent(); + if (!bPostIt && rCEvt.IsMouseEvent()) + { + Point aPt(rCEvt.GetMousePosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rCEvt.GetMousePosPixel()); + if (rCEvt.IsMouseEvent() && pWin) + { + tools::Rectangle aR( + pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + } + CommandEvent aCEvt(aPixPos, rCEvt.GetCommand(), rCEvt.IsMouseEvent()); + // Command is void at the OutlinerView, sadly + mpTextEditOutlinerView->Command(aCEvt); + if (pWin != nullptr && pWin != mpTextEditWin) + SetTextEditWin(pWin); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + else + { + mpTextEditOutlinerView->Command(rCEvt); + if (comphelper::LibreOfficeKit::isActive()) + { + // It could execute CommandEventId::ExtTextInput, while SdrObjEditView::KeyInput + // isn't called + if (mpTextEditOutliner && mpTextEditOutliner->IsModified()) + GetModel().SetChanged(); + } + return true; + } + } + return SdrGlueEditView::Command(rCEvt, pWin); +} + +bool SdrObjEditView::ImpIsTextEditAllSelected() const +{ + bool bRet = false; + if (mpTextEditOutliner != nullptr && mpTextEditOutlinerView != nullptr) + { + if (SdrTextObj::HasTextImpl(mpTextEditOutliner.get())) + { + const sal_Int32 nParaCnt = mpTextEditOutliner->GetParagraphCount(); + Paragraph* pLastPara + = mpTextEditOutliner->GetParagraph(nParaCnt > 1 ? nParaCnt - 1 : 0); + + ESelection aESel(mpTextEditOutlinerView->GetSelection()); + if (aESel.nStartPara == 0 && aESel.nStartPos == 0 && aESel.nEndPara == (nParaCnt - 1)) + { + if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nEndPos) + bRet = true; + } + // in case the selection was done backwards + if (!bRet && aESel.nEndPara == 0 && aESel.nEndPos == 0 + && aESel.nStartPara == (nParaCnt - 1)) + { + if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nStartPos) + bRet = true; + } + } + else + { + bRet = true; + } + } + return bRet; +} + +void SdrObjEditView::ImpMakeTextCursorAreaVisible() +{ + if (mpTextEditOutlinerView != nullptr && mpTextEditWin != nullptr) + { + vcl::Cursor* pCsr = mpTextEditWin->GetCursor(); + if (pCsr != nullptr) + { + Size aSiz(pCsr->GetSize()); + if (!aSiz.IsEmpty()) + { + MakeVisible(tools::Rectangle(pCsr->GetPos(), aSiz), *mpTextEditWin); + } + } + } +} + +SvtScriptType SdrObjEditView::GetScriptType() const +{ + SvtScriptType nScriptType = SvtScriptType::NONE; + + if (IsTextEdit()) + { + auto pText = mxWeakTextEditObj.get(); + if (pText->GetOutlinerParaObject()) + nScriptType = pText->GetOutlinerParaObject()->GetTextObject().GetScriptType(); + + if (mpTextEditOutlinerView) + nScriptType = mpTextEditOutlinerView->GetSelectedScriptType(); + } + else + { + const size_t nMarkCount(GetMarkedObjectCount()); + + for (size_t i = 0; i < nMarkCount; ++i) + { + OutlinerParaObject* pParaObj = GetMarkedObjectByIndex(i)->GetOutlinerParaObject(); + + if (pParaObj) + { + nScriptType |= pParaObj->GetTextObject().GetScriptType(); + } + } + } + + if (nScriptType == SvtScriptType::NONE) + nScriptType = SvtScriptType::LATIN; + + return nScriptType; +} + +void SdrObjEditView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if (mxSelectionController.is()) + if (mxSelectionController->GetAttributes(rTargetSet, bOnlyHardAttr)) + return; + + if (IsTextEdit()) + { + DBG_ASSERT(mpTextEditOutlinerView != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL"); + DBG_ASSERT(mpTextEditOutliner != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL"); + + auto pText = mxWeakTextEditObj.get(); + // take care of bOnlyHardAttr(!) + if (!bOnlyHardAttr && pText->GetStyleSheet()) + rTargetSet.Put(pText->GetStyleSheet()->GetItemSet()); + + // add object attributes + rTargetSet.Put(pText->GetMergedItemSet()); + + if (mpTextEditOutlinerView) + { + // FALSE= regard InvalidItems as "holes," not as Default + rTargetSet.Put(mpTextEditOutlinerView->GetAttribs(), false); + } + + if (GetMarkedObjectCount() == 1 && GetMarkedObjectByIndex(0) == pText.get()) + { + MergeNotPersistAttrFromMarked(rTargetSet); + } + } + else + { + SdrGlueEditView::GetAttributes(rTargetSet, bOnlyHardAttr); + } +} + +bool SdrObjEditView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + bool bRet = false; + rtl::Reference<SdrTextObj> pTextEditObj = mxWeakTextEditObj.get(); + bool bTextEdit = mpTextEditOutlinerView != nullptr && pTextEditObj != nullptr; + bool bAllTextSelected = ImpIsTextEditAllSelected(); + const SfxItemSet* pSet = &rSet; + + if (!bTextEdit) + { + // no TextEdit active -> all Items to drawing object + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll); + + if (!bRet) + { + SdrGlueEditView::SetAttributes(*pSet, bReplaceAll); + bRet = true; + } + } + else + { +#ifdef DBG_UTIL + { + bool bHasEEFeatureItems = false; + SfxItemIter aIter(rSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem; + pItem = aIter.NextItem()) + { + if (!IsInvalidItem(pItem)) + { + sal_uInt16 nW = pItem->Which(); + if (nW >= EE_FEATURE_START && nW <= EE_FEATURE_END) + bHasEEFeatureItems = true; + } + } + + if (bHasEEFeatureItems) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog( + nullptr, VclMessageType::Info, VclButtonsType::Ok, + "SdrObjEditView::SetAttributes(): Setting EE_FEATURE items " + "at the SdrView does not make sense! It only leads to " + "overhead and unreadable documents.")); + xInfoBox->run(); + } + } +#endif + + bool bOnlyEEItems; + bool bNoEEItems = !SearchOutlinerItems(*pSet, bReplaceAll, &bOnlyEEItems); + // everything selected? -> attributes to the border, too + // if no EEItems, attributes to the border only + if (bAllTextSelected || bNoEEItems) + { + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll); + + if (!bRet) + { + const bool bUndo = IsUndoEnabled(); + + if (bUndo) + { + BegUndo(ImpGetDescriptionString(STR_EditSetAttributes)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pTextEditObj)); + + // If this is a text object also rescue the OutlinerParaObject since + // applying attributes to the object may change text layout when + // multiple portions exist with multiple formats. If an OutlinerParaObject + // really exists and needs to be rescued is evaluated in the undo + // implementation itself. + bool bRescueText(pTextEditObj); + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject( + *pTextEditObj, false, !bNoEEItems || bRescueText)); + EndUndo(); + } + + pTextEditObj->SetMergedItemSetAndBroadcast(*pSet, bReplaceAll); + + FlushComeBackTimer(); // to set ModeHasChanged immediately + } + } + else if (!bOnlyEEItems) + { + // Otherwise split Set, if necessary. + // Now we build an ItemSet aSet that doesn't contain EE_Items from + // *pSet (otherwise it would be a copy). + WhichRangesContainer pNewWhichTable + = RemoveWhichRange(pSet->GetRanges(), EE_ITEMS_START, EE_ITEMS_END); + SfxItemSet aSet(GetModel().GetItemPool(), std::move(pNewWhichTable)); + SfxWhichIter aIter(aSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich != 0) + { + const SfxPoolItem* pItem; + SfxItemState eState = pSet->GetItemState(nWhich, false, &pItem); + if (eState == SfxItemState::SET) + aSet.Put(*pItem); + nWhich = aIter.NextWhich(); + } + + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(aSet, bReplaceAll); + + if (!bRet) + { + if (IsUndoEnabled()) + { + BegUndo(ImpGetDescriptionString(STR_EditSetAttributes)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pTextEditObj)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pTextEditObj)); + EndUndo(); + } + + pTextEditObj->SetMergedItemSetAndBroadcast(aSet, bReplaceAll); + + if (GetMarkedObjectCount() == 1 && GetMarkedObjectByIndex(0) == pTextEditObj.get()) + { + SetNotPersistAttrToMarked(aSet); + } + } + FlushComeBackTimer(); + } + if (!bNoEEItems) + { + // and now the attributes to the EditEngine + if (bReplaceAll) + { + mpTextEditOutlinerView->RemoveAttribs(true); + } + mpTextEditOutlinerView->SetAttribs(rSet); + + Outliner* pTEOutliner = mpTextEditOutlinerView->GetOutliner(); + if (pTEOutliner && pTEOutliner->IsModified()) + GetModel().SetChanged(); + + ImpMakeTextCursorAreaVisible(); + } + bRet = true; + } + return bRet; +} + +SfxStyleSheet* SdrObjEditView::GetStyleSheet() const +{ + SfxStyleSheet* pSheet = nullptr; + + if (mxSelectionController.is()) + { + if (mxSelectionController->GetStyleSheet(pSheet)) + return pSheet; + } + + if (mpTextEditOutlinerView) + { + pSheet = mpTextEditOutlinerView->GetStyleSheet(); + } + else + { + pSheet = SdrGlueEditView::GetStyleSheet(); + } + return pSheet; +} + +void SdrObjEditView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (mxSelectionController.is()) + { + if (mxSelectionController->SetStyleSheet(pStyleSheet, bDontRemoveHardAttr)) + return; + } + + // if we are currently in edit mode we must also set the stylesheet + // on all paragraphs in the Outliner for the edit view + if (nullptr != mpTextEditOutlinerView) + { + Outliner* pOutliner = mpTextEditOutlinerView->GetOutliner(); + + const sal_Int32 nParaCount = pOutliner->GetParagraphCount(); + for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++) + { + pOutliner->SetStyleSheet(nPara, pStyleSheet); + } + } + + SdrGlueEditView::SetStyleSheet(pStyleSheet, bDontRemoveHardAttr); +} + +void SdrObjEditView::AddDeviceToPaintView(OutputDevice& rNewDev, vcl::Window* pWindow) +{ + SdrGlueEditView::AddDeviceToPaintView(rNewDev, pWindow); + + if (mxWeakTextEditObj.get() && !mbTextEditOnlyOneView + && rNewDev.GetOutDevType() == OUTDEV_WINDOW) + { + OutlinerView* pOutlView = ImpMakeOutlinerView(rNewDev.GetOwnerWindow(), nullptr); + mpTextEditOutliner->InsertView(pOutlView); + } +} + +void SdrObjEditView::DeleteDeviceFromPaintView(OutputDevice& rOldDev) +{ + SdrGlueEditView::DeleteDeviceFromPaintView(rOldDev); + + if (mxWeakTextEditObj.get() && !mbTextEditOnlyOneView + && rOldDev.GetOutDevType() == OUTDEV_WINDOW) + { + for (size_t i = mpTextEditOutliner->GetViewCount(); i > 0;) + { + i--; + OutlinerView* pOLV = mpTextEditOutliner->GetView(i); + if (pOLV && pOLV->GetWindow() == rOldDev.GetOwnerWindow()) + { + mpTextEditOutliner->RemoveView(i); + } + } + } + + lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), &rOldDev); +} + +bool SdrObjEditView::IsTextEditInSelectionMode() const +{ + return mpTextEditOutliner != nullptr && mpTextEditOutliner->IsInSelectionMode(); +} + +// MacroMode + +void SdrObjEditView::BegMacroObj(const Point& rPnt, short nTol, SdrObject* pObj, SdrPageView* pPV, + vcl::Window* pWin) +{ + BrkMacroObj(); + if (pObj != nullptr && pPV != nullptr && pWin != nullptr && pObj->HasMacro()) + { + nTol = ImpGetHitTolLogic(nTol, nullptr); + pMacroObj = pObj; + pMacroPV = pPV; + pMacroWin = pWin; + mbMacroDown = false; + nMacroTol = sal_uInt16(nTol); + aMacroDownPos = rPnt; + MovMacroObj(rPnt); + } +} + +void SdrObjEditView::ImpMacroUp(const Point& rUpPos) +{ + if (pMacroObj != nullptr && mbMacroDown) + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rUpPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec); + mbMacroDown = false; + } +} + +void SdrObjEditView::ImpMacroDown(const Point& rDownPos) +{ + if (pMacroObj != nullptr && !mbMacroDown) + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rDownPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec); + mbMacroDown = true; + } +} + +void SdrObjEditView::MovMacroObj(const Point& rPnt) +{ + if (pMacroObj == nullptr) + return; + + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rPnt; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + bool bDown = pMacroObj->IsMacroHit(aHitRec); + if (bDown) + ImpMacroDown(rPnt); + else + ImpMacroUp(rPnt); +} + +void SdrObjEditView::BrkMacroObj() +{ + if (pMacroObj != nullptr) + { + ImpMacroUp(aMacroDownPos); + pMacroObj = nullptr; + pMacroPV = nullptr; + pMacroWin = nullptr; + } +} + +bool SdrObjEditView::EndMacroObj() +{ + if (pMacroObj != nullptr && mbMacroDown) + { + ImpMacroUp(aMacroDownPos); + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = aMacroDownPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + bool bRet = pMacroObj->DoMacro(aHitRec); + pMacroObj = nullptr; + pMacroPV = nullptr; + pMacroWin = nullptr; + return bRet; + } + else + { + BrkMacroObj(); + return false; + } +} + +/** fills the given any with a XTextCursor for the current text selection. + Leaves the any untouched if there currently is no text selected */ +void SdrObjEditView::getTextSelection(css::uno::Any& rSelection) +{ + if (!IsTextEdit()) + return; + + OutlinerView* pOutlinerView = GetTextEditOutlinerView(); + if (!(pOutlinerView && pOutlinerView->HasSelection())) + return; + + SdrObject* pObj = GetTextEditObject(); + + if (!pObj) + return; + + css::uno::Reference<css::text::XText> xText(pObj->getUnoShape(), css::uno::UNO_QUERY); + if (xText.is()) + { + SvxUnoTextBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextBase>(xText); + if (pRange) + { + rSelection <<= pRange->createTextCursorBySelection(pOutlinerView->GetSelection()); + } + } +} + +/* check if we have a single selection and that single object likes + to handle the mouse and keyboard events itself + + TODO: the selection controller should be queried from the + object specific view contact. Currently this method only + works for tables. +*/ +void SdrObjEditView::MarkListHasChanged() +{ + SdrGlueEditView::MarkListHasChanged(); + + if (mxSelectionController.is()) + { + mxLastSelectionController = mxSelectionController; + mxSelectionController->onSelectionHasChanged(); + } + + mxSelectionController.clear(); + + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (rMarkList.GetMarkCount() != 1) + return; + + const SdrObject* pObj(rMarkList.GetMark(0)->GetMarkedSdrObj()); + SdrView* pView(dynamic_cast<SdrView*>(this)); + + // check for table + if (pObj && pView && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + mxSelectionController = sdr::table::CreateTableController( + *pView, static_cast<const sdr::table::SdrTableObj&>(*pObj), mxLastSelectionController); + + if (mxSelectionController.is()) + { + mxLastSelectionController.clear(); + mxSelectionController->onSelectionHasChanged(); + } + } +} + +IMPL_LINK(SdrObjEditView, EndPasteOrDropHdl, PasteOrDropInfos*, pInfo, void) +{ + OnEndPasteOrDrop(pInfo); +} + +IMPL_LINK(SdrObjEditView, BeginPasteOrDropHdl, PasteOrDropInfos*, pInfo, void) +{ + OnBeginPasteOrDrop(pInfo); +} + +void SdrObjEditView::OnBeginPasteOrDrop(PasteOrDropInfos*) +{ + // applications can derive from these virtual methods to do something before a drop or paste operation +} + +void SdrObjEditView::OnEndPasteOrDrop(PasteOrDropInfos*) +{ + // applications can derive from these virtual methods to do something before a drop or paste operation +} + +sal_uInt16 SdrObjEditView::GetSelectionLevel() const +{ + sal_uInt16 nLevel = 0xFFFF; + if (IsTextEdit()) + { + DBG_ASSERT(mpTextEditOutlinerView != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL"); + DBG_ASSERT(mpTextEditOutliner != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL"); + if (mpTextEditOutlinerView) + { + //start and end position + ESelection aSelect = mpTextEditOutlinerView->GetSelection(); + sal_uInt16 nStartPara = ::std::min(aSelect.nStartPara, aSelect.nEndPara); + sal_uInt16 nEndPara = ::std::max(aSelect.nStartPara, aSelect.nEndPara); + //get level from each paragraph + nLevel = 0; + for (sal_uInt16 nPara = nStartPara; nPara <= nEndPara; nPara++) + { + sal_uInt16 nParaDepth + = 1 << static_cast<sal_uInt16>(mpTextEditOutliner->GetDepth(nPara)); + if (!(nLevel & nParaDepth)) + nLevel += nParaDepth; + } + //reduce one level for Outliner Object + //if( nLevel > 0 && GetTextEditObject()->GetObjIdentifier() == OBJ_OUTLINETEXT ) + // nLevel = nLevel >> 1; + //no bullet paragraph selected + if (nLevel == 0) + nLevel = 0xFFFF; + } + } + return nLevel; +} + +bool SdrObjEditView::SupportsFormatPaintbrush(SdrInventor nObjectInventor, + SdrObjKind nObjectIdentifier) +{ + if (nObjectInventor != SdrInventor::Default && nObjectInventor != SdrInventor::E3d) + return false; + switch (nObjectIdentifier) + { + case SdrObjKind::NONE: + case SdrObjKind::Group: + return false; + case SdrObjKind::Line: + case SdrObjKind::Rectangle: + case SdrObjKind::CircleOrEllipse: + case SdrObjKind::CircleSection: + case SdrObjKind::CircleArc: + case SdrObjKind::CircleCut: + case SdrObjKind::Polygon: + case SdrObjKind::PolyLine: + case SdrObjKind::PathLine: + case SdrObjKind::PathFill: + case SdrObjKind::FreehandLine: + case SdrObjKind::FreehandFill: + case SdrObjKind::Text: + case SdrObjKind::TitleText: + case SdrObjKind::OutlineText: + case SdrObjKind::Graphic: + case SdrObjKind::OLE2: + case SdrObjKind::Table: + return true; + case SdrObjKind::Caption: + return false; + case SdrObjKind::Edge: + case SdrObjKind::PathPoly: + case SdrObjKind::PathPolyLine: + return true; + case SdrObjKind::Page: + case SdrObjKind::Measure: + case SdrObjKind::OLEPluginFrame: + case SdrObjKind::UNO: + return false; + case SdrObjKind::CustomShape: + return true; + default: + return false; + } +} + +static const WhichRangesContainer& GetFormatRangeImpl(bool bTextOnly) +{ + static const WhichRangesContainer gFull( + svl::Items<XATTR_LINE_FIRST, XATTR_LINE_LAST, XATTR_FILL_FIRST, XATTRSET_FILL, + SDRATTR_SHADOW_FIRST, SDRATTR_SHADOW_LAST, SDRATTR_MISC_FIRST, + SDRATTR_MISC_LAST, // table cell formats + SDRATTR_GRAF_FIRST, SDRATTR_GRAF_LAST, SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST, + SDRATTR_GLOW_FIRST, SDRATTR_GLOW_LAST, SDRATTR_SOFTEDGE_FIRST, + SDRATTR_SOFTEDGE_LAST, EE_PARA_START, EE_PARA_END, EE_CHAR_START, EE_CHAR_END>); + + static const WhichRangesContainer gTextOnly( + svl::Items<SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, EE_PARA_START, EE_PARA_END, EE_CHAR_START, + EE_CHAR_END>); + + return bTextOnly ? gTextOnly : gFull; +} + +void SdrObjEditView::TakeFormatPaintBrush(std::shared_ptr<SfxItemSet>& rFormatSet) +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (rMarkList.GetMarkCount() <= 0) + return; + + OutlinerView* pOLV = GetTextEditOutlinerView(); + + rFormatSet = std::make_shared<SfxItemSet>(GetModel().GetItemPool(), + GetFormatRangeImpl(pOLV != nullptr)); + if (pOLV) + { + rFormatSet->Put(pOLV->GetAttribs()); + } + else + { + const bool bOnlyHardAttr = false; + rFormatSet->Put(GetAttrFromMarked(bOnlyHardAttr)); + } + + // check for cloning from table cell, in which case we need to copy cell-specific formatting attributes + const SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + auto pTable = static_cast<const sdr::table::SdrTableObj*>(pObj); + if (mxSelectionController.is() && pTable->getActiveCell().is()) + { + mxSelectionController->GetAttributes(*rFormatSet, false); + } + } +} + +static SfxItemSet CreatePaintSet(const WhichRangesContainer& pRanges, SfxItemPool& rPool, + const SfxItemSet& rSourceSet, const SfxItemSet& rTargetSet, + bool bNoCharacterFormats, bool bNoParagraphFormats) +{ + SfxItemSet aPaintSet(rPool, pRanges); + + for (const auto& pRange : pRanges) + { + sal_uInt16 nWhich = pRange.first; + const sal_uInt16 nLastWhich = pRange.second; + + if (bNoCharacterFormats && (nWhich == EE_CHAR_START)) + continue; + + if (bNoParagraphFormats && (nWhich == EE_PARA_START)) + continue; + + for (; nWhich <= nLastWhich; nWhich++) + { + const SfxPoolItem* pSourceItem = rSourceSet.GetItem(nWhich); + const SfxPoolItem* pTargetItem = rTargetSet.GetItem(nWhich); + + if ((pSourceItem && !pTargetItem) + || (pSourceItem && pTargetItem && *pSourceItem != *pTargetItem)) + { + aPaintSet.Put(*pSourceItem); + } + } + } + return aPaintSet; +} + +void SdrObjEditView::ApplyFormatPaintBrushToText(SfxItemSet const& rFormatSet, SdrTextObj& rTextObj, + SdrText* pText, bool bNoCharacterFormats, + bool bNoParagraphFormats) +{ + OutlinerParaObject* pParaObj = pText ? pText->GetOutlinerParaObject() : nullptr; + if (!pParaObj) + return; + + SdrOutliner& rOutliner = rTextObj.ImpGetDrawOutliner(); + rOutliner.SetText(*pParaObj); + + sal_Int32 nParaCount(rOutliner.GetParagraphCount()); + + if (!nParaCount) + return; + + for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++) + { + if (!bNoCharacterFormats) + rOutliner.RemoveCharAttribs(nPara); + + SfxItemSet aSet(rOutliner.GetParaAttribs(nPara)); + aSet.Put(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(), rFormatSet, aSet, + bNoCharacterFormats, bNoParagraphFormats)); + rOutliner.SetParaAttribs(nPara, aSet); + } + + std::optional<OutlinerParaObject> pTemp = rOutliner.CreateParaObject(0, nParaCount); + rOutliner.Clear(); + + rTextObj.NbcSetOutlinerParaObjectForText(std::move(pTemp), pText); +} + +void SdrObjEditView::DisposeUndoManager() +{ + if (mpTextEditOutliner) + { + if (typeid(mpTextEditOutliner->GetUndoManager()) != typeid(EditUndoManager)) + { + // Non-owning pointer, clear it. + mpTextEditOutliner->SetUndoManager(nullptr); + } + } + + mpOldTextEditUndoManager = nullptr; +} + +void SdrObjEditView::ApplyFormatPaintBrush(SfxItemSet& rFormatSet, bool bNoCharacterFormats, + bool bNoParagraphFormats) +{ + if (mxSelectionController.is() + && mxSelectionController->ApplyFormatPaintBrush(rFormatSet, bNoCharacterFormats, + bNoParagraphFormats)) + { + return; + } + + OutlinerView* pOLV = GetTextEditOutlinerView(); + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (!pOLV) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + const SfxItemSet& rShapeSet = pObj->GetMergedItemSet(); + + // if not in text edit mode (aka the user selected text or clicked on a word) + // apply formatting attributes to selected shape + // All formatting items (see ranges above) that are unequal in selected shape and + // the format paintbrush are hard set on the selected shape. + + const WhichRangesContainer& pRanges = rFormatSet.GetRanges(); + bool bTextOnly = true; + + for (const auto& pRange : pRanges) + { + if ((pRange.first != EE_PARA_START) && (pRange.first != EE_CHAR_START)) + { + bTextOnly = false; + break; + } + } + + if (!bTextOnly) + { + SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(false), *rShapeSet.GetPool(), + rFormatSet, rShapeSet, bNoCharacterFormats, + bNoParagraphFormats)); + SetAttrToMarked(aPaintSet, false /*bReplaceAll*/); + } + + // now apply character and paragraph formatting to text, if the shape has any + SdrTextObj* pTextObj = DynCastSdrTextObj(pObj); + if (pTextObj) + { + sal_Int32 nText = pTextObj->getTextCount(); + + while (--nText >= 0) + { + SdrText* pText = pTextObj->getText(nText); + ApplyFormatPaintBrushToText(rFormatSet, *pTextObj, pText, bNoCharacterFormats, + bNoParagraphFormats); + } + } + } + else + { + ::Outliner* pOutliner = pOLV->GetOutliner(); + if (pOutliner) + { + const EditEngine& rEditEngine = pOutliner->GetEditEngine(); + + ESelection aSel(pOLV->GetSelection()); + if (!aSel.HasRange()) + pOLV->SetSelection(rEditEngine.GetWord(aSel, css::i18n::WordType::DICTIONARY_WORD)); + + const bool bRemoveParaAttribs = !bNoParagraphFormats; + pOLV->RemoveAttribsKeepLanguages(bRemoveParaAttribs); + SfxItemSet aSet(pOLV->GetAttribs()); + SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(), + rFormatSet, aSet, bNoCharacterFormats, + bNoParagraphFormats)); + pOLV->SetAttribs(aPaintSet); + } + } + + // check for cloning to table cell, in which case we need to copy cell-specific formatting attributes + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + auto pTable = static_cast<sdr::table::SdrTableObj*>(pObj); + if (pTable->getActiveCell().is() && mxSelectionController.is()) + { + mxSelectionController->SetAttributes(rFormatSet, false); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdetc.cxx b/svx/source/svdraw/svdetc.cxx new file mode 100644 index 0000000000..e5f99a8f8f --- /dev/null +++ b/svx/source/svdraw/svdetc.cxx @@ -0,0 +1,704 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <officecfg/Office/Common.hxx> +#include <svtools/colorcfg.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdedxv.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdoutl.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <editeng/eeitem.hxx> +#include <svl/itemset.hxx> +#include <svl/whiter.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xflhtit.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xflgrit.hxx> +#include <svx/svdoole2.hxx> +#include <svl/itempool.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <svx/xflbckit.hxx> +#include <svx/extrusionbar.hxx> +#include <svx/fontworkbar.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdotable.hxx> +#include <svx/sdrhittesthelper.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +using namespace ::com::sun::star; + +// Global data of the DrawingEngine +SdrGlobalData::SdrGlobalData() +{ + if (!utl::ConfigManager::IsFuzzing()) + { + svx::ExtrusionBar::RegisterInterface(); + svx::FontworkBar::RegisterInterface(); + } +} + +const LocaleDataWrapper& SdrGlobalData::GetLocaleData() +{ + return GetSysLocale().GetLocaleData(); +} + +namespace { + +struct TheSdrGlobalData: public rtl::Static<SdrGlobalData, TheSdrGlobalData> {}; + +} + +SdrGlobalData & GetSdrGlobalData() { + return TheSdrGlobalData::get(); +} + +OLEObjCache::OLEObjCache() +{ + if (!utl::ConfigManager::IsFuzzing()) + { +// This limit is only useful on 32-bit windows, where we can run out of virtual memory (see tdf#95579) +// For everything else, we are better off keeping it in main memory rather than using our hacky page-out thing +#if defined _WIN32 && !defined _WIN64 + nSize = officecfg::Office::Common::Cache::DrawingEngine::OLE_Objects::get(); +#else + nSize = SAL_MAX_INT32; // effectively disable the page-out mechanism +#endif + } + else + nSize = 100; + pTimer.reset( new AutoTimer( "svx OLEObjCache pTimer UnloadCheck" ) ); + pTimer->SetInvokeHandler( LINK(this, OLEObjCache, UnloadCheckHdl) ); + pTimer->SetTimeout(20000); + pTimer->SetStatic(); +} + +OLEObjCache::~OLEObjCache() +{ + pTimer->Stop(); +} + +IMPL_LINK_NOARG(OLEObjCache, UnloadCheckHdl, Timer*, void) +{ + if (nSize >= maObjs.size()) + return; + + // more objects than configured cache size try to remove objects + // of course not the freshly inserted one at nIndex=0 + size_t nCount2 = maObjs.size(); + size_t nIndex = nCount2-1; + while( nIndex && nCount2 > nSize ) + { + SdrOle2Obj* pUnloadObj = maObjs[nIndex--]; + if (!pUnloadObj) + continue; + + try + { + // it is important to get object without reinitialization to avoid reentrance + const uno::Reference< embed::XEmbeddedObject > & xUnloadObj = pUnloadObj->GetObjRef_NoInit(); + + bool bUnload = !xUnloadObj || SdrOle2Obj::CanUnloadRunningObj( xUnloadObj, pUnloadObj->GetAspect() ); + + // check whether the object can be unloaded before looking for the parent objects + if ( xUnloadObj.is() && bUnload ) + { + uno::Reference< frame::XModel > xUnloadModel( xUnloadObj->getComponent(), uno::UNO_QUERY ); + if ( xUnloadModel.is() ) + { + for (SdrOle2Obj* pCacheObj : maObjs) + { + if ( pCacheObj && pCacheObj != pUnloadObj ) + { + uno::Reference< frame::XModel > xParentModel = pCacheObj->GetParentXModel(); + if ( xUnloadModel == xParentModel ) + { + bUnload = false; // the object has running embedded objects + break; + } + } + } + } + } + + if (bUnload && UnloadObj(*pUnloadObj)) + { + // object was successfully unloaded + RemoveObj(pUnloadObj); + nCount2 = std::min(nCount2 - 1, maObjs.size()); + if (nIndex >= nCount2) + nIndex = nCount2 - 1; + } + } + catch( uno::Exception& ) + {} + } +} + +void OLEObjCache::InsertObj(SdrOle2Obj* pObj) +{ + if (!maObjs.empty()) + { + SdrOle2Obj* pExistingObj = maObjs.front(); + if ( pObj == pExistingObj ) + // the object is already on the top, nothing has to be changed + return; + } + + // get the old position of the object to know whether it is already in container + std::vector<SdrOle2Obj*>::iterator it = std::find(maObjs.begin(), maObjs.end(), pObj); + bool bFound = it != maObjs.end(); + + if (bFound) + maObjs.erase(it); + // insert object into first position + maObjs.insert(maObjs.begin(), pObj); + + // if a new object was inserted, recalculate the cache + if (!bFound) + pTimer->Invoke(); + + if (!bFound || !pTimer->IsActive()) + pTimer->Start(); +} + +void OLEObjCache::RemoveObj(SdrOle2Obj* pObj) +{ + std::vector<SdrOle2Obj*>::iterator it = std::find(maObjs.begin(), maObjs.end(), pObj); + if (it != maObjs.end()) + maObjs.erase(it); + if (maObjs.empty()) + pTimer->Stop(); +} + +size_t OLEObjCache::size() const +{ + return maObjs.size(); +} + +SdrOle2Obj* OLEObjCache::operator[](size_t nPos) +{ + return maObjs[nPos]; +} + +const SdrOle2Obj* OLEObjCache::operator[](size_t nPos) const +{ + return maObjs[nPos]; +} + +bool OLEObjCache::UnloadObj(SdrOle2Obj& rObj) +{ + bool bUnloaded = false; + + //#i80528# The old mechanism is completely useless, only taking into account if + // in all views the GrafDraft feature is used. This will nearly never have been the + // case since no one ever used this option. + + // A much better (and working) criteria would be the VOC contact count. + // The question is what will happen when i make it work now suddenly? I + // will try it for 2.4. + const sdr::contact::ViewContact& rViewContact = rObj.GetViewContact(); + const bool bVisible(rViewContact.HasViewObjectContacts()); + + if(!bVisible) + { + bUnloaded = rObj.Unload(); + } + + return bUnloaded; +} + +std::optional<Color> GetDraftFillColor(const SfxItemSet& rSet) +{ + drawing::FillStyle eFill=rSet.Get(XATTR_FILLSTYLE).GetValue(); + + switch(eFill) + { + case drawing::FillStyle_SOLID: + { + return rSet.Get(XATTR_FILLCOLOR).GetColorValue(); + } + case drawing::FillStyle_HATCH: + { + Color aCol1(rSet.Get(XATTR_FILLHATCH).GetHatchValue().GetColor()); + Color aCol2(COL_WHITE); + + // when hatched background is activated, use object fill color as hatch color + bool bFillHatchBackground = rSet.Get(XATTR_FILLBACKGROUND).GetValue(); + if(bFillHatchBackground) + { + aCol2 = rSet.Get(XATTR_FILLCOLOR).GetColorValue(); + } + + const basegfx::BColor aAverageColor(basegfx::average(aCol1.getBColor(), aCol2.getBColor())); + return Color(aAverageColor); + } + case drawing::FillStyle_GRADIENT: { + const basegfx::BGradient& rGrad=rSet.Get(XATTR_FILLGRADIENT).GetGradientValue(); + Color aCol1(Color(rGrad.GetColorStops().front().getStopColor())); + Color aCol2(Color(rGrad.GetColorStops().back().getStopColor())); + const basegfx::BColor aAverageColor(basegfx::average(aCol1.getBColor(), aCol2.getBColor())); + return Color(aAverageColor); + } + case drawing::FillStyle_BITMAP: + { + Bitmap aBitmap(rSet.Get(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic().GetBitmapEx().GetBitmap()); + const Size aSize(aBitmap.GetSizePixel()); + const sal_uInt32 nWidth = aSize.Width(); + const sal_uInt32 nHeight = aSize.Height(); + if (nWidth <= 0 || nHeight <= 0) + return {}; + + BitmapScopedReadAccess pAccess(aBitmap); + + if (pAccess) + { + sal_uInt32 nRt(0); + sal_uInt32 nGn(0); + sal_uInt32 nBl(0); + const sal_uInt32 nMaxSteps(8); + const sal_uInt32 nXStep((nWidth > nMaxSteps) ? nWidth / nMaxSteps : 1); + const sal_uInt32 nYStep((nHeight > nMaxSteps) ? nHeight / nMaxSteps : 1); + sal_uInt32 nCount(0); + + for(sal_uInt32 nY(0); nY < nHeight; nY += nYStep) + { + for(sal_uInt32 nX(0); nX < nWidth; nX += nXStep) + { + const BitmapColor& rCol2 = pAccess->GetColor(nY, nX); + + nRt += rCol2.GetRed(); + nGn += rCol2.GetGreen(); + nBl += rCol2.GetBlue(); + nCount++; + } + } + + nRt /= nCount; + nGn /= nCount; + nBl /= nCount; + + return Color(sal_uInt8(nRt), sal_uInt8(nGn), sal_uInt8(nBl)); + } + break; + } + default: break; + } + + return {}; +} + +std::unique_ptr<SdrOutliner> SdrMakeOutliner(OutlinerMode nOutlinerMode, SdrModel& rModel) +{ + SfxItemPool* pPool = &rModel.GetItemPool(); + std::unique_ptr<SdrOutliner> pOutl(new SdrOutliner( pPool, nOutlinerMode )); + pOutl->SetEditTextObjectPool( pPool ); + pOutl->SetStyleSheetPool( static_cast<SfxStyleSheetPool*>(rModel.GetStyleSheetPool())); + pOutl->SetDefTab(rModel.GetDefaultTabulator()); + Outliner::SetForbiddenCharsTable(rModel.GetForbiddenCharsTable()); + pOutl->SetAsianCompressionMode(rModel.GetCharCompressType()); + pOutl->SetKernAsianPunctuation(rModel.IsKernAsianPunctuation()); + pOutl->SetAddExtLeading(rModel.IsAddExtLeading()); + return pOutl; +} + +std::vector<Link<SdrObjCreatorParams, rtl::Reference<SdrObject>>>& ImpGetUserMakeObjHdl() +{ + SdrGlobalData& rGlobalData=GetSdrGlobalData(); + return rGlobalData.aUserMakeObjHdl; +} + +bool SearchOutlinerItems(const SfxItemSet& rSet, bool bInklDefaults, bool* pbOnlyEE) +{ + bool bHas=false; + bool bOnly=true; + bool bLookOnly=pbOnlyEE!=nullptr; + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich=aIter.FirstWhich(); + while (((bLookOnly && bOnly) || !bHas) && nWhich!=0) { + // For bInklDefaults, the entire Which range is decisive, + // in other cases only the set items are. + // Disabled and DontCare are regarded as holes in the Which range. + SfxItemState eState=aIter.GetItemState(); + if ((eState==SfxItemState::DEFAULT && bInklDefaults) || eState==SfxItemState::SET) { + if (nWhich<EE_ITEMS_START || nWhich>EE_ITEMS_END) bOnly=false; + else bHas=true; + } + nWhich=aIter.NextWhich(); + } + if (!bHas) bOnly=false; + if (pbOnlyEE!=nullptr) *pbOnlyEE=bOnly; + return bHas; +} + +WhichRangesContainer RemoveWhichRange(const WhichRangesContainer& pOldWhichTable, sal_uInt16 nRangeBeg, sal_uInt16 nRangeEnd) +{ + // Six possible cases (per range): + // [Beg..End] [nRangeBeg, nRangeEnd], to delete + // [b..e] [b..e] [b..e] Cases 1,3,2: doesn't matter, delete, doesn't matter + Ranges + // [b........e] [b........e] Cases 4,5 : shrink range | in + // [b......................e] Case 6 : splitting + pOldWhichTable + std::vector<WhichPair> buf; + for (const auto & rPair : pOldWhichTable) { + auto const begin = rPair.first; + auto const end = rPair.second; + if (end < nRangeBeg || begin > nRangeEnd) { // cases 1, 2 + buf.push_back({begin, end}); + } else if (begin >= nRangeBeg && end <= nRangeEnd) { // case 3 + // drop + } else if (end <= nRangeEnd) { // case 4 + buf.push_back({begin, nRangeBeg - 1}); + } else if (begin >= nRangeBeg) { // case 5 + buf.push_back({nRangeEnd + 1, end}); + } else { // case 6 + buf.push_back({begin, nRangeBeg - 1}); + buf.push_back({nRangeEnd + 1, end}); + } + } + std::unique_ptr<WhichPair[]> pNewWhichTable(new WhichPair[buf.size()]); + std::copy(buf.begin(), buf.end(), pNewWhichTable.get()); + return WhichRangesContainer(std::move(pNewWhichTable), buf.size()); +} + + +SvdProgressInfo::SvdProgressInfo( const Link<void*,bool>&_rLink ) +{ + maLink = _rLink; + m_nSumCurAction = 0; + + m_nObjCount = 0; + m_nCurObj = 0; + + m_nActionCount = 0; + m_nCurAction = 0; + + m_nInsertCount = 0; + m_nCurInsert = 0; +} + +void SvdProgressInfo::Init( size_t nObjCount ) +{ + m_nObjCount = nObjCount; +} + +bool SvdProgressInfo::ReportActions( size_t nActionCount ) +{ + m_nSumCurAction += nActionCount; + m_nCurAction += nActionCount; + if(m_nCurAction > m_nActionCount) + m_nCurAction = m_nActionCount; + + return maLink.Call(nullptr); +} + +void SvdProgressInfo::ReportInserts( size_t nInsertCount ) +{ + m_nSumCurAction += nInsertCount; + m_nCurInsert += nInsertCount; + + maLink.Call(nullptr); +} + +void SvdProgressInfo::ReportRescales( size_t nRescaleCount ) +{ + m_nSumCurAction += nRescaleCount; + maLink.Call(nullptr); +} + +void SvdProgressInfo::SetActionCount( size_t nActionCount ) +{ + m_nActionCount = nActionCount; +} + +void SvdProgressInfo::SetInsertCount( size_t nInsertCount ) +{ + m_nInsertCount = nInsertCount; +} + +void SvdProgressInfo::SetNextObject() +{ + m_nActionCount = 0; + m_nCurAction = 0; + + m_nInsertCount = 0; + m_nCurInsert = 0; + + m_nCurObj++; + ReportActions(0); +} + +// #i101872# isolate GetTextEditBackgroundColor to tooling; it will anyways only be used as long +// as text edit is not running on overlay + +namespace +{ + std::optional<Color> impGetSdrObjListFillColor( + const SdrObjList& rList, + const Point& rPnt, + const SdrPageView& rTextEditPV, + const SdrLayerIDSet& rVisLayers) + { + bool bMaster(rList.getSdrPageFromSdrObjList() && rList.getSdrPageFromSdrObjList()->IsMasterPage()); + + for(size_t no(rList.GetObjCount()); no > 0; ) + { + no--; + SdrObject* pObj = rList.GetObj(no); + SdrObjList* pOL = pObj->GetSubList(); + + if(pOL) + { + // group object + if (auto oColor = impGetSdrObjListFillColor(*pOL, rPnt, rTextEditPV, rVisLayers)) + return oColor; + } + else + { + SdrTextObj* pText = DynCastSdrTextObj(pObj); + + // Exclude zero master page object (i.e. background shape) from color query + if(pText + && pObj->IsClosedObj() + && (!bMaster || (!pObj->IsNotVisibleAsMaster() && 0 != no)) + && pObj->GetCurrentBoundRect().Contains(rPnt) + && !pText->IsHideContour() + && SdrObjectPrimitiveHit(*pObj, rPnt, {0, 0}, rTextEditPV, &rVisLayers, false)) + { + if (auto oColor = GetDraftFillColor(pObj->GetMergedItemSet())) + return oColor; + } + } + } + + return {}; + } + + std::optional<Color> impGetSdrPageFillColor( + const SdrPage& rPage, + const Point& rPnt, + const SdrPageView& rTextEditPV, + const SdrLayerIDSet& rVisLayers, + bool bSkipBackgroundShape) + { + if (auto oColor = impGetSdrObjListFillColor(rPage, rPnt, rTextEditPV, rVisLayers)) + return oColor; + + if(!rPage.IsMasterPage()) + { + if(rPage.TRG_HasMasterPage()) + { + SdrLayerIDSet aSet(rVisLayers); + aSet &= rPage.TRG_GetMasterPageVisibleLayers(); + SdrPage& rMasterPage = rPage.TRG_GetMasterPage(); + + // Don't fall back to background shape on + // master pages. This is later handled by + // GetBackgroundColor, and is necessary to cater for + // the silly ordering: 1. shapes, 2. master page + // shapes, 3. page background, 4. master page + // background. + if (auto oColor = impGetSdrPageFillColor(rMasterPage, rPnt, rTextEditPV, aSet, true)) + return oColor; + } + } + + // Only now determine background color from background shapes + if(!bSkipBackgroundShape) + { + return rPage.GetPageBackgroundColor(); + } + + return {}; + } + + Color impCalcBackgroundColor( + const tools::Rectangle& rArea, + const SdrPageView& rTextEditPV, + const SdrPage& rPage) + { + svtools::ColorConfig aColorConfig; + Color aBackground(aColorConfig.GetColorValue(svtools::DOCCOLOR).nColor); + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + + if(!rStyleSettings.GetHighContrastMode()) + { + // search in page + const sal_uInt16 SPOTCOUNT(5); + Point aSpotPos[SPOTCOUNT]; + Color aSpotColor[SPOTCOUNT]; + sal_uInt32 nHeight( rArea.GetSize().Height() ); + sal_uInt32 nWidth( rArea.GetSize().Width() ); + sal_uInt32 nWidth14 = nWidth / 4; + sal_uInt32 nHeight14 = nHeight / 4; + sal_uInt32 nWidth34 = ( 3 * nWidth ) / 4; + sal_uInt32 nHeight34 = ( 3 * nHeight ) / 4; + + sal_uInt16 i; + for ( i = 0; i < SPOTCOUNT; i++ ) + { + // five spots are used + switch ( i ) + { + case 0 : + { + // Center-Spot + aSpotPos[i] = rArea.Center(); + } + break; + + case 1 : + { + // TopLeft-Spot + aSpotPos[i] = rArea.TopLeft(); + aSpotPos[i].AdjustX(nWidth14 ); + aSpotPos[i].AdjustY(nHeight14 ); + } + break; + + case 2 : + { + // TopRight-Spot + aSpotPos[i] = rArea.TopLeft(); + aSpotPos[i].AdjustX(nWidth34 ); + aSpotPos[i].AdjustY(nHeight14 ); + } + break; + + case 3 : + { + // BottomLeft-Spot + aSpotPos[i] = rArea.TopLeft(); + aSpotPos[i].AdjustX(nWidth14 ); + aSpotPos[i].AdjustY(nHeight34 ); + } + break; + + case 4 : + { + // BottomRight-Spot + aSpotPos[i] = rArea.TopLeft(); + aSpotPos[i].AdjustX(nWidth34 ); + aSpotPos[i].AdjustY(nHeight34 ); + } + break; + + } + + aSpotColor[i] = + impGetSdrPageFillColor(rPage, aSpotPos[i], rTextEditPV, rTextEditPV.GetVisibleLayers(), false).value_or(COL_WHITE); + } + + sal_uInt16 aMatch[SPOTCOUNT]; + + for ( i = 0; i < SPOTCOUNT; i++ ) + { + // were same spot colors found? + aMatch[i] = 0; + + for ( sal_uInt16 j = 0; j < SPOTCOUNT; j++ ) + { + if( j != i ) + { + if( aSpotColor[i] == aSpotColor[j] ) + { + aMatch[i]++; + } + } + } + } + + // highest weight to center spot + aBackground = aSpotColor[0]; + + for ( sal_uInt16 nMatchCount = SPOTCOUNT - 1; nMatchCount > 1; nMatchCount-- ) + { + // which spot color was found most? + for ( i = 0; i < SPOTCOUNT; i++ ) + { + if( aMatch[i] == nMatchCount ) + { + aBackground = aSpotColor[i]; + nMatchCount = 1; // break outer for-loop + break; + } + } + } + } + + return aBackground; + } +} // end of anonymous namespace + +Color GetTextEditBackgroundColor(const SdrObjEditView& rView) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + + if(!rStyleSettings.GetHighContrastMode()) + { + SdrTextObj* pText = rView.GetTextEditObject(); + + if(pText && pText->IsClosedObj()) + { + sdr::table::SdrTableObj* pTable = dynamic_cast< sdr::table::SdrTableObj * >( pText ); + + if( pTable ) + if (auto oColor = GetDraftFillColor(pTable->GetActiveCellItemSet())) + return *oColor; + + if (auto oColor = GetDraftFillColor(pText->GetMergedItemSet())) + return *oColor; + } + + if (pText) + { + SdrPageView* pTextEditPV = rView.GetTextEditPageView(); + + if(pTextEditPV) + { + Point aPvOfs(pText->GetTextEditOffset()); + const SdrPage* pPg = pTextEditPV->GetPage(); + + if(pPg) + { + tools::Rectangle aSnapRect( pText->GetSnapRect() ); + aSnapRect.Move(aPvOfs.X(), aPvOfs.Y()); + + return impCalcBackgroundColor(aSnapRect, *pTextEditPV, *pPg); + } + } + } + } + + return svtools::ColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdfmtf.cxx b/svx/source/svdraw/svdfmtf.cxx new file mode 100644 index 0000000000..923c40a550 --- /dev/null +++ b/svx/source/svdraw/svdfmtf.cxx @@ -0,0 +1,1608 @@ +/* -*- 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 "svdfmtf.hxx" +#include <math.h> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlncapit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xflgrit.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/metric.hxx> +#include <editeng/charscaleitem.hxx> +#include <svx/xflhtit.hxx> +#include <svx/sdmetitm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtaitm.hxx> +#include <svx/sdtditm.hxx> +#include <svx/sdtfsitm.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdetc.hxx> +#include <svl/itemset.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <tools/helpers.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/xlinjoit.hxx> +#include <svx/xlndsit.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfltrit.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbstit.hxx> +#include <svx/svdpntv.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svx/svditer.hxx> +#include <svx/svdogrp.hxx> +#include <vcl/BitmapTools.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +ImpSdrGDIMetaFileImport::ImpSdrGDIMetaFileImport( + SdrModel& rModel, + SdrLayerID nLay, + const tools::Rectangle& rRect) +: mpVD(VclPtr<VirtualDevice>::Create()), + maScaleRect(rRect), + mnMapScalingOfs(0), + mpModel(&rModel), + mnLayer(nLay), + mnLineWidth(0), + maLineJoin(basegfx::B2DLineJoin::NONE), + maLineCap(css::drawing::LineCap_BUTT), + maDash(css::drawing::DashStyle_RECT, 0, 0, 0, 0, 0), + mbMov(false), + mbSize(false), + maOfs(0, 0), + mfScaleX(1.0), + mfScaleY(1.0), + maScaleX(1.0), + maScaleY(1.0), + mbFntDirty(true), + mbLastObjWasPolyWithoutLine(false), + mbNoLine(false), + mbNoFill(false), + mbLastObjWasLine(false) +{ + mpVD->EnableOutput(false); + mpVD->SetLineColor(); + mpVD->SetFillColor(); + maOldLineColor.SetRed( mpVD->GetLineColor().GetRed() + 1 ); + mpLineAttr = std::make_unique<SfxItemSetFixed<XATTR_LINE_FIRST, XATTR_LINE_LAST>>(rModel.GetItemPool()); + mpFillAttr = std::make_unique<SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST>>(rModel.GetItemPool()); + mpTextAttr = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(rModel.GetItemPool()); + checkClip(); +} + +void ImpSdrGDIMetaFileImport::DoLoopActions(GDIMetaFile const & rMtf, SvdProgressInfo* pProgrInfo, sal_uInt32* pActionsToReport) +{ + const sal_uLong nCount(rMtf.GetActionSize()); + + for(sal_uLong a(0); a < nCount; a++) + { + MetaAction* pAct = rMtf.GetAction(a); + + if(!pAct) + { + OSL_ENSURE(false, "OOps, no action at valid position (!)"); + pAct = rMtf.GetAction(0); + } + + switch (pAct->GetType()) + { + case MetaActionType::PIXEL : break; + case MetaActionType::POINT : break; + case MetaActionType::LINE : DoAction(static_cast<MetaLineAction &>(*pAct)); break; + case MetaActionType::RECT : DoAction(static_cast<MetaRectAction &>(*pAct)); break; + case MetaActionType::ROUNDRECT : DoAction(static_cast<MetaRoundRectAction &>(*pAct)); break; + case MetaActionType::ELLIPSE : DoAction(static_cast<MetaEllipseAction &>(*pAct)); break; + case MetaActionType::ARC : DoAction(static_cast<MetaArcAction &>(*pAct)); break; + case MetaActionType::PIE : DoAction(static_cast<MetaPieAction &>(*pAct)); break; + case MetaActionType::CHORD : DoAction(static_cast<MetaChordAction &>(*pAct)); break; + case MetaActionType::POLYLINE : DoAction(static_cast<MetaPolyLineAction &>(*pAct)); break; + case MetaActionType::POLYGON : DoAction(static_cast<MetaPolygonAction &>(*pAct)); break; + case MetaActionType::POLYPOLYGON : DoAction(static_cast<MetaPolyPolygonAction &>(*pAct)); break; + case MetaActionType::TEXT : DoAction(static_cast<MetaTextAction &>(*pAct)); break; + case MetaActionType::TEXTARRAY : DoAction(static_cast<MetaTextArrayAction &>(*pAct)); break; + case MetaActionType::STRETCHTEXT : DoAction(static_cast<MetaStretchTextAction &>(*pAct)); break; + case MetaActionType::BMP : DoAction(static_cast<MetaBmpAction &>(*pAct)); break; + case MetaActionType::BMPSCALE : DoAction(static_cast<MetaBmpScaleAction &>(*pAct)); break; + case MetaActionType::BMPEX : DoAction(static_cast<MetaBmpExAction &>(*pAct)); break; + case MetaActionType::BMPEXSCALE : DoAction(static_cast<MetaBmpExScaleAction &>(*pAct)); break; + case MetaActionType::LINECOLOR : DoAction(static_cast<MetaLineColorAction &>(*pAct)); break; + case MetaActionType::FILLCOLOR : DoAction(static_cast<MetaFillColorAction &>(*pAct)); break; + case MetaActionType::TEXTCOLOR : DoAction(static_cast<MetaTextColorAction &>(*pAct)); break; + case MetaActionType::TEXTFILLCOLOR : DoAction(static_cast<MetaTextFillColorAction &>(*pAct)); break; + case MetaActionType::FONT : DoAction(static_cast<MetaFontAction &>(*pAct)); break; + case MetaActionType::TEXTALIGN : DoAction(static_cast<MetaTextAlignAction &>(*pAct)); break; + case MetaActionType::MAPMODE : DoAction(static_cast<MetaMapModeAction &>(*pAct)); break; + case MetaActionType::CLIPREGION : DoAction(static_cast<MetaClipRegionAction &>(*pAct)); break; + case MetaActionType::MOVECLIPREGION : DoAction(static_cast<MetaMoveClipRegionAction &>(*pAct)); break; + case MetaActionType::ISECTRECTCLIPREGION: DoAction(static_cast<MetaISectRectClipRegionAction&>(*pAct)); break; + case MetaActionType::ISECTREGIONCLIPREGION: DoAction(static_cast<MetaISectRegionClipRegionAction&>(*pAct)); break; + case MetaActionType::RASTEROP : DoAction(static_cast<MetaRasterOpAction &>(*pAct)); break; + case MetaActionType::PUSH : DoAction(static_cast<MetaPushAction &>(*pAct)); break; + case MetaActionType::POP : DoAction(static_cast<MetaPopAction &>(*pAct)); break; + case MetaActionType::HATCH : DoAction(static_cast<MetaHatchAction &>(*pAct)); break; + + // #i125211# MetaCommentAction may change index, thus hand it over + case MetaActionType::COMMENT : DoAction(static_cast<MetaCommentAction&>(*pAct), rMtf, a); + break; + + // missing actions added + case MetaActionType::TEXTRECT : DoAction(static_cast<MetaTextRectAction&>(*pAct)); break; + case MetaActionType::BMPSCALEPART : DoAction(static_cast<MetaBmpScalePartAction&>(*pAct)); break; + case MetaActionType::BMPEXSCALEPART : DoAction(static_cast<MetaBmpExScalePartAction&>(*pAct)); break; + case MetaActionType::MASK : DoAction(static_cast<MetaMaskAction&>(*pAct)); break; + case MetaActionType::MASKSCALE : DoAction(static_cast<MetaMaskScaleAction&>(*pAct)); break; + case MetaActionType::MASKSCALEPART : DoAction(static_cast<MetaMaskScalePartAction&>(*pAct)); break; + case MetaActionType::GRADIENT : DoAction(static_cast<MetaGradientAction&>(*pAct)); break; + case MetaActionType::WALLPAPER : OSL_ENSURE(false, "Tried to construct SdrObject from MetaWallpaperAction: not supported (!)"); break; + case MetaActionType::Transparent : DoAction(static_cast<MetaTransparentAction&>(*pAct)); break; + case MetaActionType::EPS : OSL_ENSURE(false, "Tried to construct SdrObject from MetaEPSAction: not supported (!)"); break; + case MetaActionType::REFPOINT : DoAction(static_cast<MetaRefPointAction&>(*pAct)); break; + case MetaActionType::TEXTLINECOLOR : DoAction(static_cast<MetaTextLineColorAction&>(*pAct)); break; + case MetaActionType::TEXTLINE : OSL_ENSURE(false, "Tried to construct SdrObject from MetaTextLineAction: not supported (!)"); break; + case MetaActionType::FLOATTRANSPARENT : DoAction(static_cast<MetaFloatTransparentAction&>(*pAct)); break; + case MetaActionType::GRADIENTEX : DoAction(static_cast<MetaGradientExAction&>(*pAct)); break; + case MetaActionType::LAYOUTMODE : DoAction(static_cast<MetaLayoutModeAction&>(*pAct)); break; + case MetaActionType::TEXTLANGUAGE : DoAction(static_cast<MetaTextLanguageAction&>(*pAct)); break; + case MetaActionType::OVERLINECOLOR : DoAction(static_cast<MetaOverlineColorAction&>(*pAct)); break; + default: break; + } + + if(pProgrInfo && pActionsToReport) + { + (*pActionsToReport)++; + + if(*pActionsToReport >= 16) // update all 16 actions + { + if(!pProgrInfo->ReportActions(*pActionsToReport)) + break; + + *pActionsToReport = 0; + } + } + } +} + +size_t ImpSdrGDIMetaFileImport::DoImport( + const GDIMetaFile& rMtf, + SdrObjList& rOL, + size_t nInsPos, + SvdProgressInfo* pProgrInfo) +{ + // setup some global scale parameter + // mfScaleX, mfScaleY, maScaleX, maScaleY, mbMov, mbSize + mfScaleX = mfScaleY = 1.0; + const Size aMtfSize(rMtf.GetPrefSize()); + + if(aMtfSize.Width() & aMtfSize.Height() && (!maScaleRect.IsEmpty())) + { + maOfs = maScaleRect.TopLeft(); + + if(aMtfSize.Width() != (maScaleRect.GetWidth() - 1)) + { + mfScaleX = static_cast<double>( maScaleRect.GetWidth() - 1 ) / static_cast<double>(aMtfSize.Width()); + } + + if(aMtfSize.Height() != (maScaleRect.GetHeight() - 1)) + { + mfScaleY = static_cast<double>( maScaleRect.GetHeight() - 1 ) / static_cast<double>(aMtfSize.Height()); + } + } + + mbMov = maOfs.X()!=0 || maOfs.Y()!=0; + mbSize = false; + maScaleX = Fraction( 1, 1 ); + maScaleY = Fraction( 1, 1 ); + + if(aMtfSize.Width() != (maScaleRect.GetWidth() - 1)) + { + maScaleX = Fraction(maScaleRect.GetWidth() - 1, aMtfSize.Width()); + mbSize = true; + } + + if(aMtfSize.Height() != (maScaleRect.GetHeight() - 1)) + { + maScaleY = Fraction(maScaleRect.GetHeight() - 1, aMtfSize.Height()); + mbSize = true; + } + + if(pProgrInfo) + { + pProgrInfo->SetActionCount(rMtf.GetActionSize()); + } + + sal_uInt32 nActionsToReport(0); + + // execute + DoLoopActions(rMtf, pProgrInfo, &nActionsToReport); + + if(pProgrInfo) + { + pProgrInfo->ReportActions(nActionsToReport); + nActionsToReport = 0; + } + + // MapMode scaling + MapScaling(); + + // To calculate the progress meter, we use GetActionSize()*3. + // However, maTmpList has a lower entry count limit than GetActionSize(), + // so the actions that were assumed were too much have to be re-added. + nActionsToReport = (rMtf.GetActionSize() - maTmpList.size()) * 2; + + // announce all currently unannounced rescales + if(pProgrInfo) + { + pProgrInfo->ReportRescales(nActionsToReport); + pProgrInfo->SetInsertCount(maTmpList.size()); + } + + nActionsToReport = 0; + + // insert all objects cached in aTmpList now into rOL from nInsPos + nInsPos = std::min(nInsPos, rOL.GetObjCount()); + + for(rtl::Reference<SdrObject>& pObj : maTmpList) + { + rOL.NbcInsertObject(pObj.get(), nInsPos); + nInsPos++; + + if(pProgrInfo) + { + nActionsToReport++; + + if(nActionsToReport >= 32) // update all 32 actions + { + pProgrInfo->ReportInserts(nActionsToReport); + nActionsToReport = 0; + } + } + } + + // report all remaining inserts for the last time + if(pProgrInfo) + { + pProgrInfo->ReportInserts(nActionsToReport); + } + + return maTmpList.size(); +} + +void ImpSdrGDIMetaFileImport::SetAttributes(SdrObject* pObj, bool bForceTextAttr) +{ + mbNoLine = false; + mbNoFill = false; + bool bLine(!bForceTextAttr); + bool bFill(!pObj || (pObj->IsClosedObj() && !bForceTextAttr)); + bool bText(bForceTextAttr || (pObj && pObj->GetOutlinerParaObject())); + + if(bLine) + { + if(mnLineWidth) + { + mpLineAttr->Put(XLineWidthItem(mnLineWidth)); + } + else + { + mpLineAttr->Put(XLineWidthItem(0)); + } + + maOldLineColor = mpVD->GetLineColor(); + + if(mpVD->IsLineColor()) + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_SOLID)); + mpLineAttr->Put(XLineColorItem(OUString(), mpVD->GetLineColor())); + } + else + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_NONE)); + } + + switch(maLineJoin) + { + case basegfx::B2DLineJoin::NONE: + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_NONE)); + break; + case basegfx::B2DLineJoin::Bevel: + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_BEVEL)); + break; + case basegfx::B2DLineJoin::Miter: + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_MITER)); + break; + case basegfx::B2DLineJoin::Round: + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_ROUND)); + break; + } + + // Add LineCap support + mpLineAttr->Put(XLineCapItem(maLineCap)); + + if(((maDash.GetDots() && maDash.GetDotLen()) || (maDash.GetDashes() && maDash.GetDashLen())) && maDash.GetDistance()) + { + mpLineAttr->Put(XLineDashItem(OUString(), maDash)); + } + else + { + mpLineAttr->Put(XLineDashItem(OUString(), XDash(css::drawing::DashStyle_RECT))); + } + } + else + { + mbNoLine = true; + } + + if(bFill) + { + if(mpVD->IsFillColor()) + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID)); + mpFillAttr->Put(XFillColorItem(OUString(), mpVD->GetFillColor())); + } + else + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_NONE)); + } + } + else + { + mbNoFill = true; + } + + if(bText && mbFntDirty) + { + vcl::Font aFnt(mpVD->GetFont()); + const sal_uInt32 nHeight(FRound(aFnt.GetFontSize().Height() * mfScaleY)); + + mpTextAttr->Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO ) ); + mpTextAttr->Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CJK ) ); + mpTextAttr->Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CTL ) ); + mpTextAttr->Put(SvxPostureItem(aFnt.GetItalic(), EE_CHAR_ITALIC)); + mpTextAttr->Put(SvxWeightItem(aFnt.GetWeight(), EE_CHAR_WEIGHT)); + mpTextAttr->Put( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) ); + mpTextAttr->Put( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + mpTextAttr->Put( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + mpTextAttr->Put(SvxCharScaleWidthItem(100, EE_CHAR_FONTWIDTH)); + mpTextAttr->Put(SvxUnderlineItem(aFnt.GetUnderline(), EE_CHAR_UNDERLINE)); + mpTextAttr->Put(SvxOverlineItem(aFnt.GetOverline(), EE_CHAR_OVERLINE)); + mpTextAttr->Put(SvxCrossedOutItem(aFnt.GetStrikeout(), EE_CHAR_STRIKEOUT)); + mpTextAttr->Put(SvxShadowedItem(aFnt.IsShadow(), EE_CHAR_SHADOW)); + + // #i118485# Setting this item leads to problems (written #i118498# for this) + // mpTextAttr->Put(SvxAutoKernItem(aFnt.IsKerning(), EE_CHAR_KERNING)); + + mpTextAttr->Put(SvxWordLineModeItem(aFnt.IsWordLineMode(), EE_CHAR_WLM)); + mpTextAttr->Put(SvxContourItem(aFnt.IsOutline(), EE_CHAR_OUTLINE)); + mpTextAttr->Put(SvxColorItem(mpVD->GetTextColor(), EE_CHAR_COLOR)); + //... svxfont textitem svditext + mbFntDirty = false; + } + + if(!pObj) + return; + + pObj->SetLayer(mnLayer); + + if(bLine) + { + pObj->SetMergedItemSet(*mpLineAttr); + } + + if(bFill) + { + pObj->SetMergedItemSet(*mpFillAttr); + } + + if(bText) + { + pObj->SetMergedItemSet(*mpTextAttr); + pObj->SetMergedItem(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); + } +} + +void ImpSdrGDIMetaFileImport::InsertObj(SdrObject* pObj1, bool bScale) +{ + rtl::Reference<SdrObject> pObj = pObj1; + if(bScale && !maScaleRect.IsEmpty()) + { + if(mbSize) + { + pObj->NbcResize(Point(), maScaleX, maScaleY); + } + + if(mbMov) + { + pObj->NbcMove(Size(maOfs.X(), maOfs.Y())); + } + } + + if(isClip()) + { + const basegfx::B2DPolyPolygon aPoly(pObj->TakeXorPoly()); + const basegfx::B2DRange aOldRange(aPoly.getB2DRange()); + const SdrLayerID aOldLayer(pObj->GetLayer()); + const SfxItemSet aOldItemSet(pObj->GetMergedItemSet()); + const SdrGrafObj* pSdrGrafObj = dynamic_cast< SdrGrafObj* >(pObj.get()); + const SdrTextObj* pSdrTextObj = DynCastSdrTextObj(pObj.get()); + + if(pSdrTextObj && pSdrTextObj->HasText()) + { + // all text objects are created from ImportText and have no line or fill attributes, so + // it is okay to concentrate on the text itself + while(true) + { + const basegfx::B2DPolyPolygon aTextContour(pSdrTextObj->TakeContour()); + const basegfx::B2DRange aTextRange(aTextContour.getB2DRange()); + const basegfx::B2DRange aClipRange(maClip.getB2DRange()); + + // no overlap -> completely outside + if(!aClipRange.overlaps(aTextRange)) + { + pObj.clear(); + break; + } + + // when the clip is a rectangle fast check for inside is possible + if(basegfx::utils::isRectangle(maClip) && aClipRange.isInside(aTextRange)) + { + // completely inside ClipRect + break; + } + + // here text needs to be clipped; to do so, convert to SdrObjects with polygons + // and add these recursively. Delete original object, do not add in this run + rtl::Reference<SdrObject> pConverted = pSdrTextObj->ConvertToPolyObj(true, true); + pObj.clear(); + + if(pConverted) + { + // recursively add created conversion; per definition this shall not + // contain further SdrTextObjs. Visit only non-group objects + SdrObjListIter aIter(*pConverted, SdrIterMode::DeepNoGroups); + + // work with clones; the created conversion may contain group objects + // and when working with the original objects the loop itself could + // break and the cleanup later would be pretty complicated (only delete group + // objects, are these empty, ...?) + while(aIter.IsMore()) + { + SdrObject* pCandidate = aIter.Next(); + OSL_ENSURE(pCandidate && dynamic_cast< SdrObjGroup* >(pCandidate) == nullptr, "SdrObjListIter with SdrIterMode::DeepNoGroups error (!)"); + rtl::Reference<SdrObject> pNewClone(pCandidate->CloneSdrObject(pCandidate->getSdrModelFromSdrObject())); + + if(pNewClone) + { + InsertObj(pNewClone.get(), false); + } + else + { + OSL_ENSURE(false, "SdrObject::Clone() failed (!)"); + } + } + } + + break; + } + } + else + { + BitmapEx aBitmapEx; + + if(pSdrGrafObj) + { + aBitmapEx = pSdrGrafObj->GetGraphic().GetBitmapEx(); + } + + pObj.clear(); + + if(!aOldRange.isEmpty()) + { + // clip against ClipRegion + const basegfx::B2DPolyPolygon aNewPoly( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + aPoly, + maClip, + true, + !aPoly.isClosed())); + const basegfx::B2DRange aNewRange(aNewPoly.getB2DRange()); + + if(!aNewRange.isEmpty()) + { + pObj = new SdrPathObj( + *mpModel, + aNewPoly.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine, + aNewPoly); + + pObj->SetLayer(aOldLayer); + pObj->SetMergedItemSet(aOldItemSet); + + if(!aBitmapEx.IsEmpty()) + { + // aNewRange is inside of aOldRange and defines which part of aBitmapEx is used + const double fScaleX(aBitmapEx.GetSizePixel().Width() / (aOldRange.getWidth() ? aOldRange.getWidth() : 1.0)); + const double fScaleY(aBitmapEx.GetSizePixel().Height() / (aOldRange.getHeight() ? aOldRange.getHeight() : 1.0)); + basegfx::B2DRange aPixel(aNewRange); + basegfx::B2DHomMatrix aTrans; + + aTrans.translate(-aOldRange.getMinX(), -aOldRange.getMinY()); + aTrans.scale(fScaleX, fScaleY); + aPixel.transform(aTrans); + + const Size aOrigSizePixel(aBitmapEx.GetSizePixel()); + const Point aClipTopLeft( + basegfx::fround(floor(std::max(0.0, aPixel.getMinX()))), + basegfx::fround(floor(std::max(0.0, aPixel.getMinY())))); + const Size aClipSize( + basegfx::fround(ceil(std::min(static_cast<double>(aOrigSizePixel.Width()), aPixel.getWidth()))), + basegfx::fround(ceil(std::min(static_cast<double>(aOrigSizePixel.Height()), aPixel.getHeight())))); + const BitmapEx aClippedBitmap( + aBitmapEx, + aClipTopLeft, + aClipSize); + + pObj->SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + pObj->SetMergedItem(XFillBitmapItem(OUString(), Graphic(aClippedBitmap))); + pObj->SetMergedItem(XFillBmpTileItem(false)); + pObj->SetMergedItem(XFillBmpStretchItem(true)); + } + } + } + } + } + + if(!pObj) + return; + + // #i111954# check object for visibility + // used are SdrPathObj, SdrRectObj, SdrCircObj, SdrGrafObj + bool bVisible(false); + + if(pObj->HasLineStyle()) + { + bVisible = true; + } + + if(!bVisible && pObj->HasFillStyle()) + { + bVisible = true; + } + + if(!bVisible) + { + SdrTextObj* pTextObj = DynCastSdrTextObj(pObj.get()); + + if(pTextObj && pTextObj->HasText()) + { + bVisible = true; + } + } + + if(!bVisible) + { + SdrGrafObj* pGrafObj = dynamic_cast< SdrGrafObj* >(pObj.get()); + + if(pGrafObj) + { + // this may be refined to check if the graphic really is visible. It + // is here to ensure that graphic objects without fill, line and text + // get created + bVisible = true; + } + } + + if(bVisible) + { + maTmpList.push_back(pObj); + + if(dynamic_cast< SdrPathObj* >(pObj.get())) + { + const bool bClosed(pObj->IsClosedObj()); + + mbLastObjWasPolyWithoutLine = mbNoLine && bClosed; + mbLastObjWasLine = !bClosed; + } + else + { + mbLastObjWasPolyWithoutLine = false; + mbLastObjWasLine = false; + } + } +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaLineAction const & rAct) +{ + // #i73407# reformulation to use new B2DPolygon classes + const basegfx::B2DPoint aStart(rAct.GetStartPoint().X(), rAct.GetStartPoint().Y()); + const basegfx::B2DPoint aEnd(rAct.GetEndPoint().X(), rAct.GetEndPoint().Y()); + + if(aStart.equal(aEnd)) + return; + + basegfx::B2DPolygon aLine; + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + + aLine.append(aStart); + aLine.append(aEnd); + aLine.transform(aTransform); + + const LineInfo& rLineInfo = rAct.GetLineInfo(); + const sal_Int32 nNewLineWidth(rLineInfo.GetWidth()); + bool bCreateLineObject(true); + + if(mbLastObjWasLine && (nNewLineWidth == mnLineWidth) && CheckLastLineMerge(aLine)) + { + bCreateLineObject = false; + } + + if(!bCreateLineObject) + return; + + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aLine)); + mnLineWidth = nNewLineWidth; + maLineJoin = rLineInfo.GetLineJoin(); + maLineCap = rLineInfo.GetLineCap(); + maDash = XDash(css::drawing::DashStyle_RECT, + rLineInfo.GetDotCount(), rLineInfo.GetDotLen(), + rLineInfo.GetDashCount(), rLineInfo.GetDashLen(), + rLineInfo.GetDistance()); + SetAttributes(pPath.get()); + mnLineWidth = 0; + maLineJoin = basegfx::B2DLineJoin::NONE; + maDash = XDash(); + InsertObj(pPath.get(), false); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaRectAction const & rAct) +{ + rtl::Reference<SdrRectObj> pRect = new SdrRectObj( + *mpModel, + rAct.GetRect()); + SetAttributes(pRect.get()); + InsertObj(pRect.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaRoundRectAction const & rAct) +{ + rtl::Reference<SdrRectObj> pRect = new SdrRectObj( + *mpModel, + rAct.GetRect()); + SetAttributes(pRect.get()); + tools::Long nRad=(rAct.GetHorzRound()+rAct.GetVertRound())/2; + if (nRad!=0) { + SfxItemSetFixed<SDRATTR_CORNER_RADIUS, SDRATTR_CORNER_RADIUS> aSet(*mpLineAttr->GetPool()); + aSet.Put(SdrMetricItem(SDRATTR_CORNER_RADIUS, nRad)); + pRect->SetMergedItemSet(aSet); + } + InsertObj(pRect.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaEllipseAction const & rAct) +{ + rtl::Reference<SdrCircObj> pCirc=new SdrCircObj( + *mpModel, + SdrCircKind::Full, + rAct.GetRect()); + SetAttributes(pCirc.get()); + InsertObj(pCirc.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaArcAction const & rAct) +{ + Point aCenter(rAct.GetRect().Center()); + Degree100 nStart=GetAngle(rAct.GetStartPoint()-aCenter); + Degree100 nEnd=GetAngle(rAct.GetEndPoint()-aCenter); + rtl::Reference<SdrCircObj> pCirc = new SdrCircObj( + *mpModel, + SdrCircKind::Arc, + rAct.GetRect(),nStart,nEnd); + SetAttributes(pCirc.get()); + InsertObj(pCirc.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaPieAction const & rAct) +{ + Point aCenter(rAct.GetRect().Center()); + Degree100 nStart=GetAngle(rAct.GetStartPoint()-aCenter); + Degree100 nEnd=GetAngle(rAct.GetEndPoint()-aCenter); + rtl::Reference<SdrCircObj> pCirc = new SdrCircObj( + *mpModel, + SdrCircKind::Section, + rAct.GetRect(), + nStart, + nEnd); + SetAttributes(pCirc.get()); + InsertObj(pCirc.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaChordAction const & rAct) +{ + Point aCenter(rAct.GetRect().Center()); + Degree100 nStart=GetAngle(rAct.GetStartPoint()-aCenter); + Degree100 nEnd=GetAngle(rAct.GetEndPoint()-aCenter); + rtl::Reference<SdrCircObj> pCirc = new SdrCircObj( + *mpModel, + SdrCircKind::Cut, + rAct.GetRect(), + nStart, + nEnd); + SetAttributes(pCirc.get()); + InsertObj(pCirc.get()); +} + +bool ImpSdrGDIMetaFileImport::CheckLastLineMerge(const basegfx::B2DPolygon& rSrcPoly) +{ + // #i102706# Do not merge closed polygons + if(rSrcPoly.isClosed()) + { + return false; + } + + // #i73407# reformulation to use new B2DPolygon classes + if(mbLastObjWasLine && (maOldLineColor == mpVD->GetLineColor()) && rSrcPoly.count()) + { + SdrObject* pTmpObj = !maTmpList.empty() ? maTmpList[maTmpList.size() - 1].get() : nullptr; + SdrPathObj* pLastPoly = dynamic_cast< SdrPathObj* >(pTmpObj); + + if(pLastPoly) + { + if(1 == pLastPoly->GetPathPoly().count()) + { + bool bOk(false); + basegfx::B2DPolygon aDstPoly(pLastPoly->GetPathPoly().getB2DPolygon(0)); + + // #i102706# Do not merge closed polygons + if(aDstPoly.isClosed()) + { + return false; + } + + if(aDstPoly.count()) + { + const sal_uInt32 nMaxDstPnt(aDstPoly.count() - 1); + const sal_uInt32 nMaxSrcPnt(rSrcPoly.count() - 1); + + if(aDstPoly.getB2DPoint(nMaxDstPnt) == rSrcPoly.getB2DPoint(0)) + { + aDstPoly.append(rSrcPoly, 1, rSrcPoly.count() - 1); + bOk = true; + } + else if(aDstPoly.getB2DPoint(0) == rSrcPoly.getB2DPoint(nMaxSrcPnt)) + { + basegfx::B2DPolygon aNew(rSrcPoly); + aNew.append(aDstPoly, 1, aDstPoly.count() - 1); + aDstPoly = aNew; + bOk = true; + } + else if(aDstPoly.getB2DPoint(0) == rSrcPoly.getB2DPoint(0)) + { + aDstPoly.flip(); + aDstPoly.append(rSrcPoly, 1, rSrcPoly.count() - 1); + bOk = true; + } + else if(aDstPoly.getB2DPoint(nMaxDstPnt) == rSrcPoly.getB2DPoint(nMaxSrcPnt)) + { + basegfx::B2DPolygon aNew(rSrcPoly); + aNew.flip(); + aDstPoly.append(aNew, 1, aNew.count() - 1); + bOk = true; + } + } + + if(bOk) + { + pLastPoly->NbcSetPathPoly(basegfx::B2DPolyPolygon(aDstPoly)); + } + + return bOk; + } + } + } + + return false; +} + +bool ImpSdrGDIMetaFileImport::CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon & rPolyPolygon) +{ + // #i73407# reformulation to use new B2DPolygon classes + if(mbLastObjWasPolyWithoutLine) + { + SdrObject* pTmpObj = !maTmpList.empty() ? maTmpList[maTmpList.size() - 1].get() : nullptr; + SdrPathObj* pLastPoly = dynamic_cast< SdrPathObj* >(pTmpObj); + + if(pLastPoly) + { + if(pLastPoly->GetPathPoly() == rPolyPolygon) + { + SetAttributes(nullptr); + + if(!mbNoLine && mbNoFill) + { + pLastPoly->SetMergedItemSet(*mpLineAttr); + + return true; + } + } + } + } + + return false; +} + +void ImpSdrGDIMetaFileImport::checkClip() +{ + if(!mpVD->IsClipRegion()) + return; + + maClip = mpVD->GetClipRegion().GetAsB2DPolyPolygon(); + + if(isClip()) + { + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + mfScaleX, + mfScaleY, + maOfs.X(), + maOfs.Y())); + + maClip.transform(aTransform); + } +} + +bool ImpSdrGDIMetaFileImport::isClip() const +{ + return !maClip.getB2DRange().isEmpty(); +} + +void ImpSdrGDIMetaFileImport::DoAction( MetaPolyLineAction const & rAct ) +{ + // #i73407# reformulation to use new B2DPolygon classes + basegfx::B2DPolygon aSource(rAct.GetPolygon().getB2DPolygon()); + + if(aSource.count()) + { + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + } + + const LineInfo& rLineInfo = rAct.GetLineInfo(); + const sal_Int32 nNewLineWidth(rLineInfo.GetWidth()); + bool bCreateLineObject(true); + + if(mbLastObjWasLine && (nNewLineWidth == mnLineWidth) && CheckLastLineMerge(aSource)) + { + bCreateLineObject = false; + } + else if(mbLastObjWasPolyWithoutLine && CheckLastPolyLineAndFillMerge(basegfx::B2DPolyPolygon(aSource))) + { + bCreateLineObject = false; + } + + if(!bCreateLineObject) + return; + + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + aSource.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine, + basegfx::B2DPolyPolygon(aSource)); + mnLineWidth = nNewLineWidth; + maLineJoin = rLineInfo.GetLineJoin(); + maLineCap = rLineInfo.GetLineCap(); + maDash = XDash(css::drawing::DashStyle_RECT, + rLineInfo.GetDotCount(), rLineInfo.GetDotLen(), + rLineInfo.GetDashCount(), rLineInfo.GetDashLen(), + rLineInfo.GetDistance()); + SetAttributes(pPath.get()); + mnLineWidth = 0; + maLineJoin = basegfx::B2DLineJoin::NONE; + maDash = XDash(); + InsertObj(pPath.get(), false); +} + +void ImpSdrGDIMetaFileImport::DoAction( MetaPolygonAction const & rAct ) +{ + // #i73407# reformulation to use new B2DPolygon classes + basegfx::B2DPolygon aSource(rAct.GetPolygon().getB2DPolygon()); + + if(!aSource.count()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + + if(!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(basegfx::B2DPolyPolygon(aSource))) + { + // #i73407# make sure polygon is closed, it's a filled primitive + aSource.setClosed(true); + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + basegfx::B2DPolyPolygon(aSource)); + SetAttributes(pPath.get()); + InsertObj(pPath.get(), false); + } +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaPolyPolygonAction const & rAct) +{ + // #i73407# reformulation to use new B2DPolygon classes + basegfx::B2DPolyPolygon aSource(rAct.GetPolyPolygon().getB2DPolyPolygon()); + + if(!aSource.count()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + + if(!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(aSource)) + { + // #i73407# make sure polygon is closed, it's a filled primitive + aSource.setClosed(true); + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + std::move(aSource)); + SetAttributes(pPath.get()); + InsertObj(pPath.get(), false); + } +} + +void ImpSdrGDIMetaFileImport::ImportText( const Point& rPos, const OUString& rStr, const MetaAction& rAct ) +{ + // calc text box size, add 5% to make it fit safely + + FontMetric aFontMetric( mpVD->GetFontMetric() ); + vcl::Font aFnt( mpVD->GetFont() ); + TextAlign eAlg( aFnt.GetAlignment() ); + + sal_Int32 nTextWidth = static_cast<sal_Int32>( mpVD->GetTextWidth( rStr ) * mfScaleX ); + sal_Int32 nTextHeight = static_cast<sal_Int32>( mpVD->GetTextHeight() * mfScaleY ); + + Point aPos( FRound(rPos.X() * mfScaleX + maOfs.X()), FRound(rPos.Y() * mfScaleY + maOfs.Y()) ); + Size aSize( nTextWidth, nTextHeight ); + + if ( eAlg == ALIGN_BASELINE ) + aPos.AdjustY( -(FRound(aFontMetric.GetAscent() * mfScaleY)) ); + else if ( eAlg == ALIGN_BOTTOM ) + aPos.AdjustY( -nTextHeight ); + + tools::Rectangle aTextRect( aPos, aSize ); + rtl::Reference<SdrRectObj> pText = new SdrRectObj( + *mpModel, + SdrObjKind::Text, + aTextRect); + + pText->SetMergedItem ( makeSdrTextUpperDistItem (0)); + pText->SetMergedItem ( makeSdrTextLowerDistItem (0)); + pText->SetMergedItem ( makeSdrTextRightDistItem (0)); + pText->SetMergedItem ( makeSdrTextLeftDistItem (0)); + + if ( aFnt.GetAverageFontWidth() || ( rAct.GetType() == MetaActionType::STRETCHTEXT ) ) + { + pText->ClearMergedItem( SDRATTR_TEXT_AUTOGROWWIDTH ); + pText->SetMergedItem( makeSdrTextAutoGrowHeightItem( false ) ); + // don't let the margins eat the space needed for the text + pText->SetMergedItem( SdrTextFitToSizeTypeItem(drawing::TextFitToSizeType_ALLLINES) ); + } + else + { + pText->SetMergedItem( makeSdrTextAutoGrowWidthItem( true ) ); + } + + pText->SetLayer(mnLayer); + pText->NbcSetText( rStr ); + SetAttributes( pText.get(), true ); + pText->SetSnapRect( aTextRect ); + + if (!aFnt.IsTransparent()) + { + SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST> aAttr(*mpFillAttr->GetPool()); + aAttr.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + aAttr.Put(XFillColorItem(OUString(), aFnt.GetFillColor())); + pText->SetMergedItemSet(aAttr); + } + Degree100 nAngle = to<Degree100>(aFnt.GetOrientation()); + if ( nAngle ) + pText->SdrAttrObj::NbcRotate(aPos,nAngle); + InsertObj( pText.get(), false ); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaTextAction const & rAct) +{ + OUString aStr(rAct.GetText()); + aStr = aStr.copy(rAct.GetIndex(), rAct.GetLen()); + ImportText( rAct.GetPoint(), aStr, rAct ); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaTextArrayAction const & rAct) +{ + OUString aStr(rAct.GetText()); + aStr = aStr.copy(rAct.GetIndex(), rAct.GetLen()); + ImportText( rAct.GetPoint(), aStr, rAct ); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaStretchTextAction const & rAct) +{ + OUString aStr(rAct.GetText()); + aStr = aStr.copy(rAct.GetIndex(), rAct.GetLen()); + ImportText( rAct.GetPoint(), aStr, rAct ); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(),rAct.GetBitmap().GetSizePixel()); + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + Graphic(BitmapEx(rAct.GetBitmap())), + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpScaleAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(),rAct.GetSize()); + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + Graphic(BitmapEx(rAct.GetBitmap())), + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpExAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(),rAct.GetBitmapEx().GetSizePixel()); + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + rAct.GetBitmapEx(), + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpExScaleAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(),rAct.GetSize()); + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + rAct.GetBitmapEx(), + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + + +void ImpSdrGDIMetaFileImport::DoAction( MetaHatchAction const & rAct ) +{ + // #i73407# reformulation to use new B2DPolygon classes + basegfx::B2DPolyPolygon aSource(rAct.GetPolyPolygon().getB2DPolyPolygon()); + + if(!aSource.count()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + + if(mbLastObjWasPolyWithoutLine && CheckLastPolyLineAndFillMerge(aSource)) + return; + + const Hatch& rHatch = rAct.GetHatch(); + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + std::move(aSource)); + // #i125211# Use the ranges from the SdrObject to create a new empty SfxItemSet + SfxItemSet aHatchAttr(mpModel->GetItemPool(), pPath->GetMergedItemSet().GetRanges()); + css::drawing::HatchStyle eStyle; + + switch(rHatch.GetStyle()) + { + case HatchStyle::Triple : + { + eStyle = css::drawing::HatchStyle_TRIPLE; + break; + } + + case HatchStyle::Double : + { + eStyle = css::drawing::HatchStyle_DOUBLE; + break; + } + + default: + { + eStyle = css::drawing::HatchStyle_SINGLE; + break; + } + } + + SetAttributes(pPath.get()); + aHatchAttr.Put(XFillStyleItem(drawing::FillStyle_HATCH)); + aHatchAttr.Put(XFillHatchItem(XHatch(rHatch.GetColor(), eStyle, rHatch.GetDistance(), rHatch.GetAngle()))); + pPath->SetMergedItemSet(aHatchAttr); + + InsertObj(pPath.get(), false); +} + + +void ImpSdrGDIMetaFileImport::DoAction(MetaLineColorAction& rAct) +{ + rAct.Execute(mpVD); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaMapModeAction& rAct) +{ + MapScaling(); + rAct.Execute(mpVD); + mbLastObjWasPolyWithoutLine = false; + mbLastObjWasLine = false; +} + +void ImpSdrGDIMetaFileImport::MapScaling() +{ + const size_t nCount(maTmpList.size()); + const MapMode& rMap = mpVD->GetMapMode(); + Point aMapOrg( rMap.GetOrigin() ); + bool bMov2(aMapOrg.X() != 0 || aMapOrg.Y() != 0); + + if(bMov2) + { + for(size_t i = mnMapScalingOfs; i < nCount; i++) + { + SdrObject* pObj = maTmpList[i].get(); + + pObj->NbcMove(Size(aMapOrg.X(), aMapOrg.Y())); + } + } + + mnMapScalingOfs = nCount; +} + + +void ImpSdrGDIMetaFileImport::DoAction( MetaCommentAction const & rAct, GDIMetaFile const & rMtf, sal_uLong& a) // GDIMetaFile* pMtf ) +{ + bool aSkipComment = false; + + if (a < rMtf.GetActionSize() && rAct.GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + // #i125211# Check if next action is a MetaGradientExAction + MetaGradientExAction* pAct = dynamic_cast< MetaGradientExAction* >(rMtf.GetAction(a + 1)); + + if( pAct && pAct->GetType() == MetaActionType::GRADIENTEX ) + { + // #i73407# reformulation to use new B2DPolygon classes + basegfx::B2DPolyPolygon aSource(pAct->GetPolyPolygon().getB2DPolyPolygon()); + + if(aSource.count()) + { + if(!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(aSource)) + { + const Gradient& rGrad = pAct->GetGradient(); + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + std::move(aSource)); + // #i125211# Use the ranges from the SdrObject to create a new empty SfxItemSet + SfxItemSet aGradAttr(mpModel->GetItemPool(), pPath->GetMergedItemSet().GetRanges()); + basegfx::BGradient aBGradient( + basegfx::BColorStops( + rGrad.GetStartColor().getBColor(), + rGrad.GetEndColor().getBColor())); + + aBGradient.SetGradientStyle(rGrad.GetStyle()); + aBGradient.SetAngle(rGrad.GetAngle()); + aBGradient.SetBorder(rGrad.GetBorder()); + aBGradient.SetXOffset(rGrad.GetOfsX()); + aBGradient.SetYOffset(rGrad.GetOfsY()); + aBGradient.SetStartIntens(rGrad.GetStartIntensity()); + aBGradient.SetEndIntens(rGrad.GetEndIntensity()); + aBGradient.SetSteps(rGrad.GetSteps()); + + // no need to use SetAttributes(..) here since line and fill style + // need to be set individually + // SetAttributes(pPath); + + // switch line off; if there was one there will be a + // MetaActionType::POLYLINE following creating another object + aGradAttr.Put(XLineStyleItem(drawing::LineStyle_NONE)); + + // add detected gradient fillstyle + aGradAttr.Put(XFillStyleItem(drawing::FillStyle_GRADIENT)); + aGradAttr.Put(XFillGradientItem(aBGradient)); + + pPath->SetMergedItemSet(aGradAttr); + + InsertObj(pPath.get()); + } + } + + aSkipComment = true; + } + } + + if(aSkipComment) + { + // #i125211# forward until closing MetaCommentAction + MetaAction* pSkipAct = rMtf.GetAction(++a); + + while( pSkipAct + && ((pSkipAct->GetType() != MetaActionType::COMMENT ) + || !(static_cast<MetaCommentAction*>(pSkipAct)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")))) + { + pSkipAct = rMtf.GetAction(++a); + } + } +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaTextRectAction const & rAct) +{ + GDIMetaFile aTemp; + + mpVD->AddTextRectActions(rAct.GetRect(), rAct.GetText(), rAct.GetStyle(), aTemp); + DoLoopActions(aTemp, nullptr, nullptr); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpScalePartAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetDestPoint(), rAct.GetDestSize()); + BitmapEx aBitmapEx(rAct.GetBitmap()); + + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + aBitmapEx.Crop(tools::Rectangle(rAct.GetSrcPoint(), rAct.GetSrcSize())); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaBmpExScalePartAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetDestPoint(),rAct.GetDestSize()); + BitmapEx aBitmapEx(rAct.GetBitmapEx()); + + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + aBitmapEx.Crop(tools::Rectangle(rAct.GetSrcPoint(), rAct.GetSrcSize())); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaMaskAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(), rAct.GetBitmap().GetSizePixel()); + BitmapEx aBitmapEx(rAct.GetBitmap(), rAct.GetColor()); + + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaMaskScaleAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetPoint(), rAct.GetSize()); + BitmapEx aBitmapEx(rAct.GetBitmap(), rAct.GetColor()); + + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaMaskScalePartAction const & rAct) +{ + tools::Rectangle aRect(rAct.GetDestPoint(), rAct.GetDestSize()); + BitmapEx aBitmapEx(rAct.GetBitmap(), rAct.GetColor()); + + aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); + aBitmapEx.Crop(tools::Rectangle(rAct.GetSrcPoint(), rAct.GetSrcSize())); + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaGradientAction const & rAct) +{ + basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rAct.GetRect()); + + if(aRange.isEmpty()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aRange.transform(aTransform); + const Gradient& rGradient = rAct.GetGradient(); + rtl::Reference<SdrRectObj> pRect = new SdrRectObj( + *mpModel, + tools::Rectangle( + floor(aRange.getMinX()), + floor(aRange.getMinY()), + ceil(aRange.getMaxX()), + ceil(aRange.getMaxY()))); + // #i125211# Use the ranges from the SdrObject to create a new empty SfxItemSet + SfxItemSet aGradientAttr(mpModel->GetItemPool(), pRect->GetMergedItemSet().GetRanges()); + const XFillGradientItem aXFillGradientItem( + basegfx::BGradient( + basegfx::BColorStops( + rGradient.GetStartColor().getBColor(), + rGradient.GetEndColor().getBColor()), + rGradient.GetStyle(), + rGradient.GetAngle(), + rGradient.GetOfsX(), + rGradient.GetOfsY(), + rGradient.GetBorder(), + rGradient.GetStartIntensity(), + rGradient.GetEndIntensity(), + rGradient.GetSteps())); + + SetAttributes(pRect.get()); + aGradientAttr.Put(XFillStyleItem(drawing::FillStyle_GRADIENT)); // #i125211# + aGradientAttr.Put(aXFillGradientItem); + pRect->SetMergedItemSet(aGradientAttr); + + InsertObj(pRect.get(), false); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaTransparentAction const & rAct) +{ + basegfx::B2DPolyPolygon aSource(rAct.GetPolyPolygon().getB2DPolyPolygon()); + + if(!aSource.count()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + aSource.setClosed(true); + + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + std::move(aSource)); + SetAttributes(pPath.get()); + pPath->SetMergedItem(XFillTransparenceItem(rAct.GetTransparence())); + InsertObj(pPath.get(), false); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaGradientExAction const & rAct) +{ + basegfx::B2DPolyPolygon aSource(rAct.GetPolyPolygon().getB2DPolyPolygon()); + + if(!aSource.count()) + return; + + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aSource.transform(aTransform); + + if(mbLastObjWasPolyWithoutLine && CheckLastPolyLineAndFillMerge(aSource)) + return; + + const Gradient& rGradient = rAct.GetGradient(); + rtl::Reference<SdrPathObj> pPath = new SdrPathObj( + *mpModel, + SdrObjKind::Polygon, + std::move(aSource)); + // #i125211# Use the ranges from the SdrObject to create a new empty SfxItemSet + SfxItemSet aGradientAttr(mpModel->GetItemPool(), pPath->GetMergedItemSet().GetRanges()); + const XFillGradientItem aXFillGradientItem( + basegfx::BGradient( + basegfx::BColorStops( + rGradient.GetStartColor().getBColor(), + rGradient.GetEndColor().getBColor()), + rGradient.GetStyle(), + rGradient.GetAngle(), + rGradient.GetOfsX(), + rGradient.GetOfsY(), + rGradient.GetBorder(), + rGradient.GetStartIntensity(), + rGradient.GetEndIntensity(), + rGradient.GetSteps())); + + SetAttributes(pPath.get()); + aGradientAttr.Put(XFillStyleItem(drawing::FillStyle_GRADIENT)); // #i125211# + aGradientAttr.Put(aXFillGradientItem); + pPath->SetMergedItemSet(aGradientAttr); + + InsertObj(pPath.get(), false); +} + +void ImpSdrGDIMetaFileImport::DoAction(MetaFloatTransparentAction const & rAct) +{ + const GDIMetaFile& rMtf = rAct.GetGDIMetaFile(); + + if(!rMtf.GetActionSize()) + return; + + const tools::Rectangle aRect(rAct.GetPoint(),rAct.GetSize()); + + // convert metafile sub-content to BitmapEx + BitmapEx aBitmapEx( + convertMetafileToBitmapEx( + rMtf, + vcl::unotools::b2DRectangleFromRectangle(aRect), + 125000)); + + // handle colors + const Gradient& rGradient = rAct.GetGradient(); + basegfx::BColor aStart(rGradient.GetStartColor().getBColor()); + basegfx::BColor aEnd(rGradient.GetEndColor().getBColor()); + + if(100 != rGradient.GetStartIntensity()) + { + aStart *= static_cast<double>(rGradient.GetStartIntensity()) / 100.0; + } + + if(100 != rGradient.GetEndIntensity()) + { + aEnd *= static_cast<double>(rGradient.GetEndIntensity()) / 100.0; + } + + const bool bEqualColors(aStart == aEnd); + const bool bNoSteps(1 == rGradient.GetSteps()); + bool bCreateObject(true); + bool bHasNewMask(false); + AlphaMask aNewMask; + double fTransparence(0.0); + bool bFixedTransparence(false); + + if(bEqualColors || bNoSteps) + { + // single transparence + const basegfx::BColor aMedium(basegfx::average(aStart, aEnd)); + fTransparence = aMedium.luminance(); + + if(basegfx::fTools::lessOrEqual(fTransparence, 0.0)) + { + // no transparence needed, all done + } + else if(basegfx::fTools::moreOrEqual(fTransparence, 1.0)) + { + // all transparent, no object + bCreateObject = false; + } + else + { + // 0.0 < transparence < 1.0, apply fixed transparence + bFixedTransparence = true; + } + } + else + { + // gradient transparence + ScopedVclPtrInstance< VirtualDevice > pVDev; + + pVDev->SetOutputSizePixel(aBitmapEx.GetBitmap().GetSizePixel()); + pVDev->DrawGradient(tools::Rectangle(Point(0, 0), pVDev->GetOutputSizePixel()), rGradient); + + aNewMask = AlphaMask(pVDev->GetBitmap(Point(0, 0), pVDev->GetOutputSizePixel())); + aNewMask.Invert(); // convert transparency to alpha + bHasNewMask = true; + } + + if(!bCreateObject) + return; + + if(bHasNewMask || bFixedTransparence) + { + if(!aBitmapEx.IsAlpha()) + { + // no transparence yet, apply new one + if(bFixedTransparence) + { + sal_uInt8 nTransparence(basegfx::fround(fTransparence * 255.0)); + + aNewMask = AlphaMask(aBitmapEx.GetBitmap().GetSizePixel(), &nTransparence); + } + + aBitmapEx = BitmapEx(aBitmapEx.GetBitmap(), aNewMask); + } + else + { + vcl::bitmap::DrawAlphaBitmapAndAlphaGradient(aBitmapEx, bFixedTransparence, fTransparence, aNewMask); + } + } + + // create and add object + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj( + *mpModel, + aBitmapEx, + aRect); + + // for MetaFloatTransparentAction, do not use SetAttributes(...) + // since these metafile content is not used to draw line/fill + // dependent of these setting at the device content + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdfmtf.hxx b/svx/source/svdraw/svdfmtf.hxx new file mode 100644 index 0000000000..31c3255827 --- /dev/null +++ b/svx/source/svdraw/svdfmtf.hxx @@ -0,0 +1,173 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_SVDRAW_SVDFMTF_HXX +#define INCLUDED_SVX_SOURCE_SVDRAW_SVDFMTF_HXX + +#include <sal/config.h> + +#include <memory> + +#include <svl/itemset.hxx> +#include <tools/fract.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <svx/svdobj.hxx> +#include <svx/xdash.hxx> + +// Forward Declarations + + +class SfxItemSet; +class SdrObjList; +class SdrModel; +class SdrPage; +class SdrObject; +class SvdProgressInfo; + + +// Helper Class ImpSdrGDIMetaFileImport +class ImpSdrGDIMetaFileImport final +{ + ::std::vector< rtl::Reference<SdrObject> > maTmpList; + ScopedVclPtr<VirtualDevice> mpVD; + tools::Rectangle maScaleRect; + size_t mnMapScalingOfs; // from here on, not edited with MapScaling + std::unique_ptr<SfxItemSet> mpLineAttr; + std::unique_ptr<SfxItemSet> mpFillAttr; + std::unique_ptr<SfxItemSet> mpTextAttr; + SdrModel* mpModel; + SdrLayerID mnLayer; + Color maOldLineColor; + sal_Int32 mnLineWidth; + basegfx::B2DLineJoin maLineJoin; + css::drawing::LineCap maLineCap; + XDash maDash; + + bool mbMov; + bool mbSize; + Point maOfs; + double mfScaleX; + double mfScaleY; + Fraction maScaleX; + Fraction maScaleY; + + bool mbFntDirty; + + // to optimize (PenNULL,Brush,DrawPoly),(Pen,BrushNULL,DrawPoly) -> two-in-one + bool mbLastObjWasPolyWithoutLine; + bool mbNoLine; + bool mbNoFill; + + // to optimize multiple lines into a Polyline + bool mbLastObjWasLine; + + // clipregion + basegfx::B2DPolyPolygon maClip; + + // check for clip and evtl. fill maClip + void checkClip(); + bool isClip() const; + + // actions + void DoAction(MetaLineAction const & rAct); + void DoAction(MetaRectAction const & rAct); + void DoAction(MetaRoundRectAction const & rAct); + void DoAction(MetaEllipseAction const & rAct); + void DoAction(MetaArcAction const & rAct); + void DoAction(MetaPieAction const & rAct); + void DoAction(MetaChordAction const & rAct); + void DoAction(MetaPolyLineAction const & rAct); + void DoAction(MetaPolygonAction const & rAct); + void DoAction(MetaPolyPolygonAction const & rAct); + void DoAction(MetaTextAction const & rAct); + void DoAction(MetaTextArrayAction const & rAct); + void DoAction(MetaStretchTextAction const & rAct); + void DoAction(MetaBmpAction const & rAct); + void DoAction(MetaBmpScaleAction const & rAct); + void DoAction(MetaBmpExAction const & rAct); + void DoAction(MetaBmpExScaleAction const & rAct); + void DoAction(MetaHatchAction const & rAct); + void DoAction(MetaLineColorAction & rAct); + void DoAction(MetaMapModeAction & rAct); + void DoAction(MetaFillColorAction & rAct) { rAct.Execute(mpVD); } + void DoAction(MetaTextColorAction & rAct) { rAct.Execute(mpVD); } + void DoAction(MetaTextFillColorAction & rAct) { rAct.Execute(mpVD); } + void DoAction(MetaFontAction & rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + void DoAction(MetaTextAlignAction & rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + void DoAction(MetaClipRegionAction & rAct) { rAct.Execute(mpVD); checkClip(); } + void DoAction(MetaRasterOpAction & rAct) { rAct.Execute(mpVD); } + void DoAction(MetaPushAction & rAct) { rAct.Execute(mpVD); checkClip(); } + void DoAction(MetaPopAction & rAct) { rAct.Execute(mpVD); mbFntDirty = true; checkClip(); } + void DoAction(MetaMoveClipRegionAction & rAct) { rAct.Execute(mpVD); checkClip(); } + void DoAction(MetaISectRectClipRegionAction& rAct) { rAct.Execute(mpVD); checkClip(); } + void DoAction(MetaISectRegionClipRegionAction& rAct) { rAct.Execute(mpVD); checkClip(); } + + // #i125211# The MetaCommentAction needs to advance (if used), thus + // give current metafile and index which may be changed + void DoAction(MetaCommentAction const & rAct, GDIMetaFile const & rMtf, sal_uLong& a); + + // missing actions added + void DoAction(MetaTextRectAction const & rAct); + void DoAction(MetaBmpScalePartAction const & rAct); + void DoAction(MetaBmpExScalePartAction const & rAct); + void DoAction(MetaMaskAction const & rAct); + void DoAction(MetaMaskScaleAction const & rAct); + void DoAction(MetaMaskScalePartAction const & rAct); + void DoAction(MetaGradientAction const & rAct); + void DoAction(MetaTransparentAction const & rAct); + void DoAction(MetaRefPointAction& rAct) { rAct.Execute(mpVD); } + void DoAction(MetaTextLineColorAction& rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + void DoAction(MetaFloatTransparentAction const & rAct); + void DoAction(MetaGradientExAction const & rAct); + void DoAction(MetaLayoutModeAction& rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + void DoAction(MetaTextLanguageAction& rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + void DoAction(MetaOverlineColorAction& rAct) { rAct.Execute(mpVD); mbFntDirty = true; } + + void ImportText(const Point& rPos, const OUString& rStr, const MetaAction& rAct); + void SetAttributes(SdrObject* pObj, bool bForceTextAttr = false); + void InsertObj(SdrObject* pObj, bool bScale = true); + void MapScaling(); + + // #i73407# reformulation to use new B2DPolygon classes + bool CheckLastLineMerge(const basegfx::B2DPolygon& rSrcPoly); + bool CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon& rPolyPolygon); + + void DoLoopActions(GDIMetaFile const & rMtf, SvdProgressInfo* pProgrInfo, sal_uInt32* pActionsToReport); + + // Copy assignment is forbidden and not implemented. + ImpSdrGDIMetaFileImport (const ImpSdrGDIMetaFileImport &) = delete; + ImpSdrGDIMetaFileImport & operator= (const ImpSdrGDIMetaFileImport &) = delete; + +public: + ImpSdrGDIMetaFileImport( + SdrModel& rModel, + SdrLayerID nLay, + const tools::Rectangle& rRect); + + size_t DoImport( + const GDIMetaFile& rMtf, + SdrObjList& rDestList, + size_t nInsPos, + SvdProgressInfo* pProgrInfo = nullptr); +}; + +#endif // INCLUDED_SVX_SOURCE_SVDRAW_SVDFMTF_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdglev.cxx b/svx/source/svdraw/svdglev.cxx new file mode 100644 index 0000000000..5a67e3685f --- /dev/null +++ b/svx/source/svdraw/svdglev.cxx @@ -0,0 +1,402 @@ +/* -*- 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/svdglev.hxx> +#include <math.h> + +#include <svx/svdundo.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/svdglue.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdobj.hxx> + +SdrGlueEditView::SdrGlueEditView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: SdrPolyEditView(rSdrModel, pOut) +{ +} + +SdrGlueEditView::~SdrGlueEditView() +{ +} + +void SdrGlueEditView::ImpDoMarkedGluePoints(PGlueDoFunc pDoFunc, bool bConst, const void* p1, const void* p2, const void* p3, const void* p4) +{ + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + if (!rPts.empty()) + { + SdrGluePointList* pGPL=nullptr; + if (bConst) { + const SdrGluePointList* pConstGPL=pObj->GetGluePointList(); + pGPL=const_cast<SdrGluePointList*>(pConstGPL); + } else { + pGPL=pObj->ForceGluePointList(); + } + if (pGPL!=nullptr) + { + if(!bConst && IsUndoEnabled() ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + for(sal_uInt16 nPtId : rPts) + { + sal_uInt16 nGlueIdx=pGPL->FindGluePoint(nPtId); + if (nGlueIdx!=SDRGLUEPOINT_NOTFOUND) + { + SdrGluePoint& rGP=(*pGPL)[nGlueIdx]; + (*pDoFunc)(rGP,pObj,p1,p2,p3,p4); + } + } + if (!bConst) + { + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + } + } + } + } + if (!bConst && nMarkCount!=0) + GetModel().SetChanged(); +} + + +static void ImpGetEscDir(SdrGluePoint & rGP, const SdrObject* /*pObj*/, const void* pbFirst, const void* pnThisEsc, const void* pnRet, const void*) +{ + TriState& nRet=*const_cast<TriState *>(static_cast<TriState const *>(pnRet)); + if (nRet!=TRISTATE_INDET) { + SdrEscapeDirection nEsc = rGP.GetEscDir(); + bool bOn = bool(nEsc & *static_cast<SdrEscapeDirection const *>(pnThisEsc)); + bool& bFirst=*const_cast<bool *>(static_cast<bool const *>(pbFirst)); + if (bFirst) { + nRet = bOn ? TRISTATE_TRUE : TRISTATE_FALSE; + bFirst = false; + } + else if (nRet != (bOn ? TRISTATE_TRUE : TRISTATE_FALSE)) nRet=TRISTATE_INDET; + } +} + +TriState SdrGlueEditView::IsMarkedGluePointsEscDir(SdrEscapeDirection nThisEsc) const +{ + ForceUndirtyMrkPnt(); + bool bFirst=true; + TriState nRet=TRISTATE_FALSE; + const_cast<SdrGlueEditView*>(this)->ImpDoMarkedGluePoints(ImpGetEscDir,true,&bFirst,&nThisEsc,&nRet); + return nRet; +} + +static void ImpSetEscDir(SdrGluePoint& rGP, const SdrObject* /*pObj*/, const void* pnThisEsc, const void* pbOn, const void*, const void*) +{ + SdrEscapeDirection nEsc=rGP.GetEscDir(); + if (*static_cast<bool const *>(pbOn)) + nEsc |= *static_cast<SdrEscapeDirection const *>(pnThisEsc); + else + nEsc &= ~*static_cast<SdrEscapeDirection const *>(pnThisEsc); + rGP.SetEscDir(nEsc); +} + +void SdrGlueEditView::SetMarkedGluePointsEscDir(SdrEscapeDirection nThisEsc, bool bOn) +{ + ForceUndirtyMrkPnt(); + BegUndo(SvxResId(STR_EditSetGlueEscDir),GetDescriptionOfMarkedGluePoints()); + ImpDoMarkedGluePoints(ImpSetEscDir,false,&nThisEsc,&bOn); + EndUndo(); +} + + +static void ImpGetPercent(SdrGluePoint & rGP, const SdrObject* /*pObj*/, const void* pbFirst, const void* pnRet, const void*, const void*) +{ + TriState& nRet=*const_cast<TriState *>(static_cast<TriState const *>(pnRet)); + if (nRet!=TRISTATE_INDET) { + bool bOn=rGP.IsPercent(); + bool& bFirst=*const_cast<bool *>(static_cast<bool const *>(pbFirst)); + if (bFirst) { + nRet = bOn ? TRISTATE_TRUE : TRISTATE_FALSE; + bFirst = false; + } + else if ((nRet!=TRISTATE_FALSE)!=bOn) + nRet=TRISTATE_INDET; + } +} + +TriState SdrGlueEditView::IsMarkedGluePointsPercent() const +{ + ForceUndirtyMrkPnt(); + bool bFirst=true; + TriState nRet = TRISTATE_TRUE; + const_cast<SdrGlueEditView*>(this)->ImpDoMarkedGluePoints(ImpGetPercent,true,&bFirst,&nRet); + return nRet; +} + +static void ImpSetPercent(SdrGluePoint& rGP, const SdrObject* pObj, const void* pbOn, const void*, const void*, const void*) +{ + Point aPos(rGP.GetAbsolutePos(*pObj)); + rGP.SetPercent(*static_cast<bool const *>(pbOn)); + rGP.SetAbsolutePos(aPos,*pObj); +} + +void SdrGlueEditView::SetMarkedGluePointsPercent(bool bOn) +{ + ForceUndirtyMrkPnt(); + BegUndo(SvxResId(STR_EditSetGluePercent),GetDescriptionOfMarkedGluePoints()); + ImpDoMarkedGluePoints(ImpSetPercent,false,&bOn); + EndUndo(); +} + + +static void ImpGetAlign(SdrGluePoint & rGP, const SdrObject* /*pObj*/, const void* pbFirst, const void* pbDontCare, const void* pbVert, const void* pnRet) +{ + SdrAlign& nRet=*const_cast<SdrAlign *>(static_cast<SdrAlign const *>(pnRet)); + bool& bDontCare=*const_cast<bool *>(static_cast<bool const *>(pbDontCare)); + bool bVert=*static_cast<bool const *>(pbVert); + if (bDontCare) + return; + + SdrAlign nAlg=SdrAlign::NONE; + if (bVert) { + nAlg=rGP.GetVertAlign(); + } else { + nAlg=rGP.GetHorzAlign(); + } + bool& bFirst=*const_cast<bool *>(static_cast<bool const *>(pbFirst)); + if (bFirst) { nRet=nAlg; bFirst=false; } + else if (nRet!=nAlg) { + if (bVert) { + nRet=SdrAlign::VERT_DONTCARE; + } else { + nRet=SdrAlign::HORZ_DONTCARE; + } + bDontCare=true; + } +} + +SdrAlign SdrGlueEditView::GetMarkedGluePointsAlign(bool bVert) const +{ + ForceUndirtyMrkPnt(); + bool bFirst=true; + bool bDontCare=false; + SdrAlign nRet=SdrAlign::NONE; + const_cast<SdrGlueEditView*>(this)->ImpDoMarkedGluePoints(ImpGetAlign,true,&bFirst,&bDontCare,&bVert,&nRet); + return nRet; +} + +static void ImpSetAlign(SdrGluePoint& rGP, const SdrObject* pObj, const void* pbVert, const void* pnAlign, const void*, const void*) +{ + Point aPos(rGP.GetAbsolutePos(*pObj)); + if (*static_cast<bool const *>(pbVert)) { // bVert? + rGP.SetVertAlign(*static_cast<SdrAlign const *>(pnAlign)); + } else { + rGP.SetHorzAlign(*static_cast<SdrAlign const *>(pnAlign)); + } + rGP.SetAbsolutePos(aPos,*pObj); +} + +void SdrGlueEditView::SetMarkedGluePointsAlign(bool bVert, SdrAlign nAlign) +{ + ForceUndirtyMrkPnt(); + BegUndo(SvxResId(STR_EditSetGlueAlign),GetDescriptionOfMarkedGluePoints()); + ImpDoMarkedGluePoints(ImpSetAlign,false,&bVert,&nAlign); + EndUndo(); +} + +void SdrGlueEditView::DeleteMarkedGluePoints() +{ + BrkAction(); + ForceUndirtyMrkPnt(); + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditDelete),GetDescriptionOfMarkedGluePoints(),SdrRepeatFunc::Delete); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + if (!rPts.empty()) + { + SdrGluePointList* pGPL=pObj->ForceGluePointList(); + if (pGPL!=nullptr) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + for(sal_uInt16 nPtId : rPts) + { + sal_uInt16 nGlueIdx=pGPL->FindGluePoint(nPtId); + if (nGlueIdx!=SDRGLUEPOINT_NOTFOUND) + { + pGPL->Delete(nGlueIdx); + } + } + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + } + } + } + if( bUndo ) + EndUndo(); + UnmarkAllGluePoints(); + if (nMarkCount!=0) + GetModel().SetChanged(); +} + + +void SdrGlueEditView::ImpCopyMarkedGluePoints() +{ + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + SdrGluePointList* pGPL=pObj->ForceGluePointList(); + if (!rPts.empty() && pGPL!=nullptr) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + SdrUShortCont aIdsToErase; + SdrUShortCont aIdsToInsert; + for(sal_uInt16 nPtId : rPts) + { + sal_uInt16 nGlueIdx=pGPL->FindGluePoint(nPtId); + if (nGlueIdx!=SDRGLUEPOINT_NOTFOUND) + { + SdrGluePoint aNewGP((*pGPL)[nGlueIdx]); // clone GluePoint + sal_uInt16 nNewIdx=pGPL->Insert(aNewGP); // and insert it + sal_uInt16 nNewId=(*pGPL)[nNewIdx].GetId(); // retrieve ID of new GluePoints + aIdsToErase.insert(nPtId); // select it (instead of the old one) + aIdsToInsert.insert(nNewId); + } + } + for(const auto& rId : aIdsToErase) + rPts.erase(rId); + rPts.insert(aIdsToInsert); + } + } + if( bUndo ) + EndUndo(); + + if (nMarkCount!=0) + GetModel().SetChanged(); +} + + +void SdrGlueEditView::ImpTransformMarkedGluePoints(PGlueTrFunc pTrFunc, const void* p1, const void* p2, const void* p3, const void* p4) +{ + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + if (!rPts.empty()) { + SdrGluePointList* pGPL=pObj->ForceGluePointList(); + if (pGPL!=nullptr) + { + if( IsUndoEnabled() ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + for(sal_uInt16 nPtId : rPts) + { + sal_uInt16 nGlueIdx=pGPL->FindGluePoint(nPtId); + if (nGlueIdx!=SDRGLUEPOINT_NOTFOUND) { + SdrGluePoint& rGP=(*pGPL)[nGlueIdx]; + Point aPos(rGP.GetAbsolutePos(*pObj)); + (*pTrFunc)(aPos,p1,p2,p3,p4); + rGP.SetAbsolutePos(aPos,*pObj); + } + } + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + } + } + } + if (nMarkCount!=0) + GetModel().SetChanged(); +} + + +static void ImpMove(Point& rPt, const void* p1, const void* /*p2*/, const void* /*p3*/, const void* /*p4*/) +{ + rPt.AdjustX(static_cast<const Size*>(p1)->Width() ); + rPt.AdjustY(static_cast<const Size*>(p1)->Height() ); +} + +void SdrGlueEditView::MoveMarkedGluePoints(const Size& rSiz, bool bCopy) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditMove)); + if (bCopy) aStr += SvxResId(STR_EditWithCopy); + BegUndo(aStr,GetDescriptionOfMarkedGluePoints(),SdrRepeatFunc::Move); + if (bCopy) ImpCopyMarkedGluePoints(); + ImpTransformMarkedGluePoints(ImpMove,&rSiz); + EndUndo(); + AdjustMarkHdl(); +} + + +static void ImpResize(Point& rPt, const void* p1, const void* p2, const void* p3, const void* /*p4*/) +{ + ResizePoint(rPt,*static_cast<const Point*>(p1),*static_cast<const Fraction*>(p2),*static_cast<const Fraction*>(p3)); +} + +void SdrGlueEditView::ResizeMarkedGluePoints(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bCopy) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditResize)); + if (bCopy) aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr,GetDescriptionOfMarkedGluePoints(),SdrRepeatFunc::Resize); + if (bCopy) ImpCopyMarkedGluePoints(); + ImpTransformMarkedGluePoints(ImpResize,&rRef,&xFact,&yFact); + EndUndo(); + AdjustMarkHdl(); +} + + +static void ImpRotate(Point& rPt, const void* p1, const void* /*p2*/, const void* p3, const void* p4) +{ + RotatePoint(rPt,*static_cast<const Point*>(p1),*static_cast<const double*>(p3),*static_cast<const double*>(p4)); +} + +void SdrGlueEditView::RotateMarkedGluePoints(const Point& rRef, Degree100 nAngle, bool bCopy) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditRotate)); + if (bCopy) aStr+=SvxResId(STR_EditWithCopy); + BegUndo(aStr,GetDescriptionOfMarkedGluePoints(),SdrRepeatFunc::Rotate); + if (bCopy) ImpCopyMarkedGluePoints(); + double nSin = sin(toRadians(nAngle)); + double nCos = cos(toRadians(nAngle)); + ImpTransformMarkedGluePoints(ImpRotate,&rRef,&nAngle,&nSin,&nCos); + EndUndo(); + AdjustMarkHdl(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdglue.cxx b/svx/source/svdraw/svdglue.cxx new file mode 100644 index 0000000000..c27fc2e2bd --- /dev/null +++ b/svx/source/svdraw/svdglue.cxx @@ -0,0 +1,394 @@ +/* -*- 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 <tools/debug.hxx> +#include <vcl/window.hxx> + +#include <svx/svdglue.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdtrans.hxx> +#include <comphelper/lok.hxx> + +const Size aGlueHalfSize(4,4); + +void SdrGluePoint::SetReallyAbsolute(bool bOn, const SdrObject& rObj) +{ + if ( m_bReallyAbsolute == bOn ) + return; + + if ( bOn ) + { + m_aPos=GetAbsolutePos(rObj); + m_bReallyAbsolute=bOn; + } + else + { + m_bReallyAbsolute=bOn; + Point aPt(m_aPos); + SetAbsolutePos(aPt,rObj); + } +} + +Point SdrGluePoint::GetAbsolutePos(const SdrObject& rObj) const +{ + if (m_bReallyAbsolute) return m_aPos; + tools::Rectangle aSnap(rObj.GetSnapRect()); + tools::Rectangle aBound(rObj.GetSnapRect()); + Point aPt(m_aPos); + + Point aOfs(aSnap.Center()); + switch (GetHorzAlign()) { + case SdrAlign::HORZ_LEFT : aOfs.setX(aSnap.Left() ); break; + case SdrAlign::HORZ_RIGHT : aOfs.setX(aSnap.Right() ); break; + default: break; + } + switch (GetVertAlign()) { + case SdrAlign::VERT_TOP : aOfs.setY(aSnap.Top() ); break; + case SdrAlign::VERT_BOTTOM: aOfs.setY(aSnap.Bottom() ); break; + default: break; + } + if (!m_bNoPercent) { + tools::Long nXMul=aSnap.Right()-aSnap.Left(); + tools::Long nYMul=aSnap.Bottom()-aSnap.Top(); + tools::Long nXDiv=10000; + tools::Long nYDiv=10000; + if (nXMul!=nXDiv) { + aPt.setX( aPt.X() * nXMul ); + aPt.setX( aPt.X() / nXDiv ); + } + if (nYMul!=nYDiv) { + aPt.setY( aPt.Y() * nYMul ); + aPt.setY( aPt.Y() / nYDiv ); + } + } + aPt+=aOfs; + // Now limit to the BoundRect of the object + if (aPt.X()<aBound.Left ()) aPt.setX(aBound.Left () ); + if (aPt.X()>aBound.Right ()) aPt.setX(aBound.Right () ); + if (aPt.Y()<aBound.Top ()) aPt.setY(aBound.Top () ); + if (aPt.Y()>aBound.Bottom()) aPt.setY(aBound.Bottom() ); + return aPt; +} + +void SdrGluePoint::SetAbsolutePos(const Point& rNewPos, const SdrObject& rObj) +{ + if (m_bReallyAbsolute) { + m_aPos=rNewPos; + return; + } + tools::Rectangle aSnap(rObj.GetSnapRect()); + Point aPt(rNewPos); + + Point aOfs(aSnap.Center()); + switch (GetHorzAlign()) { + case SdrAlign::HORZ_LEFT : aOfs.setX(aSnap.Left() ); break; + case SdrAlign::HORZ_RIGHT : aOfs.setX(aSnap.Right() ); break; + default: break; + } + switch (GetVertAlign()) { + case SdrAlign::VERT_TOP : aOfs.setY(aSnap.Top() ); break; + case SdrAlign::VERT_BOTTOM: aOfs.setY(aSnap.Bottom() ); break; + default: break; + } + aPt-=aOfs; + if (!m_bNoPercent) { + tools::Long nXMul=aSnap.Right()-aSnap.Left(); + tools::Long nYMul=aSnap.Bottom()-aSnap.Top(); + if (nXMul==0) nXMul=1; + if (nYMul==0) nYMul=1; + tools::Long nXDiv=10000; + tools::Long nYDiv=10000; + if (nXMul!=nXDiv) { + aPt.setX( aPt.X() * nXDiv ); + aPt.setX( aPt.X() / nXMul ); + } + if (nYMul!=nYDiv) { + aPt.setY( aPt.Y() * nYDiv ); + aPt.setY( aPt.Y() / nYMul ); + } + } + m_aPos=aPt; +} + +Degree100 SdrGluePoint::GetAlignAngle() const +{ + if (m_nAlign == (SdrAlign::HORZ_CENTER|SdrAlign::VERT_CENTER)) + return 0_deg100; // Invalid! + else if (m_nAlign == (SdrAlign::HORZ_RIGHT |SdrAlign::VERT_CENTER)) + return 0_deg100; + else if (m_nAlign == (SdrAlign::HORZ_RIGHT |SdrAlign::VERT_TOP)) + return 4500_deg100; + else if (m_nAlign == (SdrAlign::HORZ_CENTER|SdrAlign::VERT_TOP)) + return 9000_deg100; + else if (m_nAlign == (SdrAlign::HORZ_LEFT |SdrAlign::VERT_TOP)) + return 13500_deg100; + else if (m_nAlign == (SdrAlign::HORZ_LEFT |SdrAlign::VERT_CENTER)) + return 18000_deg100; + else if (m_nAlign == (SdrAlign::HORZ_LEFT |SdrAlign::VERT_BOTTOM)) + return 22500_deg100; + else if (m_nAlign == (SdrAlign::HORZ_CENTER|SdrAlign::VERT_BOTTOM)) + return 27000_deg100; + else if (m_nAlign == (SdrAlign::HORZ_RIGHT |SdrAlign::VERT_BOTTOM)) + return 31500_deg100; + return 0_deg100; +} + +void SdrGluePoint::SetAlignAngle(Degree100 nAngle) +{ + nAngle=NormAngle36000(nAngle); + if (nAngle>=33750_deg100 || nAngle<2250_deg100) m_nAlign=SdrAlign::HORZ_RIGHT |SdrAlign::VERT_CENTER; + else if (nAngle< 6750_deg100) m_nAlign=SdrAlign::HORZ_RIGHT |SdrAlign::VERT_TOP ; + else if (nAngle<11250_deg100) m_nAlign=SdrAlign::HORZ_CENTER|SdrAlign::VERT_TOP ; + else if (nAngle<15750_deg100) m_nAlign=SdrAlign::HORZ_LEFT |SdrAlign::VERT_TOP ; + else if (nAngle<20250_deg100) m_nAlign=SdrAlign::HORZ_LEFT |SdrAlign::VERT_CENTER; + else if (nAngle<24750_deg100) m_nAlign=SdrAlign::HORZ_LEFT |SdrAlign::VERT_BOTTOM; + else if (nAngle<29250_deg100) m_nAlign=SdrAlign::HORZ_CENTER|SdrAlign::VERT_BOTTOM; + else if (nAngle<33750_deg100) m_nAlign=SdrAlign::HORZ_RIGHT |SdrAlign::VERT_BOTTOM; +} + +Degree100 SdrGluePoint::EscDirToAngle(SdrEscapeDirection nEsc) +{ + switch (nEsc) { + case SdrEscapeDirection::RIGHT : return 0_deg100; + case SdrEscapeDirection::TOP : return 9000_deg100; + case SdrEscapeDirection::LEFT : return 18000_deg100; + case SdrEscapeDirection::BOTTOM: return 27000_deg100; + default: break; + } // switch + return 0_deg100; +} + +SdrEscapeDirection SdrGluePoint::EscAngleToDir(Degree100 nAngle) +{ + nAngle=NormAngle36000(nAngle); + if (nAngle>=31500_deg100 || nAngle<4500_deg100) + return SdrEscapeDirection::RIGHT; + if (nAngle<13500_deg100) + return SdrEscapeDirection::TOP; + if (nAngle<22500_deg100) + return SdrEscapeDirection::LEFT; + /* (nAngle<31500)*/ + return SdrEscapeDirection::BOTTOM; +} + +void SdrGluePoint::Rotate(const Point& rRef, Degree100 nAngle, double sn, double cs, const SdrObject* pObj) +{ + Point aPt(pObj!=nullptr ? GetAbsolutePos(*pObj) : GetPos()); + RotatePoint(aPt,rRef,sn,cs); + // rotate reference edge + if(m_nAlign != (SdrAlign::HORZ_CENTER|SdrAlign::VERT_CENTER)) + { + SetAlignAngle(GetAlignAngle()+nAngle); + } + // rotate exit directions + SdrEscapeDirection nEscDir0=m_nEscDir; + SdrEscapeDirection nEscDir1=SdrEscapeDirection::SMART; + if (nEscDir0&SdrEscapeDirection::LEFT ) nEscDir1 |= EscAngleToDir(EscDirToAngle(SdrEscapeDirection::LEFT )+nAngle); + if (nEscDir0&SdrEscapeDirection::TOP ) nEscDir1 |= EscAngleToDir(EscDirToAngle(SdrEscapeDirection::TOP )+nAngle); + if (nEscDir0&SdrEscapeDirection::RIGHT ) nEscDir1 |= EscAngleToDir(EscDirToAngle(SdrEscapeDirection::RIGHT )+nAngle); + if (nEscDir0&SdrEscapeDirection::BOTTOM) nEscDir1 |= EscAngleToDir(EscDirToAngle(SdrEscapeDirection::BOTTOM)+nAngle); + m_nEscDir=nEscDir1; + if (pObj!=nullptr) SetAbsolutePos(aPt,*pObj); else SetPos(aPt); +} + +void SdrGluePoint::Mirror(const Point& rRef1, const Point& rRef2, Degree100 nAngle, const SdrObject* pObj) +{ + Point aPt(pObj!=nullptr ? GetAbsolutePos(*pObj) : GetPos()); + MirrorPoint(aPt,rRef1,rRef2); + // mirror reference edge + if(m_nAlign != (SdrAlign::HORZ_CENTER|SdrAlign::VERT_CENTER)) + { + Degree100 nAW=GetAlignAngle(); + nAW+=2_deg100*(nAngle-nAW); + SetAlignAngle(nAW); + } + // mirror exit directions + SdrEscapeDirection nEscDir0=m_nEscDir; + SdrEscapeDirection nEscDir1=SdrEscapeDirection::SMART; + if (nEscDir0&SdrEscapeDirection::LEFT) { + Degree100 nEW=EscDirToAngle(SdrEscapeDirection::LEFT); + nEW+=2_deg100*(nAngle-nEW); + nEscDir1|=EscAngleToDir(nEW); + } + if (nEscDir0&SdrEscapeDirection::TOP) { + Degree100 nEW=EscDirToAngle(SdrEscapeDirection::TOP); + nEW+=2_deg100*(nAngle-nEW); + nEscDir1|=EscAngleToDir(nEW); + } + if (nEscDir0&SdrEscapeDirection::RIGHT) { + Degree100 nEW=EscDirToAngle(SdrEscapeDirection::RIGHT); + nEW+=2_deg100*(nAngle-nEW); + nEscDir1|=EscAngleToDir(nEW); + } + if (nEscDir0&SdrEscapeDirection::BOTTOM) { + Degree100 nEW=EscDirToAngle(SdrEscapeDirection::BOTTOM); + nEW+=2_deg100*(nAngle-nEW); + nEscDir1|=EscAngleToDir(nEW); + } + m_nEscDir=nEscDir1; + if (pObj!=nullptr) SetAbsolutePos(aPt,*pObj); else SetPos(aPt); +} + +void SdrGluePoint::Shear(const Point& rRef, double tn, bool bVShear, const SdrObject* pObj) +{ + Point aPt(pObj!=nullptr ? GetAbsolutePos(*pObj) : GetPos()); + ShearPoint(aPt,rRef,tn,bVShear); + if (pObj!=nullptr) SetAbsolutePos(aPt,*pObj); else SetPos(aPt); +} + +void SdrGluePoint::Invalidate(vcl::Window& rWin, const SdrObject* pObj) const +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + bool bMapMode=rWin.IsMapModeEnabled(); + Point aPt(pObj!=nullptr ? GetAbsolutePos(*pObj) : GetPos()); + aPt=rWin.LogicToPixel(aPt); + rWin.EnableMapMode(false); + + Size aSiz( aGlueHalfSize ); + tools::Rectangle aRect(aPt.X()-aSiz.Width(),aPt.Y()-aSiz.Height(), + aPt.X()+aSiz.Width(),aPt.Y()+aSiz.Height()); + + // do not erase background, that causes flicker (!) + rWin.Invalidate(aRect, InvalidateFlags::NoErase); + + rWin.EnableMapMode(bMapMode); +} + +bool SdrGluePoint::IsHit(const Point& rPnt, const OutputDevice& rOut, const SdrObject* pObj) const +{ + Point aPt(pObj!=nullptr ? GetAbsolutePos(*pObj) : GetPos()); + Size aSiz=rOut.PixelToLogic(aGlueHalfSize); + tools::Rectangle aRect(aPt.X()-aSiz.Width(),aPt.Y()-aSiz.Height(),aPt.X()+aSiz.Width(),aPt.Y()+aSiz.Height()); + return aRect.Contains(rPnt); +} + + +SdrGluePointList& SdrGluePointList::operator=(const SdrGluePointList& rSrcList) +{ + if (GetCount()!=0) m_aList.clear(); + sal_uInt16 nCount=rSrcList.GetCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + Insert(rSrcList[i]); + } + return *this; +} + +// The ID's of the gluepoints always increase monotonously! +// If an ID is taken already, the new gluepoint gets a new ID. ID 0 is reserved. +sal_uInt16 SdrGluePointList::Insert(const SdrGluePoint& rGP) +{ + SdrGluePoint aGP(rGP); + sal_uInt16 nId=aGP.GetId(); + sal_uInt16 nCount=GetCount(); + sal_uInt16 nInsPos=nCount; + sal_uInt16 nLastId=nCount!=0 ? m_aList[nCount-1].GetId() : 0; + DBG_ASSERT(nLastId>=nCount,"SdrGluePointList::Insert(): nLastId<nCount"); + bool bHole = nLastId>nCount; + if (nId<=nLastId) { + if (!bHole || nId==0) { + nId=nLastId+1; + } else { + bool bBrk = false; + for (sal_uInt16 nNum=0; nNum<nCount && !bBrk; nNum++) { + const auto& pGP2=m_aList[nNum]; + sal_uInt16 nTmpId=pGP2.GetId(); + if (nTmpId==nId) { + nId=nLastId+1; // already in use + bBrk = true; + } + if (nTmpId>nId) { + nInsPos=nNum; // insert here (sort) + bBrk = true; + } + } + } + aGP.SetId(nId); + } + m_aList.emplace(m_aList.begin()+nInsPos, aGP); + return nInsPos; +} + +void SdrGluePointList::Invalidate(vcl::Window& rWin, const SdrObject* pObj) const +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + for (auto& xGP : m_aList) + xGP.Invalidate(rWin,pObj); +} + +sal_uInt16 SdrGluePointList::FindGluePoint(sal_uInt16 nId) const +{ + // TODO: Implement a better search algorithm + // List should be sorted at all times! + sal_uInt16 nCount=GetCount(); + sal_uInt16 nRet=SDRGLUEPOINT_NOTFOUND; + for (sal_uInt16 nNum=0; nNum<nCount && nRet==SDRGLUEPOINT_NOTFOUND; nNum++) { + const auto& pGP=m_aList[nNum]; + if (pGP.GetId()==nId) nRet=nNum; + } + return nRet; +} + +sal_uInt16 SdrGluePointList::HitTest(const Point& rPnt, const OutputDevice& rOut, const SdrObject* pObj) const +{ + sal_uInt16 nCount = GetCount(); + sal_uInt16 nRet = SDRGLUEPOINT_NOTFOUND; + sal_uInt16 nNum = nCount; + while ((nNum>0) && nRet==SDRGLUEPOINT_NOTFOUND) { + nNum--; + const auto& pGP = m_aList[nNum]; + if (pGP.IsHit(rPnt,rOut,pObj)) + nRet = nNum; + } + return nRet; +} + +void SdrGluePointList::SetReallyAbsolute(bool bOn, const SdrObject& rObj) +{ + for (auto& xGP : m_aList) + xGP.SetReallyAbsolute(bOn,rObj); +} + +void SdrGluePointList::Rotate(const Point& rRef, Degree100 nAngle, double sn, double cs, const SdrObject* pObj) +{ + for (auto& xGP : m_aList) + xGP.Rotate(rRef,nAngle,sn,cs,pObj); +} + +void SdrGluePointList::Mirror(const Point& rRef1, const Point& rRef2, const SdrObject* pObj) +{ + Point aPt(rRef2); aPt-=rRef1; + Degree100 nAngle=GetAngle(aPt); + Mirror(rRef1,rRef2,nAngle,pObj); +} + +void SdrGluePointList::Mirror(const Point& rRef1, const Point& rRef2, Degree100 nAngle, const SdrObject* pObj) +{ + for (auto& xGP : m_aList) + xGP.Mirror(rRef1,rRef2,nAngle,pObj); +} + +void SdrGluePointList::Shear(const Point& rRef, double tn, bool bVShear, const SdrObject* pObj) +{ + for (auto& xGP : m_aList) + xGP.Shear(rRef,tn,bVShear,pObj); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdhdl.cxx b/svx/source/svdraw/svdhdl.cxx new file mode 100644 index 0000000000..867afa6a90 --- /dev/null +++ b/svx/source/svdraw/svdhdl.cxx @@ -0,0 +1,2674 @@ +/* -*- 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 <algorithm> +#include <cassert> + +#include <svx/svdhdl.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdmrkv.hxx> +#include <utility> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/ptrstyle.hxx> + +#include <svx/sxekitm.hxx> +#include <svx/strings.hrc> +#include <svx/svdmodel.hxx> +#include "gradtrns.hxx" +#include <svx/xflgrit.hxx> +#include <svx/svdundo.hxx> +#include <svx/dialmgr.hxx> +#include <svx/xflftrit.hxx> + +#include <svx/svdopath.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdr/overlay/overlayanimatedbitmapex.hxx> +#include <svx/sdr/overlay/overlaybitmapex.hxx> +#include <sdr/overlay/overlayline.hxx> +#include <sdr/overlay/overlaytriangle.hxx> +#include <sdr/overlay/overlayhandle.hxx> +#include <sdr/overlay/overlayrectangle.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <vcl/svapp.hxx> +#include <svx/sdr/overlay/overlaypolypolygon.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/BitmapTools.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <osl/diagnose.h> + +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <memory> +#include <bitmaps.hlst> + +namespace { + +// #i15222# +// Due to the resource problems in Win95/98 with bitmap resources I +// will change this handle bitmap providing class. Old version was splitting +// and preparing all small handle bitmaps in device bitmap format, now this will +// be done on the fly. Thus, there is only one big bitmap in memory. With +// three source bitmaps, this will be 3 system bitmap resources instead of hundreds. +// The price for that needs to be evaluated. Maybe we will need another change here +// if this is too expensive. +class SdrHdlBitmapSet +{ + // the bitmap holding all information + BitmapEx maMarkersBitmap; + + // the cropped Bitmaps for reusage + ::std::vector< BitmapEx > maRealMarkers; + + // helpers + BitmapEx& impGetOrCreateTargetBitmap(sal_uInt16 nIndex, const tools::Rectangle& rRectangle); + +public: + explicit SdrHdlBitmapSet(); + + const BitmapEx& GetBitmapEx(BitmapMarkerKind eKindOfMarker, sal_uInt16 nInd); +}; + +} + +#define KIND_COUNT (14) +#define INDEX_COUNT (6) +#define INDIVIDUAL_COUNT (5) + +SdrHdlBitmapSet::SdrHdlBitmapSet() + : maMarkersBitmap(SIP_SA_MARKERS), + // 15 kinds (BitmapMarkerKind) use index [0..5] + 5 extra + maRealMarkers((KIND_COUNT * INDEX_COUNT) + INDIVIDUAL_COUNT) +{ +} + +BitmapEx& SdrHdlBitmapSet::impGetOrCreateTargetBitmap(sal_uInt16 nIndex, const tools::Rectangle& rRectangle) +{ + BitmapEx& rTargetBitmap = maRealMarkers[nIndex]; + + if(rTargetBitmap.IsEmpty()) + { + rTargetBitmap = maMarkersBitmap; + rTargetBitmap.Crop(rRectangle); + } + + return rTargetBitmap; +} + +// change getting of bitmap to use the big resource bitmap +const BitmapEx& SdrHdlBitmapSet::GetBitmapEx(BitmapMarkerKind eKindOfMarker, sal_uInt16 nInd) +{ + // fill in size and source position in maMarkersBitmap + const sal_uInt16 nYPos(nInd * 11); + + switch(eKindOfMarker) + { + default: + { + OSL_FAIL( "Unknown kind of marker." ); + [[fallthrough]]; // return Rect_9x9 as default + } + case BitmapMarkerKind::Rect_9x9: + { + return impGetOrCreateTargetBitmap((1 * INDEX_COUNT) + nInd, tools::Rectangle(Point(7, nYPos), Size(9, 9))); + } + + case BitmapMarkerKind::Rect_7x7: + { + return impGetOrCreateTargetBitmap((0 * INDEX_COUNT) + nInd, tools::Rectangle(Point(0, nYPos), Size(7, 7))); + } + + case BitmapMarkerKind::Rect_11x11: + { + return impGetOrCreateTargetBitmap((2 * INDEX_COUNT) + nInd, tools::Rectangle(Point(16, nYPos), Size(11, 11))); + } + + case BitmapMarkerKind::Rect_13x13: + { + const sal_uInt16 nIndex((3 * INDEX_COUNT) + nInd); + + switch(nInd) + { + case 0: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(72, 66), Size(13, 13))); + } + case 1: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(85, 66), Size(13, 13))); + } + case 2: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(72, 79), Size(13, 13))); + } + case 3: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(85, 79), Size(13, 13))); + } + case 4: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(98, 79), Size(13, 13))); + } + default: // case 5: + { + return impGetOrCreateTargetBitmap(nIndex, tools::Rectangle(Point(98, 66), Size(13, 13))); + } + } + } + + case BitmapMarkerKind::Circ_7x7: + case BitmapMarkerKind::Customshape_7x7: + { + return impGetOrCreateTargetBitmap((4 * INDEX_COUNT) + nInd, tools::Rectangle(Point(27, nYPos), Size(7, 7))); + } + + case BitmapMarkerKind::Circ_9x9: + case BitmapMarkerKind::Customshape_9x9: + { + return impGetOrCreateTargetBitmap((5 * INDEX_COUNT) + nInd, tools::Rectangle(Point(34, nYPos), Size(9, 9))); + } + + case BitmapMarkerKind::Circ_11x11: + case BitmapMarkerKind::Customshape_11x11: + { + return impGetOrCreateTargetBitmap((6 * INDEX_COUNT) + nInd, tools::Rectangle(Point(43, nYPos), Size(11, 11))); + } + + case BitmapMarkerKind::Elli_7x9: + { + return impGetOrCreateTargetBitmap((7 * INDEX_COUNT) + nInd, tools::Rectangle(Point(54, nYPos), Size(7, 9))); + } + + case BitmapMarkerKind::Elli_9x11: + { + return impGetOrCreateTargetBitmap((8 * INDEX_COUNT) + nInd, tools::Rectangle(Point(61, nYPos), Size(9, 11))); + } + + case BitmapMarkerKind::Elli_9x7: + { + return impGetOrCreateTargetBitmap((9 * INDEX_COUNT) + nInd, tools::Rectangle(Point(70, nYPos), Size(9, 7))); + } + + case BitmapMarkerKind::Elli_11x9: + { + return impGetOrCreateTargetBitmap((10 * INDEX_COUNT) + nInd, tools::Rectangle(Point(79, nYPos), Size(11, 9))); + } + + case BitmapMarkerKind::RectPlus_7x7: + { + return impGetOrCreateTargetBitmap((11 * INDEX_COUNT) + nInd, tools::Rectangle(Point(90, nYPos), Size(7, 7))); + } + + case BitmapMarkerKind::RectPlus_9x9: + { + return impGetOrCreateTargetBitmap((12 * INDEX_COUNT) + nInd, tools::Rectangle(Point(97, nYPos), Size(9, 9))); + } + + case BitmapMarkerKind::RectPlus_11x11: + { + return impGetOrCreateTargetBitmap((13 * INDEX_COUNT) + nInd, tools::Rectangle(Point(106, nYPos), Size(11, 11))); + } + + case BitmapMarkerKind::Crosshair: + { + return impGetOrCreateTargetBitmap((KIND_COUNT * INDEX_COUNT) + 0, tools::Rectangle(Point(0, 68), Size(15, 15))); + } + + case BitmapMarkerKind::Glue: + { + return impGetOrCreateTargetBitmap((KIND_COUNT * INDEX_COUNT) + 1, tools::Rectangle(Point(15, 76), Size(9, 9))); + } + + case BitmapMarkerKind::Glue_Deselected: + { + return impGetOrCreateTargetBitmap((KIND_COUNT * INDEX_COUNT) + 2, tools::Rectangle(Point(15, 67), Size(9, 9))); + } + + case BitmapMarkerKind::Anchor: // AnchorTR for SW + case BitmapMarkerKind::AnchorTR: + { + return impGetOrCreateTargetBitmap((KIND_COUNT * INDEX_COUNT) + 3, tools::Rectangle(Point(24, 67), Size(24, 24))); + } + + // add AnchorPressed to be able to animate anchor control + case BitmapMarkerKind::AnchorPressed: + case BitmapMarkerKind::AnchorPressedTR: + { + return impGetOrCreateTargetBitmap((KIND_COUNT * INDEX_COUNT) + 4, tools::Rectangle(Point(48, 67), Size(24, 24))); + } + } +} + + +SdrHdl::SdrHdl(): + m_pObj(nullptr), + m_pPV(nullptr), + m_pHdlList(nullptr), + m_eKind(SdrHdlKind::Move), + m_nRotationAngle(0), + m_nObjHdlNum(0), + m_nPolyNum(0), + m_nPPntNum(0), + m_nSourceHdlNum(0), + m_bSelect(false), + m_b1PixMore(false), + m_bPlusHdl(false), + mbMoveOutside(false), + mbMouseOver(false) +{ +} + +SdrHdl::SdrHdl(const Point& rPnt, SdrHdlKind eNewKind): + m_pObj(nullptr), + m_pPV(nullptr), + m_pHdlList(nullptr), + m_aPos(rPnt), + m_eKind(eNewKind), + m_nRotationAngle(0), + m_nObjHdlNum(0), + m_nPolyNum(0), + m_nPPntNum(0), + m_nSourceHdlNum(0), + m_bSelect(false), + m_b1PixMore(false), + m_bPlusHdl(false), + mbMoveOutside(false), + mbMouseOver(false) +{ +} + +SdrHdl::~SdrHdl() +{ + GetRidOfIAObject(); +} + +void SdrHdl::Set1PixMore(bool bJa) +{ + if(m_b1PixMore != bJa) + { + m_b1PixMore = bJa; + + // create new display + Touch(); + } +} + +void SdrHdl::SetMoveOutside( bool bMoveOutside ) +{ + if(mbMoveOutside != bMoveOutside) + { + mbMoveOutside = bMoveOutside; + + // create new display + Touch(); + } +} + +void SdrHdl::SetRotationAngle(Degree100 n) +{ + if(m_nRotationAngle != n) + { + m_nRotationAngle = n; + + // create new display + Touch(); + } +} + +void SdrHdl::SetPos(const Point& rPnt) +{ + if(m_aPos != rPnt) + { + // remember new position + m_aPos = rPnt; + + // create new display + Touch(); + } +} + +void SdrHdl::SetSelected(bool bJa) +{ + if(m_bSelect != bJa) + { + // remember new value + m_bSelect = bJa; + + // create new display + Touch(); + } +} + +void SdrHdl::SetHdlList(SdrHdlList* pList) +{ + if(m_pHdlList != pList) + { + // remember list + m_pHdlList = pList; + + // now it's possible to create graphic representation + Touch(); + } +} + +void SdrHdl::SetObj(SdrObject* pNewObj) +{ + if(m_pObj != pNewObj) + { + // remember new object + m_pObj = pNewObj; + + // graphic representation may have changed + Touch(); + } +} + +void SdrHdl::Touch() +{ + // force update of graphic representation + CreateB2dIAObject(); +} + +void SdrHdl::GetRidOfIAObject() +{ + + // OVERLAYMANAGER + maOverlayGroup.clear(); +} + +void SdrHdl::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList || !m_pHdlList->GetView() || m_pHdlList->GetView()->areMarkHandlesHidden()) + return; + + BitmapColorIndex eColIndex = BitmapColorIndex::LightGreen; + BitmapMarkerKind eKindOfMarker = BitmapMarkerKind::Rect_7x7; + + bool bRot = m_pHdlList->IsRotateShear(); + if(m_pObj) + eColIndex = m_bSelect ? BitmapColorIndex::Cyan : BitmapColorIndex::LightCyan; + if(bRot) + { + // red rotation handles + if(m_pObj && m_bSelect) + eColIndex = BitmapColorIndex::Red; + else + eColIndex = BitmapColorIndex::LightRed; + } + + switch(m_eKind) + { + case SdrHdlKind::Move: + { + eKindOfMarker = m_b1PixMore ? BitmapMarkerKind::Rect_9x9 : BitmapMarkerKind::Rect_7x7; + break; + } + case SdrHdlKind::UpperLeft: + case SdrHdlKind::UpperRight: + case SdrHdlKind::LowerLeft: + case SdrHdlKind::LowerRight: + { + // corner handles + if(bRot) + { + eKindOfMarker = BitmapMarkerKind::Circ_7x7; + } + else + { + eKindOfMarker = BitmapMarkerKind::Rect_7x7; + } + break; + } + case SdrHdlKind::Upper: + case SdrHdlKind::Lower: + { + // Upper/Lower handles + if(bRot) + { + eKindOfMarker = BitmapMarkerKind::Elli_9x7; + } + else + { + eKindOfMarker = BitmapMarkerKind::Rect_7x7; + } + break; + } + case SdrHdlKind::Left: + case SdrHdlKind::Right: + { + // Left/Right handles + if(bRot) + { + eKindOfMarker = BitmapMarkerKind::Elli_7x9; + } + else + { + eKindOfMarker = BitmapMarkerKind::Rect_7x7; + } + break; + } + case SdrHdlKind::Poly: + { + if(bRot) + { + eKindOfMarker = m_b1PixMore ? BitmapMarkerKind::Circ_9x9 : BitmapMarkerKind::Circ_7x7; + } + else + { + eKindOfMarker = m_b1PixMore ? BitmapMarkerKind::Rect_9x9 : BitmapMarkerKind::Rect_7x7; + } + break; + } + case SdrHdlKind::BezierWeight: // weight at poly + { + eKindOfMarker = BitmapMarkerKind::Circ_7x7; + break; + } + case SdrHdlKind::Circle: + { + eKindOfMarker = BitmapMarkerKind::Rect_11x11; + break; + } + case SdrHdlKind::Ref1: + case SdrHdlKind::Ref2: + { + eKindOfMarker = BitmapMarkerKind::Crosshair; + break; + } + case SdrHdlKind::Glue: + { + eKindOfMarker = BitmapMarkerKind::Glue; + break; + } + case SdrHdlKind::Anchor: + { + eKindOfMarker = BitmapMarkerKind::Anchor; + break; + } + case SdrHdlKind::User: + { + break; + } + // top right anchor for SW + case SdrHdlKind::Anchor_TR: + { + eKindOfMarker = BitmapMarkerKind::AnchorTR; + break; + } + + // for SJ and the CustomShapeHandles: + case SdrHdlKind::CustomShape1: + { + eKindOfMarker = m_b1PixMore ? BitmapMarkerKind::Customshape_9x9 : BitmapMarkerKind::Customshape_7x7; + eColIndex = BitmapColorIndex::Yellow; + break; + } + default: + break; + } + + SdrMarkView* pView = m_pHdlList->GetView(); + SdrPageView* pPageView = pView->GetSdrPageView(); + + if(!pPageView) + return; + + for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++) + { + // const SdrPageViewWinRec& rPageViewWinRec = rPageViewWinList[b]; + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b); + + if(rPageWindow.GetPaintWindow().OutputToWindow()) + { + Point aMoveOutsideOffset(0, 0); + OutputDevice& rOutDev = rPageWindow.GetPaintWindow().GetOutputDevice(); + + // add offset if necessary + if(m_pHdlList->IsMoveOutside() || mbMoveOutside) + { + Size aOffset = rOutDev.PixelToLogic(Size(4, 4)); + + if(m_eKind == SdrHdlKind::UpperLeft || m_eKind == SdrHdlKind::Upper || m_eKind == SdrHdlKind::UpperRight) + aMoveOutsideOffset.AdjustY( -(aOffset.Width()) ); + if(m_eKind == SdrHdlKind::LowerLeft || m_eKind == SdrHdlKind::Lower || m_eKind == SdrHdlKind::LowerRight) + aMoveOutsideOffset.AdjustY(aOffset.Height() ); + if(m_eKind == SdrHdlKind::UpperLeft || m_eKind == SdrHdlKind::Left || m_eKind == SdrHdlKind::LowerLeft) + aMoveOutsideOffset.AdjustX( -(aOffset.Width()) ); + if(m_eKind == SdrHdlKind::UpperRight || m_eKind == SdrHdlKind::Right || m_eKind == SdrHdlKind::LowerRight) + aMoveOutsideOffset.AdjustX(aOffset.Height() ); + } + + const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager(); + if (xManager.is()) + { + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject; + if (getenv ("SVX_DRAW_HANDLES") && (eKindOfMarker == BitmapMarkerKind::Rect_7x7 || eKindOfMarker == BitmapMarkerKind::Rect_9x9 || eKindOfMarker == BitmapMarkerKind::Rect_11x11)) + { + double fSize = 7.0; + switch (eKindOfMarker) + { + case BitmapMarkerKind::Rect_9x9: + fSize = 9.0; + break; + case BitmapMarkerKind::Rect_11x11: + fSize = 11.0; + break; + default: + break; + } + float fScalingFactor = rOutDev.GetDPIScaleFactor(); + basegfx::B2DSize aB2DSize(fSize * fScalingFactor, fSize * fScalingFactor); + + Color aHandleFillColor(COL_LIGHTGREEN); + switch (eColIndex) + { + case BitmapColorIndex::Cyan: + aHandleFillColor = COL_CYAN; + break; + case BitmapColorIndex::LightCyan: + aHandleFillColor = COL_LIGHTCYAN; + break; + case BitmapColorIndex::Red: + aHandleFillColor = COL_RED; + break; + case BitmapColorIndex::LightRed: + aHandleFillColor = COL_LIGHTRED; + break; + case BitmapColorIndex::Yellow: + aHandleFillColor = COL_YELLOW; + break; + default: + break; + } + pNewOverlayObject.reset(new sdr::overlay::OverlayHandle(aPosition, aB2DSize, /*HandleStrokeColor*/COL_BLACK, aHandleFillColor)); + } + else + { + pNewOverlayObject = CreateOverlayObject( + aPosition, eColIndex, eKindOfMarker, + aMoveOutsideOffset); + } + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +BitmapMarkerKind SdrHdl::GetNextBigger(BitmapMarkerKind eKnd) +{ + BitmapMarkerKind eRetval(eKnd); + + switch(eKnd) + { + case BitmapMarkerKind::Rect_7x7: eRetval = BitmapMarkerKind::Rect_9x9; break; + case BitmapMarkerKind::Rect_9x9: eRetval = BitmapMarkerKind::Rect_11x11; break; + case BitmapMarkerKind::Rect_11x11: eRetval = BitmapMarkerKind::Rect_13x13; break; + + case BitmapMarkerKind::Circ_7x7: eRetval = BitmapMarkerKind::Circ_9x9; break; + case BitmapMarkerKind::Circ_9x9: eRetval = BitmapMarkerKind::Circ_11x11; break; + + case BitmapMarkerKind::Customshape_7x7: eRetval = BitmapMarkerKind::Customshape_9x9; break; + case BitmapMarkerKind::Customshape_9x9: eRetval = BitmapMarkerKind::Customshape_11x11; break; + //case BitmapMarkerKind::Customshape_11x11: eRetval = ; break; + + case BitmapMarkerKind::Elli_7x9: eRetval = BitmapMarkerKind::Elli_9x11; break; + + case BitmapMarkerKind::Elli_9x7: eRetval = BitmapMarkerKind::Elli_11x9; break; + + case BitmapMarkerKind::RectPlus_7x7: eRetval = BitmapMarkerKind::RectPlus_9x9; break; + case BitmapMarkerKind::RectPlus_9x9: eRetval = BitmapMarkerKind::RectPlus_11x11; break; + + // let anchor blink with its pressed state + case BitmapMarkerKind::Anchor: eRetval = BitmapMarkerKind::AnchorPressed; break; + + // same for AnchorTR + case BitmapMarkerKind::AnchorTR: eRetval = BitmapMarkerKind::AnchorPressedTR; break; + default: + break; + } + + return eRetval; +} + +namespace +{ + +OUString appendMarkerName(BitmapMarkerKind eKindOfMarker) +{ + switch(eKindOfMarker) + { + case BitmapMarkerKind::Rect_7x7: + return "rect7"; + case BitmapMarkerKind::Rect_9x9: + return "rect9"; + case BitmapMarkerKind::Rect_11x11: + return "rect11"; + case BitmapMarkerKind::Rect_13x13: + return "rect13"; + case BitmapMarkerKind::Circ_7x7: + case BitmapMarkerKind::Customshape_7x7: + return "circ7"; + case BitmapMarkerKind::Circ_9x9: + case BitmapMarkerKind::Customshape_9x9: + return "circ9"; + case BitmapMarkerKind::Circ_11x11: + case BitmapMarkerKind::Customshape_11x11: + return "circ11"; + case BitmapMarkerKind::Elli_7x9: + return "elli7x9"; + case BitmapMarkerKind::Elli_9x11: + return "elli9x11"; + case BitmapMarkerKind::Elli_9x7: + return "elli9x7"; + case BitmapMarkerKind::Elli_11x9: + return "elli11x9"; + case BitmapMarkerKind::RectPlus_7x7: + return "rectplus7"; + case BitmapMarkerKind::RectPlus_9x9: + return "rectplus9"; + case BitmapMarkerKind::RectPlus_11x11: + return "rectplus11"; + case BitmapMarkerKind::Crosshair: + return "cross"; + case BitmapMarkerKind::Anchor: + case BitmapMarkerKind::AnchorTR: + return "anchor"; + case BitmapMarkerKind::AnchorPressed: + case BitmapMarkerKind::AnchorPressedTR: + return "anchor-pressed"; + case BitmapMarkerKind::Glue: + return "glue-selected"; + case BitmapMarkerKind::Glue_Deselected: + return "glue-unselected"; + default: + break; + } + return OUString(); +} + +OUString appendMarkerColor(BitmapColorIndex eIndex) +{ + switch(eIndex) + { + case BitmapColorIndex::LightGreen: + return "1"; + case BitmapColorIndex::Cyan: + return "2"; + case BitmapColorIndex::LightCyan: + return "3"; + case BitmapColorIndex::Red: + return "4"; + case BitmapColorIndex::LightRed: + return "5"; + case BitmapColorIndex::Yellow: + return "6"; + default: + break; + } + return OUString(); +} + +BitmapEx ImpGetBitmapEx(BitmapMarkerKind eKindOfMarker, BitmapColorIndex eIndex) +{ + // use this code path only when we use HiDPI (for now) + if (Application::GetDefaultDevice()->GetDPIScalePercentage() > 100) + { + OUString sMarkerName = appendMarkerName(eKindOfMarker); + if (!sMarkerName.isEmpty()) + { + OUString sMarkerPrefix("svx/res/marker-"); + BitmapEx aBitmapEx; + + if (eKindOfMarker == BitmapMarkerKind::Crosshair + || eKindOfMarker == BitmapMarkerKind::Anchor + || eKindOfMarker == BitmapMarkerKind::AnchorTR + || eKindOfMarker == BitmapMarkerKind::AnchorPressed + || eKindOfMarker == BitmapMarkerKind::AnchorPressedTR + || eKindOfMarker == BitmapMarkerKind::Glue + || eKindOfMarker == BitmapMarkerKind::Glue_Deselected) + { + aBitmapEx = vcl::bitmap::loadFromName(sMarkerPrefix + sMarkerName + ".png"); + } + else + { + aBitmapEx = vcl::bitmap::loadFromName(sMarkerPrefix + sMarkerName + "-" + appendMarkerColor(eIndex) + ".png"); + } + + if (!aBitmapEx.IsEmpty()) + return aBitmapEx; + } + } + + // if we can't load the marker... + + static vcl::DeleteOnDeinit< SdrHdlBitmapSet > aModernSet {}; + return aModernSet.get()->GetBitmapEx(eKindOfMarker, sal_uInt16(eIndex)); +} + +} // end anonymous namespace + +std::unique_ptr<sdr::overlay::OverlayObject> SdrHdl::CreateOverlayObject( + const basegfx::B2DPoint& rPos, + BitmapColorIndex eColIndex, BitmapMarkerKind eKindOfMarker, Point aMoveOutsideOffset) +{ + std::unique_ptr<sdr::overlay::OverlayObject> pRetval; + + // support bigger sizes + bool bForceBiggerSize(false); + + if (m_pHdlList && m_pHdlList->GetHdlSize() > 3) + { + switch(eKindOfMarker) + { + case BitmapMarkerKind::Anchor: + case BitmapMarkerKind::AnchorPressed: + case BitmapMarkerKind::AnchorTR: + case BitmapMarkerKind::AnchorPressedTR: + { + // #i121463# For anchor, do not simply make bigger because of HdlSize, + // do it dependent of IsSelected() which Writer can set in drag mode + if(IsSelected()) + { + bForceBiggerSize = true; + } + break; + } + default: + { + bForceBiggerSize = true; + break; + } + } + } + + if(bForceBiggerSize) + { + eKindOfMarker = GetNextBigger(eKindOfMarker); + } + + // This handle has the focus, visualize it + if(IsFocusHdl() && m_pHdlList && m_pHdlList->GetFocusHdl() == this) + { + // create animated handle + BitmapMarkerKind eNextBigger = GetNextBigger(eKindOfMarker); + + if(eNextBigger == eKindOfMarker) + { + // this may happen for the not supported getting-bigger types. + // Choose an alternative here + switch(eKindOfMarker) + { + case BitmapMarkerKind::Rect_13x13: eNextBigger = BitmapMarkerKind::Rect_11x11; break; + case BitmapMarkerKind::Circ_11x11: eNextBigger = BitmapMarkerKind::Elli_11x9; break; + case BitmapMarkerKind::Elli_9x11: eNextBigger = BitmapMarkerKind::Elli_11x9; break; + case BitmapMarkerKind::Elli_11x9: eNextBigger = BitmapMarkerKind::Elli_9x11; break; + case BitmapMarkerKind::RectPlus_11x11: eNextBigger = BitmapMarkerKind::Rect_13x13; break; + + case BitmapMarkerKind::Crosshair: + eNextBigger = BitmapMarkerKind::Glue; + break; + + case BitmapMarkerKind::Glue: + eNextBigger = BitmapMarkerKind::Crosshair; + break; + case BitmapMarkerKind::Glue_Deselected: + eNextBigger = BitmapMarkerKind::Glue; + break; + default: + break; + } + } + + // create animated handle + BitmapEx aBmpEx1 = ImpGetBitmapEx(eKindOfMarker, eColIndex); + BitmapEx aBmpEx2 = ImpGetBitmapEx(eNextBigger, eColIndex); + + // #i53216# Use system cursor blink time. Use the unsigned value. + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const sal_uInt64 nBlinkTime(rStyleSettings.GetCursorBlinkTime()); + + if(eKindOfMarker == BitmapMarkerKind::Anchor || eKindOfMarker == BitmapMarkerKind::AnchorPressed) + { + // when anchor is used take upper left as reference point inside the handle + pRetval.reset(new sdr::overlay::OverlayAnimatedBitmapEx(rPos, aBmpEx1, aBmpEx2, nBlinkTime)); + } + else if(eKindOfMarker == BitmapMarkerKind::AnchorTR || eKindOfMarker == BitmapMarkerKind::AnchorPressedTR) + { + // AnchorTR for SW, take top right as (0,0) + pRetval.reset(new sdr::overlay::OverlayAnimatedBitmapEx(rPos, aBmpEx1, aBmpEx2, nBlinkTime, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Width() - 1), 0, + static_cast<sal_uInt16>(aBmpEx2.GetSizePixel().Width() - 1), 0)); + } + else + { + // create centered handle as default + pRetval.reset(new sdr::overlay::OverlayAnimatedBitmapEx(rPos, aBmpEx1, aBmpEx2, nBlinkTime, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Height() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx2.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx2.GetSizePixel().Height() - 1) >> 1)); + } + } + else + { + // create normal handle: use ImpGetBitmapEx(...) now + BitmapEx aBmpEx = ImpGetBitmapEx(eKindOfMarker, eColIndex); + + // When the image with handles is not found, the bitmap returned is + // empty. This is a problem when we use LibreOffice as a library + // (through LOKit - for example on Android) even when we don't show + // the handles, because the hit test would always return false. + // + // This HACK replaces the empty bitmap with a black 13x13 bitmap handle + // so that the hit test works for this case. + if (aBmpEx.IsEmpty()) + { + aBmpEx = BitmapEx(Size(13, 13), vcl::PixelFormat::N24_BPP); + aBmpEx.Erase(COL_BLACK); + } + + if(eKindOfMarker == BitmapMarkerKind::Anchor || eKindOfMarker == BitmapMarkerKind::AnchorPressed) + { + // upper left as reference point inside the handle for AnchorPressed, too + pRetval.reset(new sdr::overlay::OverlayBitmapEx(rPos, aBmpEx)); + } + else if(eKindOfMarker == BitmapMarkerKind::AnchorTR || eKindOfMarker == BitmapMarkerKind::AnchorPressedTR) + { + // AnchorTR for SW, take top right as (0,0) + pRetval.reset(new sdr::overlay::OverlayBitmapEx(rPos, aBmpEx, + static_cast<sal_uInt16>(aBmpEx.GetSizePixel().Width() - 1), 0)); + } + else + { + sal_uInt16 nCenX(static_cast<sal_uInt16>(aBmpEx.GetSizePixel().Width() - 1) >> 1); + sal_uInt16 nCenY(static_cast<sal_uInt16>(aBmpEx.GetSizePixel().Height() - 1) >> 1); + + if(aMoveOutsideOffset.X() > 0) + { + nCenX = 0; + } + else if(aMoveOutsideOffset.X() < 0) + { + nCenX = static_cast<sal_uInt16>(aBmpEx.GetSizePixel().Width() - 1); + } + + if(aMoveOutsideOffset.Y() > 0) + { + nCenY = 0; + } + else if(aMoveOutsideOffset.Y() < 0) + { + nCenY = static_cast<sal_uInt16>(aBmpEx.GetSizePixel().Height() - 1); + } + + // create centered handle as default + pRetval.reset(new sdr::overlay::OverlayBitmapEx(rPos, aBmpEx, nCenX, nCenY)); + } + } + + return pRetval; +} + +bool SdrHdl::IsHdlHit(const Point& rPnt) const +{ + // OVERLAYMANAGER + basegfx::B2DPoint aPosition(rPnt.X(), rPnt.Y()); + return maOverlayGroup.isHitLogic(aPosition); +} + +PointerStyle SdrHdl::GetPointer() const +{ + PointerStyle ePtr=PointerStyle::Move; + const bool bSize=m_eKind>=SdrHdlKind::UpperLeft && m_eKind<=SdrHdlKind::LowerRight; + const bool bRot=m_pHdlList!=nullptr && m_pHdlList->IsRotateShear(); + const bool bDis=m_pHdlList!=nullptr && m_pHdlList->IsDistortShear(); + if (bSize && m_pHdlList!=nullptr && (bRot || bDis)) { + switch (m_eKind) { + case SdrHdlKind::UpperLeft: case SdrHdlKind::UpperRight: + case SdrHdlKind::LowerLeft: case SdrHdlKind::LowerRight: ePtr=bRot ? PointerStyle::Rotate : PointerStyle::RefHand; break; + case SdrHdlKind::Left : case SdrHdlKind::Right: ePtr=PointerStyle::VShear; break; + case SdrHdlKind::Upper: case SdrHdlKind::Lower: ePtr=PointerStyle::HShear; break; + default: + break; + } + } else { + // When resizing rotated rectangles, rotate the mouse cursor slightly, too + if (bSize && m_nRotationAngle!=0_deg100) { + Degree100 nHdlAngle(0); + switch (m_eKind) { + case SdrHdlKind::LowerRight: nHdlAngle=31500_deg100; break; + case SdrHdlKind::Lower: nHdlAngle=27000_deg100; break; + case SdrHdlKind::LowerLeft: nHdlAngle=22500_deg100; break; + case SdrHdlKind::Left : nHdlAngle=18000_deg100; break; + case SdrHdlKind::UpperLeft: nHdlAngle=13500_deg100; break; + case SdrHdlKind::Upper: nHdlAngle=9000_deg100; break; + case SdrHdlKind::UpperRight: nHdlAngle=4500_deg100; break; + case SdrHdlKind::Right: nHdlAngle=0_deg100; break; + default: + break; + } + // a little bit more (for rounding) + nHdlAngle = NormAngle36000(nHdlAngle + m_nRotationAngle + 2249_deg100); + nHdlAngle/=4500_deg100; + switch (static_cast<sal_uInt8>(nHdlAngle.get())) { + case 0: ePtr=PointerStyle::ESize; break; + case 1: ePtr=PointerStyle::NESize; break; + case 2: ePtr=PointerStyle::NSize; break; + case 3: ePtr=PointerStyle::NWSize; break; + case 4: ePtr=PointerStyle::WSize; break; + case 5: ePtr=PointerStyle::SWSize; break; + case 6: ePtr=PointerStyle::SSize; break; + case 7: ePtr=PointerStyle::SESize; break; + } // switch + } else { + switch (m_eKind) { + case SdrHdlKind::UpperLeft: ePtr=PointerStyle::NWSize; break; + case SdrHdlKind::Upper: ePtr=PointerStyle::NSize; break; + case SdrHdlKind::UpperRight: ePtr=PointerStyle::NESize; break; + case SdrHdlKind::Left : ePtr=PointerStyle::WSize; break; + case SdrHdlKind::Right: ePtr=PointerStyle::ESize; break; + case SdrHdlKind::LowerLeft: ePtr=PointerStyle::SWSize; break; + case SdrHdlKind::Lower: ePtr=PointerStyle::SSize; break; + case SdrHdlKind::LowerRight: ePtr=PointerStyle::SESize; break; + case SdrHdlKind::Poly : ePtr=PointerStyle::MovePoint; break; + case SdrHdlKind::Circle : ePtr=PointerStyle::Hand; break; + case SdrHdlKind::Ref1 : ePtr=PointerStyle::RefHand; break; + case SdrHdlKind::Ref2 : ePtr=PointerStyle::RefHand; break; + case SdrHdlKind::BezierWeight : ePtr=PointerStyle::MoveBezierWeight; break; + case SdrHdlKind::Glue : ePtr=PointerStyle::MovePoint; break; + case SdrHdlKind::CustomShape1 : ePtr=PointerStyle::RefHand; break; + default: + break; + } + } + } + return ePtr; +} + +bool SdrHdl::IsFocusHdl() const +{ + switch(m_eKind) + { + case SdrHdlKind::UpperLeft: + case SdrHdlKind::Upper: + case SdrHdlKind::UpperRight: + case SdrHdlKind::Left: + case SdrHdlKind::Right: + case SdrHdlKind::LowerLeft: + case SdrHdlKind::Lower: + case SdrHdlKind::LowerRight: + { + // if it's an activated TextEdit, it's moved to extended points + return !m_pHdlList || !m_pHdlList->IsMoveOutside(); + } + + case SdrHdlKind::Move: // handle to move object + case SdrHdlKind::Poly: // selected point of polygon or curve + case SdrHdlKind::BezierWeight: // weight at a curve + case SdrHdlKind::Circle: // angle of circle segments, corner radius of rectangles + case SdrHdlKind::Ref1: // reference point 1, e. g. center of rotation + case SdrHdlKind::Ref2: // reference point 2, e. g. endpoint of reflection axis + case SdrHdlKind::Glue: // gluepoint + + // for SJ and the CustomShapeHandles: + case SdrHdlKind::CustomShape1: + + case SdrHdlKind::User: + { + return true; + } + + default: + { + return false; + } + } +} + +void SdrHdl::onMouseEnter(const MouseEvent& /*rMEvt*/) +{ +} + +void SdrHdl::onHelpRequest() +{ +} + +void SdrHdl::onMouseLeave() +{ +} + +BitmapEx SdrHdl::createGluePointBitmap() +{ + return ImpGetBitmapEx(BitmapMarkerKind::Glue_Deselected, BitmapColorIndex::LightGreen); +} + +void SdrHdl::insertNewlyCreatedOverlayObjectForSdrHdl( + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject, + const sdr::contact::ObjectContact& rObjectContact, + sdr::overlay::OverlayManager& rOverlayManager) +{ + // check if we have an OverlayObject + if(!pOverlayObject) + { + return; + } + + // Add GridOffset for non-linear ViewToDevice transformation (calc) + if(nullptr != GetObj() && rObjectContact.supportsGridOffsets()) + { + basegfx::B2DVector aOffset(0.0, 0.0); + const sdr::contact::ViewObjectContact& rVOC(GetObj()->GetViewContact().GetViewObjectContact( + const_cast<sdr::contact::ObjectContact&>(rObjectContact))); + + rObjectContact.calculateGridOffsetForViewObjectContact(aOffset, rVOC); + + if(!aOffset.equalZero()) + { + pOverlayObject->setOffset(aOffset); + } + } + + // add to OverlayManager + rOverlayManager.add(*pOverlayObject); + + // add to local OverlayObjectList - ownership change (!) + maOverlayGroup.append(std::move(pOverlayObject)); +} + +SdrHdlColor::SdrHdlColor(const Point& rRef, Color aCol, const Size& rSize, bool bLum) +: SdrHdl(rRef, SdrHdlKind::Color), + m_aMarkerSize(rSize), + m_bUseLuminance(bLum) +{ + if(IsUseLuminance()) + aCol = GetLuminance(aCol); + + // remember color + m_aMarkerColor = aCol; +} + +SdrHdlColor::~SdrHdlColor() +{ +} + +void SdrHdlColor::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList) + return; + + 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()) + { + BitmapEx aBmpCol(CreateColorDropper(m_aMarkerColor)); + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(new + sdr::overlay::OverlayBitmapEx( + aPosition, + aBmpCol, + static_cast<sal_uInt16>(aBmpCol.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpCol.GetSizePixel().Height() - 1) >> 1 + )); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +BitmapEx SdrHdlColor::CreateColorDropper(Color aCol) +{ + // get the Bitmap + VclPtr<VirtualDevice> pWrite(VclPtr<VirtualDevice>::Create()); + pWrite->SetOutputSizePixel(m_aMarkerSize); + pWrite->SetBackground(aCol); + pWrite->Erase(); + + // draw outer border + sal_Int32 nWidth = m_aMarkerSize.Width(); + sal_Int32 nHeight = m_aMarkerSize.Height(); + + pWrite->SetLineColor(COL_LIGHTGRAY); + pWrite->DrawLine(Point(0, 0), Point(0, nHeight - 1)); + pWrite->DrawLine(Point(1, 0), Point(nWidth - 1, 0)); + pWrite->SetLineColor(COL_GRAY); + pWrite->DrawLine(Point(1, nHeight - 1), Point(nWidth - 1, nHeight - 1)); + pWrite->DrawLine(Point(nWidth - 1, 1), Point(nWidth - 1, nHeight - 2)); + + // draw lighter UpperLeft + const Color aLightColor( + static_cast<sal_uInt8>(::std::min(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetRed()) + sal_Int16(0x0040)), sal_Int16(0x00ff))), + static_cast<sal_uInt8>(::std::min(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetGreen()) + sal_Int16(0x0040)), sal_Int16(0x00ff))), + static_cast<sal_uInt8>(::std::min(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetBlue()) + sal_Int16(0x0040)), sal_Int16(0x00ff)))); + pWrite->SetLineColor(aLightColor); + pWrite->DrawLine(Point(1, 1), Point(1, nHeight - 2)); + pWrite->DrawLine(Point(2, 1), Point(nWidth - 2, 1)); + + // draw darker LowerRight + const Color aDarkColor( + static_cast<sal_uInt8>(::std::max(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetRed()) - sal_Int16(0x0040)), sal_Int16(0x0000))), + static_cast<sal_uInt8>(::std::max(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetGreen()) - sal_Int16(0x0040)), sal_Int16(0x0000))), + static_cast<sal_uInt8>(::std::max(static_cast<sal_Int16>(static_cast<sal_Int16>(aCol.GetBlue()) - sal_Int16(0x0040)), sal_Int16(0x0000)))); + pWrite->SetLineColor(aDarkColor); + pWrite->DrawLine(Point(2, nHeight - 2), Point(nWidth - 2, nHeight - 2)); + pWrite->DrawLine(Point(nWidth - 2, 2), Point(nWidth - 2, nHeight - 3)); + + return pWrite->GetBitmapEx(Point(0,0), m_aMarkerSize); +} + +Color SdrHdlColor::GetLuminance(const Color& rCol) +{ + sal_uInt8 aLum = rCol.GetLuminance(); + Color aRetval(aLum, aLum, aLum); + return aRetval; +} + +void SdrHdlColor::SetColor(Color aNew, bool bCallLink) +{ + if(IsUseLuminance()) + aNew = GetLuminance(aNew); + + if(m_aMarkerColor != aNew) + { + // remember new color + m_aMarkerColor = aNew; + + // create new display + Touch(); + + // tell about change + if(bCallLink) + m_aColorChangeHdl.Call(this); + } +} + +void SdrHdlColor::SetSize(const Size& rNew) +{ + if(rNew != m_aMarkerSize) + { + // remember new size + m_aMarkerSize = rNew; + + // create new display + Touch(); + } +} + +SdrHdlGradient::SdrHdlGradient(const Point& rRef1, const Point& rRef2, bool bGrad) + : SdrHdl(rRef1, bGrad ? SdrHdlKind::Gradient : SdrHdlKind::Transparence) + , m_pColHdl1(nullptr) + , m_pColHdl2(nullptr) + , m_a2ndPos(rRef2) + , m_bGradient(bGrad) + , m_bMoveSingleHandle(false) + , m_bMoveFirstHandle(false) +{ +} + +SdrHdlGradient::~SdrHdlGradient() +{ +} + +void SdrHdlGradient::Set2ndPos(const Point& rPnt) +{ + if(m_a2ndPos != rPnt) + { + // remember new position + m_a2ndPos = rPnt; + + // create new display + Touch(); + } +} + +void SdrHdlGradient::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList) + return; + + 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()) + { + // striped line in between + basegfx::B2DVector aVec(m_a2ndPos.X() - m_aPos.X(), m_a2ndPos.Y() - m_aPos.Y()); + double fVecLen = aVec.getLength(); + double fLongPercentArrow = (1.0 - 0.05) * fVecLen; + double fHalfArrowWidth = (0.05 * 0.5) * fVecLen; + aVec.normalize(); + basegfx::B2DVector aPerpend(-aVec.getY(), aVec.getX()); + sal_Int32 nMidX = static_cast<sal_Int32>(m_aPos.X() + aVec.getX() * fLongPercentArrow); + sal_Int32 nMidY = static_cast<sal_Int32>(m_aPos.Y() + aVec.getY() * fLongPercentArrow); + Point aMidPoint(nMidX, nMidY); + + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + basegfx::B2DPoint aMidPos(aMidPoint.X(), aMidPoint.Y()); + + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(new + sdr::overlay::OverlayLineStriped( + aPosition, aMidPos + )); + + pNewOverlayObject->setBaseColor(IsGradient() ? COL_BLACK : COL_BLUE); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + + // arrowhead + Point aLeft(aMidPoint.X() + static_cast<sal_Int32>(aPerpend.getX() * fHalfArrowWidth), + aMidPoint.Y() + static_cast<sal_Int32>(aPerpend.getY() * fHalfArrowWidth)); + Point aRight(aMidPoint.X() - static_cast<sal_Int32>(aPerpend.getX() * fHalfArrowWidth), + aMidPoint.Y() - static_cast<sal_Int32>(aPerpend.getY() * fHalfArrowWidth)); + + basegfx::B2DPoint aPositionLeft(aLeft.X(), aLeft.Y()); + basegfx::B2DPoint aPositionRight(aRight.X(), aRight.Y()); + basegfx::B2DPoint aPosition2(m_a2ndPos.X(), m_a2ndPos.Y()); + + pNewOverlayObject.reset(new + sdr::overlay::OverlayTriangle( + aPositionLeft, + aPosition2, + aPositionRight, + IsGradient() ? COL_BLACK : COL_BLUE + )); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +IMPL_LINK_NOARG(SdrHdlGradient, ColorChangeHdl, SdrHdlColor*, void) +{ + if(GetObj()) + FromIAOToItem(GetObj(), true, true); +} + +void SdrHdlGradient::FromIAOToItem(SdrObject* _pObj, bool bSetItemOnObject, bool bUndo) +{ + // from IAO positions and colors to gradient + const SfxItemSet& rSet = _pObj->GetMergedItemSet(); + + GradTransGradient aOldGradTransGradient; + GradTransGradient aGradTransGradient; + GradTransVector aGradTransVector; + + aGradTransVector.maPositionA = basegfx::B2DPoint(GetPos().X(), GetPos().Y()); + aGradTransVector.maPositionB = basegfx::B2DPoint(Get2ndPos().X(), Get2ndPos().Y()); + if(m_pColHdl1) + aGradTransVector.aCol1 = m_pColHdl1->GetColor(); + if(m_pColHdl2) + aGradTransVector.aCol2 = m_pColHdl2->GetColor(); + + if(IsGradient()) + aOldGradTransGradient.aGradient = rSet.Get(XATTR_FILLGRADIENT).GetGradientValue(); + else + aOldGradTransGradient.aGradient = rSet.Get(XATTR_FILLFLOATTRANSPARENCE).GetGradientValue(); + + // transform vector data to gradient + GradTransformer::VecToGrad(aGradTransVector, aGradTransGradient, aOldGradTransGradient, _pObj, m_bMoveSingleHandle, m_bMoveFirstHandle); + + if(bSetItemOnObject) + { + SdrModel& rModel(_pObj->getSdrModelFromSdrObject()); + SfxItemSet aNewSet(rModel.GetItemPool()); + const OUString aString; + + if(IsGradient()) + { + XFillGradientItem aNewGradItem(aString, aGradTransGradient.aGradient); + aNewSet.Put(aNewGradItem); + } + else + { + XFillFloatTransparenceItem aNewTransItem(aString, aGradTransGradient.aGradient); + aNewSet.Put(aNewTransItem); + } + + if(bUndo && rModel.IsUndoEnabled()) + { + rModel.BegUndo(SvxResId(IsGradient() ? SIP_XA_FILLGRADIENT : SIP_XA_FILLTRANSPARENCE)); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoAttrObject(*_pObj)); + rModel.EndUndo(); + } + + m_pObj->SetMergedItemSetAndBroadcast(aNewSet); + } + + // back transformation, set values on pIAOHandle + GradTransformer::GradToVec(aGradTransGradient, aGradTransVector, _pObj); + + SetPos(Point(FRound(aGradTransVector.maPositionA.getX()), FRound(aGradTransVector.maPositionA.getY()))); + Set2ndPos(Point(FRound(aGradTransVector.maPositionB.getX()), FRound(aGradTransVector.maPositionB.getY()))); + if(m_pColHdl1) + { + m_pColHdl1->SetPos(Point(FRound(aGradTransVector.maPositionA.getX()), FRound(aGradTransVector.maPositionA.getY()))); + m_pColHdl1->SetColor(aGradTransVector.aCol1); + } + if(m_pColHdl2) + { + m_pColHdl2->SetPos(Point(FRound(aGradTransVector.maPositionB.getX()), FRound(aGradTransVector.maPositionB.getY()))); + m_pColHdl2->SetColor(aGradTransVector.aCol2); + } +} + + +SdrHdlLine::~SdrHdlLine() {} + +void SdrHdlLine::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList) + return; + + SdrMarkView* pView = m_pHdlList->GetView(); + + if(!(pView && !pView->areMarkHandlesHidden() && m_pHdl1 && m_pHdl2)) + 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()) + { + basegfx::B2DPoint aPosition1(m_pHdl1->GetPos().X(), m_pHdl1->GetPos().Y()); + basegfx::B2DPoint aPosition2(m_pHdl2->GetPos().X(), m_pHdl2->GetPos().Y()); + + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(new + sdr::overlay::OverlayLineStriped( + aPosition1, + aPosition2 + )); + + // color(?) + pNewOverlayObject->setBaseColor(COL_LIGHTRED); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +PointerStyle SdrHdlLine::GetPointer() const +{ + return PointerStyle::RefHand; +} + + +SdrHdlBezWgt::~SdrHdlBezWgt() {} + +void SdrHdlBezWgt::CreateB2dIAObject() +{ + // call parent + SdrHdl::CreateB2dIAObject(); + + // create lines + if(!m_pHdlList) + return; + + 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()) + { + basegfx::B2DPoint aPosition1(m_pHdl1->GetPos().X(), m_pHdl1->GetPos().Y()); + basegfx::B2DPoint aPosition2(m_aPos.X(), m_aPos.Y()); + + if(!aPosition1.equal(aPosition2)) + { + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(new + sdr::overlay::OverlayLineStriped( + aPosition1, + aPosition2 + )); + + // line part is not hittable + pNewOverlayObject->setHittable(false); + + // color(?) + pNewOverlayObject->setBaseColor(COL_LIGHTBLUE); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } + } +} + + +E3dVolumeMarker::E3dVolumeMarker(const basegfx::B2DPolyPolygon& rWireframePoly) +{ + m_aWireframePoly = rWireframePoly; +} + +void E3dVolumeMarker::CreateB2dIAObject() +{ + // create lines + if(!m_pHdlList) + return; + + 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() && m_aWireframePoly.count()) + { + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(new + sdr::overlay::OverlayPolyPolygonStripedAndFilled( + m_aWireframePoly)); + + pNewOverlayObject->setBaseColor(COL_BLACK); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + + +ImpEdgeHdl::~ImpEdgeHdl() +{ +} + +void ImpEdgeHdl::CreateB2dIAObject() +{ + if(m_nObjHdlNum <= 1 && m_pObj) + { + // first throw away old one + GetRidOfIAObject(); + + BitmapColorIndex eColIndex = BitmapColorIndex::LightCyan; + BitmapMarkerKind eKindOfMarker = BitmapMarkerKind::Rect_7x7; + + if(m_pHdlList) + { + SdrMarkView* pView = m_pHdlList->GetView(); + + if(pView && !pView->areMarkHandlesHidden()) + { + const SdrEdgeObj* pEdge = static_cast<SdrEdgeObj*>(m_pObj); + + if(pEdge->GetConnectedNode(m_nObjHdlNum == 0) != nullptr) + eColIndex = BitmapColorIndex::LightRed; + + if(m_nPPntNum < 2) + { + // Handle with plus sign inside + eKindOfMarker = BitmapMarkerKind::Circ_7x7; + } + + SdrPageView* pPageView = pView->GetSdrPageView(); + + if(pPageView) + { + 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()) + { + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(CreateOverlayObject( + aPosition, + eColIndex, + eKindOfMarker)); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } + } + } + } + } + else + { + // call parent + SdrHdl::CreateB2dIAObject(); + } +} + +void ImpEdgeHdl::SetLineCode(SdrEdgeLineCode eCode) +{ + if(m_eLineCode != eCode) + { + // remember new value + m_eLineCode = eCode; + + // create new display + Touch(); + } +} + +PointerStyle ImpEdgeHdl::GetPointer() const +{ + SdrEdgeObj* pEdge=dynamic_cast<SdrEdgeObj*>( m_pObj ); + if (pEdge==nullptr) + return SdrHdl::GetPointer(); + if (m_nObjHdlNum<=1) + return PointerStyle::MovePoint; + if (IsHorzDrag()) + return PointerStyle::ESize; + else + return PointerStyle::SSize; +} + +bool ImpEdgeHdl::IsHorzDrag() const +{ + SdrEdgeObj* pEdge=dynamic_cast<SdrEdgeObj*>( m_pObj ); + if (pEdge==nullptr) + return false; + if (m_nObjHdlNum<=1) + return false; + + SdrEdgeKind eEdgeKind = pEdge->GetObjectItem(SDRATTR_EDGEKIND).GetValue(); + + const SdrEdgeInfoRec& rInfo=pEdge->m_aEdgeInfo; + if (eEdgeKind==SdrEdgeKind::OrthoLines || eEdgeKind==SdrEdgeKind::Bezier) + { + return !rInfo.ImpIsHorzLine(m_eLineCode,*pEdge->m_pEdgeTrack); + } + else if (eEdgeKind==SdrEdgeKind::ThreeLines) + { + tools::Long nAngle=m_nObjHdlNum==2 ? rInfo.m_nAngle1 : rInfo.m_nAngle2; + return nAngle==0 || nAngle==18000; + } + return false; +} + + +ImpMeasureHdl::~ImpMeasureHdl() +{ +} + +void ImpMeasureHdl::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList) + return; + + SdrMarkView* pView = m_pHdlList->GetView(); + + if(!pView || pView->areMarkHandlesHidden()) + return; + + BitmapColorIndex eColIndex = BitmapColorIndex::LightCyan; + BitmapMarkerKind eKindOfMarker = BitmapMarkerKind::Rect_9x9; + + if(m_nObjHdlNum > 1) + { + eKindOfMarker = BitmapMarkerKind::Rect_7x7; + } + + if(m_bSelect) + { + eColIndex = BitmapColorIndex::Cyan; + } + + 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()) + { + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(CreateOverlayObject( + aPosition, + eColIndex, + eKindOfMarker)); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +PointerStyle ImpMeasureHdl::GetPointer() const +{ + switch (m_nObjHdlNum) + { + case 0: case 1: return PointerStyle::Hand; + case 2: case 3: return PointerStyle::MovePoint; + case 4: case 5: return SdrHdl::GetPointer(); // will then be rotated appropriately + } // switch + return PointerStyle::NotAllowed; +} + + +ImpTextframeHdl::ImpTextframeHdl(const tools::Rectangle& rRect) : + SdrHdl(rRect.TopLeft(),SdrHdlKind::Move), + maRect(rRect) +{ +} + +void ImpTextframeHdl::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if(!m_pHdlList) + return; + + 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()) + { + const basegfx::B2DPoint aTopLeft(maRect.Left(), maRect.Top()); + const basegfx::B2DPoint aBottomRight(maRect.Right(), maRect.Bottom()); + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + + std::unique_ptr<sdr::overlay::OverlayRectangle> pNewOverlayObject(new sdr::overlay::OverlayRectangle( + aTopLeft, + aBottomRight, + aHilightColor, + fTransparence, + 3.0, + 3.0, + -toRadians(m_nRotationAngle), + true)); // allow animation; the Handle is not shown at text edit time + + pNewOverlayObject->setHittable(false); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNewOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + + +static bool ImpSdrHdlListSorter(std::unique_ptr<SdrHdl> const& lhs, std::unique_ptr<SdrHdl> const& rhs) +{ + SdrHdlKind eKind1=lhs->GetKind(); + SdrHdlKind eKind2=rhs->GetKind(); + // Level 1: first normal handles, then Glue, then User, then Plus handles, then reference point handles + unsigned n1=1; + unsigned n2=1; + if (eKind1!=eKind2) + { + if (eKind1==SdrHdlKind::Ref1 || eKind1==SdrHdlKind::Ref2 || eKind1==SdrHdlKind::MirrorAxis) n1=5; + else if (eKind1==SdrHdlKind::Glue) n1=2; + else if (eKind1==SdrHdlKind::User) n1=3; + else if (eKind1==SdrHdlKind::SmartTag) n1=0; + if (eKind2==SdrHdlKind::Ref1 || eKind2==SdrHdlKind::Ref2 || eKind2==SdrHdlKind::MirrorAxis) n2=5; + else if (eKind2==SdrHdlKind::Glue) n2=2; + else if (eKind2==SdrHdlKind::User) n2=3; + else if (eKind2==SdrHdlKind::SmartTag) n2=0; + } + if (lhs->IsPlusHdl()) n1=4; + if (rhs->IsPlusHdl()) n2=4; + if (n1==n2) + { + // Level 2: PageView (Pointer) + SdrPageView* pPV1=lhs->GetPageView(); + SdrPageView* pPV2=rhs->GetPageView(); + if (pPV1==pPV2) + { + // Level 3: Position (x+y) + SdrObject* pObj1=lhs->GetObj(); + SdrObject* pObj2=rhs->GetObj(); + if (pObj1==pObj2) + { + sal_uInt32 nNum1=lhs->GetObjHdlNum(); + sal_uInt32 nNum2=rhs->GetObjHdlNum(); + if (nNum1==nNum2) + { + if (eKind1==eKind2) + return lhs<rhs; // Hack, to always get to the same sorting + return static_cast<sal_uInt16>(eKind1)<static_cast<sal_uInt16>(eKind2); + } + else + return nNum1<nNum2; + } + else + { + return pObj1<pObj2; + } + } + else + { + return pPV1<pPV2; + } + } + else + { + return n1<n2; + } +} + +namespace { + +// Helper struct for re-sorting handles +struct ImplHdlAndIndex +{ + SdrHdl* mpHdl; + sal_uInt32 mnIndex; +}; + +} + +extern "C" { + +// Helper method for sorting handles taking care of OrdNums, keeping order in +// single objects and re-sorting polygon handles intuitively +static int ImplSortHdlFunc( const void* pVoid1, const void* pVoid2 ) +{ + const ImplHdlAndIndex* p1 = static_cast<ImplHdlAndIndex const *>(pVoid1); + const ImplHdlAndIndex* p2 = static_cast<ImplHdlAndIndex const *>(pVoid2); + + if(p1->mpHdl->GetObj() == p2->mpHdl->GetObj()) + { + if(p1->mpHdl->GetObj() && dynamic_cast<const SdrPathObj*>(p1->mpHdl->GetObj()) != nullptr) + { + // same object and a path object + if((p1->mpHdl->GetKind() == SdrHdlKind::Poly || p1->mpHdl->GetKind() == SdrHdlKind::BezierWeight) + && (p2->mpHdl->GetKind() == SdrHdlKind::Poly || p2->mpHdl->GetKind() == SdrHdlKind::BezierWeight)) + { + // both handles are point or control handles + if(p1->mpHdl->GetPolyNum() == p2->mpHdl->GetPolyNum()) + { + if(p1->mpHdl->GetPointNum() < p2->mpHdl->GetPointNum()) + { + return -1; + } + else + { + return 1; + } + } + else if(p1->mpHdl->GetPolyNum() < p2->mpHdl->GetPolyNum()) + { + return -1; + } + else + { + return 1; + } + } + } + } + else + { + if(!p1->mpHdl->GetObj()) + { + return -1; + } + else if(!p2->mpHdl->GetObj()) + { + return 1; + } + else + { + // different objects, use OrdNum for sort + const sal_uInt32 nOrdNum1 = p1->mpHdl->GetObj()->GetOrdNum(); + const sal_uInt32 nOrdNum2 = p2->mpHdl->GetObj()->GetOrdNum(); + + if(nOrdNum1 < nOrdNum2) + { + return -1; + } + else + { + return 1; + } + } + } + + // fallback to indices + if(p1->mnIndex < p2->mnIndex) + { + return -1; + } + else + { + return 1; + } +} + +} + +void SdrHdlList::TravelFocusHdl(bool bForward) +{ + // security correction + if (mnFocusIndex >= GetHdlCount()) + mnFocusIndex = SAL_MAX_SIZE; + + if(maList.empty()) + return; + + // take care of old handle + const size_t nOldHdlNum(mnFocusIndex); + SdrHdl* pOld = nullptr; + if (nOldHdlNum < GetHdlCount()) + pOld = GetHdl(nOldHdlNum); + + if(pOld) + { + // switch off old handle + mnFocusIndex = SAL_MAX_SIZE; + pOld->Touch(); + } + + // allocate pointer array for sorted handle list + std::unique_ptr<ImplHdlAndIndex[]> pHdlAndIndex(new ImplHdlAndIndex[maList.size()]); + + // build sorted handle list + for( size_t a = 0; a < maList.size(); ++a) + { + pHdlAndIndex[a].mpHdl = maList[a].get(); + pHdlAndIndex[a].mnIndex = a; + } + + qsort(pHdlAndIndex.get(), maList.size(), sizeof(ImplHdlAndIndex), ImplSortHdlFunc); + + // look for old num in sorted array + size_t nOldHdl(nOldHdlNum); + + if(nOldHdlNum != SAL_MAX_SIZE) + { + for(size_t a = 0; a < maList.size(); ++a) + { + if(pHdlAndIndex[a].mpHdl == pOld) + { + nOldHdl = a; + break; + } + } + } + + // build new HdlNum + size_t nNewHdl(nOldHdl); + + // do the focus travel + if(bForward) + { + if(nOldHdl != SAL_MAX_SIZE) + { + if(nOldHdl == maList.size() - 1) + { + // end forward run + nNewHdl = SAL_MAX_SIZE; + } + else + { + // simply the next handle + nNewHdl++; + } + } + else + { + // start forward run at first entry + nNewHdl = 0; + } + } + else + { + if(nOldHdl == SAL_MAX_SIZE) + { + // start backward run at last entry + nNewHdl = maList.size() - 1; + + } + else + { + if(nOldHdl == 0) + { + // end backward run + nNewHdl = SAL_MAX_SIZE; + } + else + { + // simply the previous handle + nNewHdl--; + } + } + } + + // build new HdlNum + sal_uIntPtr nNewHdlNum(nNewHdl); + + // look for old num in sorted array + if(nNewHdl != SAL_MAX_SIZE) + { + SdrHdl* pNew = pHdlAndIndex[nNewHdl].mpHdl; + + for(size_t a = 0; a < maList.size(); ++a) + { + if(maList[a].get() == pNew) + { + nNewHdlNum = a; + break; + } + } + } + + // take care of next handle + if(nOldHdlNum != nNewHdlNum) + { + mnFocusIndex = nNewHdlNum; + if (mnFocusIndex < GetHdlCount()) + { + SdrHdl* pNew = GetHdl(mnFocusIndex); + pNew->Touch(); + } + } +} + +SdrHdl* SdrHdlList::GetFocusHdl() const +{ + if(mnFocusIndex < GetHdlCount()) + return GetHdl(mnFocusIndex); + else + return nullptr; +} + +void SdrHdlList::SetFocusHdl(SdrHdl* pNew) +{ + if(!pNew) + return; + + SdrHdl* pActual = GetFocusHdl(); + + if(pActual && pActual == pNew) + return; + + const size_t nNewHdlNum = GetHdlNum(pNew); + + if(nNewHdlNum != SAL_MAX_SIZE) + { + mnFocusIndex = nNewHdlNum; + + if(pActual) + { + pActual->Touch(); + } + + pNew->Touch(); + } +} + +void SdrHdlList::ResetFocusHdl() +{ + SdrHdl* pHdl = GetFocusHdl(); + + mnFocusIndex = SAL_MAX_SIZE; + + if(pHdl) + { + pHdl->Touch(); + } +} + + +SdrHdlList::SdrHdlList(SdrMarkView* pV) +: mnFocusIndex(SAL_MAX_SIZE), + m_pView(pV) +{ + m_nHdlSize = 3; + m_bRotateShear = false; + m_bMoveOutside = false; + m_bDistortShear = false; +} + +SdrHdlList::~SdrHdlList() +{ + Clear(); +} + +void SdrHdlList::SetHdlSize(sal_uInt16 nSiz) +{ + if(m_nHdlSize != nSiz) + { + // remember new value + m_nHdlSize = nSiz; + + // propagate change to IAOs + for(size_t i=0; i<GetHdlCount(); ++i) + { + SdrHdl* pHdl = GetHdl(i); + pHdl->Touch(); + } + } +} + +void SdrHdlList::SetMoveOutside(bool bOn) +{ + if(m_bMoveOutside != bOn) + { + // remember new value + m_bMoveOutside = bOn; + + // propagate change to IAOs + for(size_t i=0; i<GetHdlCount(); ++i) + { + SdrHdl* pHdl = GetHdl(i); + pHdl->Touch(); + } + } +} + +void SdrHdlList::SetRotateShear(bool bOn) +{ + m_bRotateShear = bOn; +} + +void SdrHdlList::SetDistortShear(bool bOn) +{ + m_bDistortShear = bOn; +} + +std::unique_ptr<SdrHdl> SdrHdlList::RemoveHdl(size_t nNum) +{ + std::unique_ptr<SdrHdl> pRetval = std::move(maList[nNum]); + maList.erase(maList.begin() + nNum); + + return pRetval; +} + +void SdrHdlList::RemoveAllByKind(SdrHdlKind eKind) +{ + std::erase_if(maList, [&eKind](std::unique_ptr<SdrHdl>& rItem) { return rItem->GetKind() == eKind; }); +} + +void SdrHdlList::Clear() +{ + maList.clear(); + + m_bRotateShear=false; + m_bDistortShear=false; +} + +void SdrHdlList::Sort() +{ + // remember currently focused handle + SdrHdl* pPrev = GetFocusHdl(); + + std::sort( maList.begin(), maList.end(), ImpSdrHdlListSorter ); + + // get now and compare + SdrHdl* pNow = GetFocusHdl(); + + if(pPrev == pNow) + return; + + if(pPrev) + { + pPrev->Touch(); + } + + if(pNow) + { + pNow->Touch(); + } +} + +size_t SdrHdlList::GetHdlNum(const SdrHdl* pHdl) const +{ + if (pHdl==nullptr) + return SAL_MAX_SIZE; + auto it = std::find_if( maList.begin(), maList.end(), + [&](const std::unique_ptr<SdrHdl> & p) { return p.get() == pHdl; }); + assert(it != maList.end()); + if( it == maList.end() ) + return SAL_MAX_SIZE; + return it - maList.begin(); +} + +void SdrHdlList::AddHdl(std::unique_ptr<SdrHdl> pHdl) +{ + assert(pHdl); + pHdl->SetHdlList(this); + maList.push_back(std::move(pHdl)); +} + +SdrHdl* SdrHdlList::IsHdlListHit(const Point& rPnt) const +{ + SdrHdl* pRet=nullptr; + const size_t nCount=GetHdlCount(); + size_t nNum=nCount; + while (nNum>0 && pRet==nullptr) + { + nNum--; + SdrHdl* pHdl=GetHdl(nNum); + if (pHdl->IsHdlHit(rPnt)) + pRet=pHdl; + } + return pRet; +} + +SdrHdl* SdrHdlList::GetHdl(SdrHdlKind eKind1) const +{ + SdrHdl* pRet=nullptr; + for (size_t i=0; i<GetHdlCount() && pRet==nullptr; ++i) + { + SdrHdl* pHdl=GetHdl(i); + if (pHdl->GetKind()==eKind1) + pRet=pHdl; + } + return pRet; +} + +void SdrHdlList::MoveTo(SdrHdlList& rOther) +{ + for (auto & pHdl : maList) + pHdl->SetHdlList(&rOther); + rOther.maList.insert(rOther.maList.end(), + std::make_move_iterator(maList.begin()), std::make_move_iterator(maList.end())); + maList.clear(); +} + +SdrCropHdl::SdrCropHdl( + const Point& rPnt, + SdrHdlKind eNewKind, + double fShearX, + double fRotation) +: SdrHdl(rPnt, eNewKind), + mfShearX(fShearX), + mfRotation(fRotation) +{ +} + + +BitmapEx SdrCropHdl::GetBitmapForHandle( const BitmapEx& rBitmap, int nSize ) +{ + int nPixelSize = 0, nX = 0, nY = 0, nOffset = 0; + + if( nSize <= 3 ) + { + nPixelSize = 13; + nOffset = 0; + } + else if( nSize <=4 ) + { + nPixelSize = 17; + nOffset = 39; + } + else + { + nPixelSize = 21; + nOffset = 90; + } + + switch( m_eKind ) + { + case SdrHdlKind::UpperLeft: nX = 0; nY = 0; break; + case SdrHdlKind::Upper: nX = 1; nY = 0; break; + case SdrHdlKind::UpperRight: nX = 2; nY = 0; break; + case SdrHdlKind::Left: nX = 0; nY = 1; break; + case SdrHdlKind::Right: nX = 2; nY = 1; break; + case SdrHdlKind::LowerLeft: nX = 0; nY = 2; break; + case SdrHdlKind::Lower: nX = 1; nY = 2; break; + case SdrHdlKind::LowerRight: nX = 2; nY = 2; break; + default: break; + } + + tools::Rectangle aSourceRect( Point( nX * nPixelSize + nOffset, nY * nPixelSize), Size(nPixelSize, nPixelSize) ); + + BitmapEx aRetval(rBitmap); + aRetval.Crop(aSourceRect); + return aRetval; +} + + +void SdrCropHdl::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + SdrMarkView* pView = m_pHdlList ? m_pHdlList->GetView() : nullptr; + SdrPageView* pPageView = pView ? pView->GetSdrPageView() : nullptr; + + if( !pPageView || pView->areMarkHandlesHidden() ) + return; + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + int nHdlSize = m_pHdlList->GetHdlSize(); + + const BitmapEx aHandlesBitmap(SIP_SA_CROP_MARKERS); + BitmapEx aBmpEx1( GetBitmapForHandle( aHandlesBitmap, nHdlSize ) ); + + 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()) + { + basegfx::B2DPoint aPosition(m_aPos.X(), m_aPos.Y()); + + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject; + + // animate focused handles + if(IsFocusHdl() && (m_pHdlList->GetFocusHdl() == this)) + { + if( nHdlSize >= 2 ) + nHdlSize = 1; + + BitmapEx aBmpEx2( GetBitmapForHandle( aHandlesBitmap, nHdlSize + 1 ) ); + + const sal_uInt64 nBlinkTime = rStyleSettings.GetCursorBlinkTime(); + + pOverlayObject.reset(new sdr::overlay::OverlayAnimatedBitmapEx( + aPosition, + aBmpEx1, + aBmpEx2, + nBlinkTime, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Height() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx2.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx2.GetSizePixel().Height() - 1) >> 1, + mfShearX, + mfRotation)); + } + else + { + // create centered handle as default + pOverlayObject.reset(new sdr::overlay::OverlayBitmapEx( + aPosition, + aBmpEx1, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Width() - 1) >> 1, + static_cast<sal_uInt16>(aBmpEx1.GetSizePixel().Height() - 1) >> 1, + 0.0, + mfShearX, + mfRotation)); + } + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + + +// with the correction of crop handling I could get rid of the extra mirroring flag, adapted stuff +// accordingly + +SdrCropViewHdl::SdrCropViewHdl( + basegfx::B2DHomMatrix aObjectTransform, + Graphic aGraphic, + double fCropLeft, + double fCropTop, + double fCropRight, + double fCropBottom) +: SdrHdl(Point(), SdrHdlKind::User), + maObjectTransform(std::move(aObjectTransform)), + maGraphic(std::move(aGraphic)), + mfCropLeft(fCropLeft), + mfCropTop(fCropTop), + mfCropRight(fCropRight), + mfCropBottom(fCropBottom) +{ +} + +namespace { + +void translateRotationToMirroring(basegfx::B2DVector & scale, double * rotate) { + assert(rotate != nullptr); + + // detect 180 degree rotation, this is the same as mirrored in X and Y, + // thus change to mirroring. Prefer mirroring here. Use the equal call + // with getSmallValue here, the original which uses rtl::math::approxEqual + // is too correct here. Maybe this changes with enhanced precision in aw080 + // to the better so that this can be reduced to the more precise call again + if(basegfx::fTools::equal(fabs(*rotate), M_PI, 0.000000001)) + { + scale.setX(scale.getX() * -1.0); + scale.setY(scale.getY() * -1.0); + *rotate = 0.0; + } +} + +} + +void SdrCropViewHdl::CreateB2dIAObject() +{ + GetRidOfIAObject(); + SdrMarkView* pView = m_pHdlList ? m_pHdlList->GetView() : nullptr; + SdrPageView* pPageView = pView ? pView->GetSdrPageView() : nullptr; + + if(!pPageView || pView->areMarkHandlesHidden()) + { + return; + } + + // decompose to have current translate and scale + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX); + + if(aScale.equalZero()) + { + return; + } + + translateRotationToMirroring(aScale, &fRotate); + + // remember mirroring, reset at Scale and adapt crop values for usage; + // mirroring can stay in the object transformation, so do not have to + // cope with it here (except later for the CroppedImage transformation, + // see below) + const bool bMirroredX(aScale.getX() < 0.0); + const bool bMirroredY(aScale.getY() < 0.0); + double fCropLeft(mfCropLeft); + double fCropTop(mfCropTop); + double fCropRight(mfCropRight); + double fCropBottom(mfCropBottom); + + if(bMirroredX) + { + aScale.setX(-aScale.getX()); + } + + if(bMirroredY) + { + aScale.setY(-aScale.getY()); + } + + // create target translate and scale + const basegfx::B2DVector aTargetScale( + aScale.getX() + fCropRight + fCropLeft, + aScale.getY() + fCropBottom + fCropTop); + const basegfx::B2DVector aTargetTranslate( + aTranslate.getX() - fCropLeft, + aTranslate.getY() - fCropTop); + + // create ranges to make comparisons + const basegfx::B2DRange aCurrentForCompare( + aTranslate.getX(), aTranslate.getY(), + aTranslate.getX() + aScale.getX(), aTranslate.getY() + aScale.getY()); + basegfx::B2DRange aCropped( + aTargetTranslate.getX(), aTargetTranslate.getY(), + aTargetTranslate.getX() + aTargetScale.getX(), aTargetTranslate.getY() + aTargetScale.getY()); + + if(aCropped.isEmpty()) + { + // nothing to return since cropped content is completely empty + return; + } + + if(aCurrentForCompare.equal(aCropped)) + { + // no crop at all + return; + } + + // back-transform to have values in unit coordinates + basegfx::B2DHomMatrix aBackToUnit; + aBackToUnit.translate(-aTranslate.getX(), -aTranslate.getY()); + aBackToUnit.scale( + basegfx::fTools::equalZero(aScale.getX()) ? 1.0 : 1.0 / aScale.getX(), + basegfx::fTools::equalZero(aScale.getY()) ? 1.0 : 1.0 / aScale.getY()); + + // transform cropped back to unit coordinates + aCropped.transform(aBackToUnit); + + // prepare crop PolyPolygon + basegfx::B2DPolygon aGraphicOutlinePolygon( + basegfx::utils::createPolygonFromRect( + aCropped)); + basegfx::B2DPolyPolygon aCropPolyPolygon(aGraphicOutlinePolygon); + + // current range is unit range + basegfx::B2DRange aOverlap(0.0, 0.0, 1.0, 1.0); + + aOverlap.intersect(aCropped); + + if(!aOverlap.isEmpty()) + { + aCropPolyPolygon.append( + basegfx::utils::createPolygonFromRect( + aOverlap)); + } + + // transform to object coordinates to prepare for clip + aCropPolyPolygon.transform(maObjectTransform); + aGraphicOutlinePolygon.transform(maObjectTransform); + + // create cropped transformation + basegfx::B2DHomMatrix aCroppedTransform; + + aCroppedTransform.scale( + aCropped.getWidth(), + aCropped.getHeight()); + aCroppedTransform.translate( + aCropped.getMinX(), + aCropped.getMinY()); + aCroppedTransform = maObjectTransform * aCroppedTransform; + + // prepare graphic primitive (transformed) + const drawinglayer::primitive2d::Primitive2DReference aGraphic( + new drawinglayer::primitive2d::GraphicPrimitive2D( + aCroppedTransform, + maGraphic)); + + // prepare outline polygon for whole graphic + const basegfx::BColor aHilightColor(SvtOptionsDrawinglayer::getHilightColor().getBColor()); + const drawinglayer::primitive2d::Primitive2DReference aGraphicOutline( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + std::move(aGraphicOutlinePolygon), + aHilightColor)); + + // combine these + drawinglayer::primitive2d::Primitive2DContainer aCombination(2); + aCombination[0] = aGraphic; + aCombination[1] = aGraphicOutline; + + // embed to MaskPrimitive2D + const drawinglayer::primitive2d::Primitive2DReference aMaskedGraphic( + new drawinglayer::primitive2d::MaskPrimitive2D( + std::move(aCropPolyPolygon), + std::move(aCombination))); + + // embed to UnifiedTransparencePrimitive2D + const drawinglayer::primitive2d::Primitive2DReference aTransparenceMaskedGraphic( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer { aMaskedGraphic }, + 0.8)); + + const drawinglayer::primitive2d::Primitive2DContainer aSequence { aTransparenceMaskedGraphic }; + + for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++) + { + // const SdrPageViewWinRec& rPageViewWinRec = rPageViewWinList[b]; + const SdrPageWindow& rPageWindow = *(pPageView->GetPageWindow(b)); + + if(rPageWindow.GetPaintWindow().OutputToWindow()) + { + const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager(); + if(xManager.is()) + { + std::unique_ptr<sdr::overlay::OverlayObject> pNew(new sdr::overlay::OverlayPrimitive2DSequenceObject(drawinglayer::primitive2d::Primitive2DContainer(aSequence))); + + // only informative object, no hit + pNew->setHittable(false); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pNew), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdhlpln.cxx b/svx/source/svdraw/svdhlpln.cxx new file mode 100644 index 0000000000..0d515191ed --- /dev/null +++ b/svx/source/svdraw/svdhlpln.cxx @@ -0,0 +1,109 @@ +/* -*- 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/svdhlpln.hxx> + +#include <vcl/outdev.hxx> +#include <vcl/ptrstyle.hxx> + + +PointerStyle SdrHelpLine::GetPointer() const +{ + switch (eKind) { + case SdrHelpLineKind::Vertical : return PointerStyle::ESize; + case SdrHelpLineKind::Horizontal: return PointerStyle::SSize; + default : return PointerStyle::Move; + } // switch +} + +bool SdrHelpLine::IsHit(const Point& rPnt, sal_uInt16 nTolLog, const OutputDevice& rOut) const +{ + Size a1Pix(rOut.PixelToLogic(Size(1,1))); + bool bXHit=rPnt.X()>=aPos.X()-nTolLog && rPnt.X()<=aPos.X()+nTolLog+a1Pix.Width(); + bool bYHit=rPnt.Y()>=aPos.Y()-nTolLog && rPnt.Y()<=aPos.Y()+nTolLog+a1Pix.Height(); + switch (eKind) { + case SdrHelpLineKind::Vertical : return bXHit; + case SdrHelpLineKind::Horizontal: return bYHit; + case SdrHelpLineKind::Point: { + if (bXHit || bYHit) { + Size aRad(rOut.PixelToLogic(Size(SDRHELPLINE_POINT_PIXELSIZE,SDRHELPLINE_POINT_PIXELSIZE))); + return rPnt.X()>=aPos.X()-aRad.Width() && rPnt.X()<=aPos.X()+aRad.Width()+a1Pix.Width() && + rPnt.Y()>=aPos.Y()-aRad.Height() && rPnt.Y()<=aPos.Y()+aRad.Height()+a1Pix.Height(); + } + } break; + } // switch + return false; +} + +tools::Rectangle SdrHelpLine::GetBoundRect(const OutputDevice& rOut) const +{ + tools::Rectangle aRet(aPos,aPos); + Point aOfs(rOut.GetMapMode().GetOrigin()); + Size aSiz(rOut.GetOutputSize()); + switch (eKind) { + case SdrHelpLineKind::Vertical : aRet.SetTop(-aOfs.Y() ); aRet.SetBottom(-aOfs.Y()+aSiz.Height() ); break; + case SdrHelpLineKind::Horizontal: aRet.SetLeft(-aOfs.X() ); aRet.SetRight(-aOfs.X()+aSiz.Width() ); break; + case SdrHelpLineKind::Point : { + Size aRad(rOut.PixelToLogic(Size(SDRHELPLINE_POINT_PIXELSIZE,SDRHELPLINE_POINT_PIXELSIZE))); + aRet.AdjustLeft( -(aRad.Width()) ); + aRet.AdjustRight(aRad.Width() ); + aRet.AdjustTop( -(aRad.Height()) ); + aRet.AdjustBottom(aRad.Height() ); + } break; + } // switch + return aRet; +} + +SdrHelpLineList& SdrHelpLineList::operator=(const SdrHelpLineList& rSrcList) +{ + aList.clear(); + sal_uInt16 nCount=rSrcList.GetCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + Insert(rSrcList[i]); + } + return *this; +} + +bool SdrHelpLineList::operator==(const SdrHelpLineList& rSrcList) const +{ + bool bEqual = false; + sal_uInt16 nCount=GetCount(); + if (nCount==rSrcList.GetCount()) { + bEqual = true; + for (sal_uInt16 i=0; i<nCount && bEqual; i++) { + if (*aList[i]!=*rSrcList.aList[i]) { + bEqual = false; + } + } + } + return bEqual; +} + +sal_uInt16 SdrHelpLineList::HitTest(const Point& rPnt, sal_uInt16 nTolLog, const OutputDevice& rOut) const +{ + sal_uInt16 nCount=GetCount(); + for (sal_uInt16 i=nCount; i>0;) { + i--; + if (aList[i]->IsHit(rPnt,nTolLog,rOut)) return i; + } + return SDRHELPLINE_NOTFOUND; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svditer.cxx b/svx/source/svdraw/svditer.cxx new file mode 100644 index 0000000000..eaca2b335d --- /dev/null +++ b/svx/source/svdraw/svditer.cxx @@ -0,0 +1,140 @@ +/* -*- 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/svditer.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdmark.hxx> +#include <osl/diagnose.h> + +SdrObjListIter::SdrObjListIter(const SdrObjList* pObjList, SdrIterMode eMode, bool bReverse) +: mnIndex(0), + mbReverse(bReverse), + mbUseZOrder(true) +{ + if(nullptr != pObjList) + { + ImpProcessObjectList(*pObjList, eMode); + } + + Reset(); +} + +SdrObjListIter::SdrObjListIter(const SdrObjList* pObjList, bool bUseZOrder, SdrIterMode eMode, bool bReverse) +: mnIndex(0), + mbReverse(bReverse), + mbUseZOrder(bUseZOrder) +{ + if(nullptr != pObjList) + { + // correct when we have no ObjectNavigationOrder + if(!mbUseZOrder && !pObjList->HasObjectNavigationOrder()) + { + mbUseZOrder = false; + } + + ImpProcessObjectList(*pObjList, eMode); + } + + Reset(); +} + +SdrObjListIter::SdrObjListIter(const SdrObject& rSdrObject, SdrIterMode eMode, bool bReverse) +: mnIndex(0), + mbReverse(bReverse), + mbUseZOrder(true) +{ + ImpProcessObj(rSdrObject, eMode); + Reset(); +} + +SdrObjListIter::SdrObjListIter(const SdrPage* pSdrPage, SdrIterMode eMode, bool bReverse) +: mnIndex(0), + mbReverse(bReverse), + mbUseZOrder(true) +{ + if (pSdrPage) + ImpProcessObjectList(*pSdrPage, eMode); + Reset(); +} + +SdrObjListIter::SdrObjListIter( const SdrMarkList& rMarkList, SdrIterMode eMode ) +: mnIndex(0), + mbReverse(false), + mbUseZOrder(true) +{ + ImpProcessMarkList(rMarkList, eMode); + Reset(); +} + +void SdrObjListIter::ImpProcessObjectList(const SdrObjList& rObjList, SdrIterMode eMode) +{ + for(size_t nIdx(0), nCount(rObjList.GetObjCount()); nIdx < nCount; ++nIdx) + { + const SdrObject* pSdrObject(mbUseZOrder + ? rObjList.GetObj(nIdx) + : rObjList.GetObjectForNavigationPosition(nIdx)); + + if(nullptr == pSdrObject) + { + OSL_ENSURE(false, "SdrObjListIter: corrupted SdrObjList (!)"); + } + else + { + ImpProcessObj(*pSdrObject, eMode); + } + } +} + +void SdrObjListIter::ImpProcessMarkList(const SdrMarkList& rMarkList, SdrIterMode eMode) +{ + for( size_t nIdx = 0, nCount = rMarkList.GetMarkCount(); nIdx < nCount; ++nIdx ) + { + if( SdrObject* pObj = rMarkList.GetMark( nIdx )->GetMarkedSdrObj() ) + { + ImpProcessObj(*pObj, eMode); + } + } +} + +void SdrObjListIter::ImpProcessObj(const SdrObject& rSdrObject, SdrIterMode eMode) +{ + // TTTT: Note: The behaviour has changed here, it will now deep-iterate + // for SdrObjGroup and E3dScene. Old version only deep-dived for SdrObjGroup, + // E3dScene was just added flat. This is now more correct, but potentially + // there will exist code in the 3D area that *self-iterates* with local + // functions/methods due to this iterator was not doing the expected thing. + // These will be difficult to find, but in most cases should do no harm, + // but cost runtime. Will need to have an eye on this aspect on continued + // changes... + const SdrObjList* pChildren(rSdrObject.getChildrenOfSdrObject()); + const bool bIsGroup(nullptr != pChildren); + + if(!bIsGroup || (SdrIterMode::DeepNoGroups != eMode)) + { + maObjList.push_back(&rSdrObject); + } + + if(bIsGroup && (SdrIterMode::Flat != eMode)) + { + ImpProcessObjectList(*pChildren, eMode); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdlayer.cxx b/svx/source/svdraw/svdlayer.cxx new file mode 100644 index 0000000000..74d5222f92 --- /dev/null +++ b/svx/source/svdraw/svdlayer.cxx @@ -0,0 +1,362 @@ +/* -*- 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 <com/sun/star/uno/Sequence.hxx> + +#include <svx/svdlayer.hxx> +#include <svx/svdmodel.hxx> + +#include <algorithm> +#include <utility> + +bool SdrLayerIDSet::IsEmpty() const +{ + for(sal_uInt8 i : m_aData) + { + if(i != 0) + return false; + } + + return true; +} + +void SdrLayerIDSet::operator&=(const SdrLayerIDSet& r2ndSet) +{ + for(sal_uInt16 i(0); i < 32; i++) + { + m_aData[i] &= r2ndSet.m_aData[i]; + } +} + +/** initialize this set with a UNO sequence of sal_Int8 (e.g. as stored in settings.xml) +*/ +void SdrLayerIDSet::PutValue( const css::uno::Any & rAny ) +{ + css::uno::Sequence< sal_Int8 > aSeq; + if( !(rAny >>= aSeq) ) + return; + + sal_Int16 nCount = static_cast<sal_Int16>(aSeq.getLength()); + if( nCount > 32 ) + nCount = 32; + + sal_Int16 nIndex; + for( nIndex = 0; nIndex < nCount; nIndex++ ) + { + m_aData[nIndex] = static_cast<sal_uInt8>(aSeq[nIndex]); + } + + for( ; nIndex < 32; nIndex++ ) + { + m_aData[nIndex] = 0; + } +} + +SdrLayer::SdrLayer(SdrLayerID nNewID, OUString aNewName) : + maName(std::move(aNewName)), pModel(nullptr), nID(nNewID) +{ + // ODF default values + mbVisibleODF = true; + mbPrintableODF = true; + mbLockedODF = false; +} + +void SdrLayer::SetName(const OUString& rNewName) +{ + if (rNewName == maName) + return; + + maName = rNewName; + + if (pModel) + { + SdrHint aHint(SdrHintKind::LayerChange); + pModel->Broadcast(aHint); + pModel->SetChanged(); + } +} + +bool SdrLayer::operator==(const SdrLayer& rCmpLayer) const +{ + return (nID == rCmpLayer.nID + && maName == rCmpLayer.maName); +} + +SdrLayerAdmin::SdrLayerAdmin(SdrLayerAdmin* pNewParent): + pParent(pNewParent), + pModel(nullptr), + maControlLayerName("controls") +{ +} + +SdrLayerAdmin::SdrLayerAdmin(const SdrLayerAdmin& rSrcLayerAdmin): + pParent(nullptr), + pModel(nullptr), + maControlLayerName("controls") +{ + *this = rSrcLayerAdmin; +} + +SdrLayerAdmin::~SdrLayerAdmin() +{ +} + +void SdrLayerAdmin::ClearLayers() +{ + maLayers.clear(); +} + +SdrLayerAdmin& SdrLayerAdmin::operator=(const SdrLayerAdmin& rSrcLayerAdmin) +{ + if (this != &rSrcLayerAdmin) + { + maLayers.clear(); + pParent=rSrcLayerAdmin.pParent; + sal_uInt16 i; + sal_uInt16 nCount=rSrcLayerAdmin.GetLayerCount(); + for (i=0; i<nCount; i++) { + maLayers.emplace_back(new SdrLayer(*rSrcLayerAdmin.GetLayer(i))); + } + } + return *this; +} + +void SdrLayerAdmin::SetModel(SdrModel* pNewModelel) +{ + if (pNewModelel!=pModel) { + pModel=pNewModelel; + sal_uInt16 nCount=GetLayerCount(); + sal_uInt16 i; + for (i=0; i<nCount; i++) { + GetLayer(i)->SetModel(pNewModelel); + } + } +} + +void SdrLayerAdmin::Broadcast() const +{ + if (pModel!=nullptr) { + SdrHint aHint(SdrHintKind::LayerOrderChange); + pModel->Broadcast(aHint); + pModel->SetChanged(); + } +} + +void SdrLayerAdmin::InsertLayer(std::unique_ptr<SdrLayer> pLayer, sal_uInt16 nPos) +{ + pLayer->SetModel(pModel); + if(nPos==0xFFFF) + maLayers.push_back(std::move(pLayer)); + else + maLayers.insert(maLayers.begin() + nPos, std::move(pLayer)); + Broadcast(); +} + +std::unique_ptr<SdrLayer> SdrLayerAdmin::RemoveLayer(sal_uInt16 nPos) +{ + std::unique_ptr<SdrLayer> pRetLayer = std::move(maLayers[nPos]); + maLayers.erase(maLayers.begin()+nPos); + Broadcast(); + return pRetLayer; +} + +SdrLayer* SdrLayerAdmin::NewLayer(const OUString& rName, sal_uInt16 nPos) +{ + SdrLayerID nID=GetUniqueLayerID(); + SdrLayer* pLay=new SdrLayer(nID,rName); + pLay->SetModel(pModel); + if(nPos==0xFFFF) + maLayers.push_back(std::unique_ptr<SdrLayer>(pLay)); + else + maLayers.insert(maLayers.begin() + nPos, std::unique_ptr<SdrLayer>(pLay)); + Broadcast(); + return pLay; +} + +sal_uInt16 SdrLayerAdmin::GetLayerPos(const SdrLayer* pLayer) const +{ + sal_uInt16 nRet=SDRLAYERPOS_NOTFOUND; + if (pLayer!=nullptr) { + auto it = std::find_if(maLayers.begin(), maLayers.end(), + [&](const std::unique_ptr<SdrLayer> & p) { return p.get() == pLayer; }); + if (it!=maLayers.end()) { + nRet=it - maLayers.begin(); + } + } + return nRet; +} + +SdrLayer* SdrLayerAdmin::GetLayer(const OUString& rName) +{ + return const_cast<SdrLayer*>(const_cast<const SdrLayerAdmin*>(this)->GetLayer(rName)); +} + +const SdrLayer* SdrLayerAdmin::GetLayer(const OUString& rName) const +{ + sal_uInt16 i(0); + const SdrLayer* pLay = nullptr; + + while(i < GetLayerCount() && !pLay) + { + if (rName == GetLayer(i)->GetName()) + pLay = GetLayer(i); + else + i++; + } + + if(!pLay && pParent) + { + pLay = pParent->GetLayer(rName); + } + + return pLay; +} + +SdrLayerID SdrLayerAdmin::GetLayerID(const OUString& rName) const +{ + SdrLayerID nRet=SDRLAYER_NOTFOUND; + const SdrLayer* pLay=GetLayer(rName); + if (pLay!=nullptr) nRet=pLay->GetID(); + return nRet; +} + +const SdrLayer* SdrLayerAdmin::GetLayerPerID(SdrLayerID nID) const +{ + for (auto const & pLayer : maLayers) + if (pLayer->GetID() == nID) + return pLayer.get(); + return nullptr; +} + +// Global LayerIDs begin at 0 and increase, +// local LayerIDs begin at 254 and decrease; +// 255 is reserved for SDRLAYER_NOTFOUND. + +SdrLayerID SdrLayerAdmin::GetUniqueLayerID() const +{ + SdrLayerIDSet aSet; + for (sal_uInt16 j=0; j<GetLayerCount(); j++) + { + aSet.Set(GetLayer(j)->GetID()); + } + sal_uInt8 i; + if (pParent != nullptr) + { + i = 254; + while (i && aSet.IsSet(SdrLayerID(i))) + --i; + assert(i != 0); + if (i == 0) + i = 254; + } + else + { + i = 0; + while (i<=254 && aSet.IsSet(SdrLayerID(i))) + i++; + assert(i <= 254); + if (i>254) + i = 0; + } + return SdrLayerID(i); +} + +void SdrLayerAdmin::SetControlLayerName(const OUString& rNewName) +{ + maControlLayerName = rNewName; +} + +void SdrLayerAdmin::getVisibleLayersODF( SdrLayerIDSet& rOutSet) const +{ + rOutSet.ClearAll(); + for( auto & pCurrentLayer : maLayers ) + { + if ( pCurrentLayer->IsVisibleODF() ) + rOutSet.Set( pCurrentLayer->GetID() ); + } +} + +void SdrLayerAdmin::getPrintableLayersODF( SdrLayerIDSet& rOutSet) const +{ + rOutSet.ClearAll(); + for( auto & pCurrentLayer : maLayers ) + { + if ( pCurrentLayer->IsPrintableODF() ) + rOutSet.Set( pCurrentLayer->GetID() ); + } +} + +void SdrLayerAdmin::getLockedLayersODF( SdrLayerIDSet& rOutSet) const +{ + rOutSet.ClearAll(); + for( auto& pCurrentLayer : maLayers ) + { + if ( pCurrentLayer->IsLockedODF() ) + rOutSet.Set( pCurrentLayer->GetID() ); + } +} + + // Generates a bitfield for settings.xml from the SdrLayerIDSet. + // Output is a UNO sequence of BYTE (which is 'short' in API). +void SdrLayerAdmin::QueryValue(const SdrLayerIDSet& rViewLayerSet, css::uno::Any& rAny) +{ + // tdf#119392 The SdrLayerIDSet in a view is ordered according LayerID, but in file + // the bitfield is interpreted in order of layers in <draw:layer-set>. + // First generate a new bitfield based on rViewLayerSet in the needed order. + sal_uInt8 aTmp[32]; // 256 bits in settings.xml makes byte 0 to 31 + for (auto nIndex = 0; nIndex <32; nIndex++) + { + aTmp[nIndex] = 0; + } + sal_uInt8 nByteIndex = 0; + sal_uInt8 nBitpos = 0; + sal_uInt16 nLayerPos = 0; // Position of the layer in member aLayer and in <draw:layer-set> in file + sal_uInt16 nLayerIndex = 0; + for( const auto& pCurrentLayer : maLayers ) + { + SdrLayerID nCurrentID = pCurrentLayer->GetID(); + if ( rViewLayerSet.IsSet(nCurrentID) ) + { + nLayerPos = nLayerIndex; + nByteIndex = nLayerPos / 8; + if (nByteIndex > 31) + continue; // skip position, if too large for bitfield + nBitpos = nLayerPos % 8; + aTmp[nByteIndex] |= (1 << nBitpos); + } + ++nLayerIndex; + } + + // Second transform the bitfield to byte sequence, same as in previous version of QueryValue + sal_uInt8 nNumBytesSet = 0; + for( auto nIndex = 31; nIndex >= 0; nIndex--) + { + if( 0 != aTmp[nIndex] ) + { + nNumBytesSet = nIndex + 1; + break; + } + } + css::uno::Sequence< sal_Int8 > aSeq( nNumBytesSet ); + std::transform(aTmp, aTmp + nNumBytesSet, aSeq.getArray(), + [](const sal_uInt8 b) { return static_cast<sal_Int8>(b); }); + rAny <<= aSeq; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdmark.cxx b/svx/source/svdraw/svdmark.cxx new file mode 100644 index 0000000000..521e56d44b --- /dev/null +++ b/svx/source/svdraw/svdmark.cxx @@ -0,0 +1,780 @@ +/* -*- 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 <sal/config.h> + +#include <osl/time.h> +#include <svx/svdmark.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> + + +#include <svx/obj3d.hxx> +#include <svx/scene3d.hxx> +#include <svl/SfxBroadcaster.hxx> +#include <svx/svdoedge.hxx> +#include <osl/diagnose.h> + +#include <cassert> + +void SdrMark::setTime() +{ + TimeValue aNow; + osl_getSystemTime(&aNow); + mnTimeStamp = sal_Int64(aNow.Seconds) * 1000000000 + aNow.Nanosec; +} + +SdrMark::SdrMark(SdrObject* pNewObj, SdrPageView* pNewPageView) +: mpSelectedSdrObject(pNewObj), + mpPageView(pNewPageView), + mbCon1(false), + mbCon2(false), + mnUser(0) +{ + if(mpSelectedSdrObject) + { + mpSelectedSdrObject->AddObjectUser( *this ); + } + setTime(); +} + +SdrMark::SdrMark(const SdrMark& rMark) +: ObjectUser(), + mnTimeStamp(0), + mpSelectedSdrObject(nullptr), + mpPageView(nullptr), + mbCon1(false), + mbCon2(false), + mnUser(0) +{ + *this = rMark; +} + +SdrMark::~SdrMark() +{ + if (mpSelectedSdrObject) + { + mpSelectedSdrObject->RemoveObjectUser( *this ); + } +} + +void SdrMark::ObjectInDestruction(const SdrObject& rObject) +{ + (void) rObject; // avoid warnings + OSL_ENSURE(mpSelectedSdrObject && mpSelectedSdrObject == &rObject, "SdrMark::ObjectInDestruction: called from object different from hosted one (!)"); + OSL_ENSURE(mpSelectedSdrObject, "SdrMark::ObjectInDestruction: still selected SdrObject is deleted, deselect first (!)"); + mpSelectedSdrObject = nullptr; +} + +void SdrMark::SetMarkedSdrObj(SdrObject* pNewObj) +{ + if(mpSelectedSdrObject) + { + mpSelectedSdrObject->RemoveObjectUser( *this ); + } + + mpSelectedSdrObject = pNewObj; + + if(mpSelectedSdrObject) + { + mpSelectedSdrObject->AddObjectUser( *this ); + } +} + +SdrMark& SdrMark::operator=(const SdrMark& rMark) +{ + SetMarkedSdrObj(rMark.mpSelectedSdrObject); + + mnTimeStamp = rMark.mnTimeStamp; + mpPageView = rMark.mpPageView; + mbCon1 = rMark.mbCon1; + mbCon2 = rMark.mbCon2; + mnUser = rMark.mnUser; + maPoints = rMark.maPoints; + maGluePoints = rMark.maGluePoints; + + return *this; +} + +static bool ImpSdrMarkListSorter(std::unique_ptr<SdrMark> const& lhs, std::unique_ptr<SdrMark> const& rhs) +{ + SdrObject* pObj1 = lhs->GetMarkedSdrObj(); + SdrObject* pObj2 = rhs->GetMarkedSdrObj(); + SdrObjList* pOL1 = pObj1 ? pObj1->getParentSdrObjListFromSdrObject() : nullptr; + SdrObjList* pOL2 = pObj2 ? pObj2->getParentSdrObjListFromSdrObject() : nullptr; + + if (pOL1 == pOL2) + { + // AF: Note that I reverted a change from sal_uInt32 to sal_uLong (made + // for 64bit compliance, #i78198#) because internally in SdrObject + // both nOrdNum and mnNavigationPosition are stored as sal_uInt32. + sal_uInt32 nObjOrd1(pObj1 ? pObj1->GetNavigationPosition() : 0); + sal_uInt32 nObjOrd2(pObj2 ? pObj2->GetNavigationPosition() : 0); + + return nObjOrd1 < nObjOrd2; + } + else + { + return pOL1 < pOL2; + } +} + + +void SdrMarkList::ForceSort() const +{ + if(!mbSorted) + { + const_cast<SdrMarkList*>(this)->ImpForceSort(); + } +} + +void SdrMarkList::ImpForceSort() +{ + if(mbSorted) + return; + + mbSorted = true; + size_t nCount = maList.size(); + + // remove invalid + if(nCount > 0 ) + { + std::erase_if(maList, [](std::unique_ptr<SdrMark>& rItem) { return rItem->GetMarkedSdrObj() == nullptr; }); + nCount = maList.size(); + } + + if(nCount <= 1) + return; + + std::sort(maList.begin(), maList.end(), ImpSdrMarkListSorter); + + // remove duplicates + if(maList.size() <= 1) + return; + + SdrMark* pCurrent = maList.back().get(); + for (size_t count = maList.size() - 1; count; --count) + { + size_t i = count - 1; + SdrMark* pCmp = maList[i].get(); + assert(pCurrent->GetMarkedSdrObj()); + if(pCurrent->GetMarkedSdrObj() == pCmp->GetMarkedSdrObj()) + { + // Con1/Con2 Merging + if(pCmp->IsCon1()) + pCurrent->SetCon1(true); + + if(pCmp->IsCon2()) + pCurrent->SetCon2(true); + + // delete pCmp + maList.erase(maList.begin() + i); + } + else + { + pCurrent = pCmp; + } + } +} + +void SdrMarkList::Clear() +{ + maList.clear(); + mbSorted = true; //we're empty, so can be considered sorted + SetNameDirty(); +} + +SdrMarkList& SdrMarkList::operator=(const SdrMarkList& rLst) +{ + if (this != &rLst) + { + Clear(); + + for(size_t i = 0; i < rLst.GetMarkCount(); ++i) + { + SdrMark* pMark = rLst.GetMark(i); + maList.emplace_back(new SdrMark(*pMark)); + } + + maMarkName = rLst.maMarkName; + mbNameOk = rLst.mbNameOk; + maPointName = rLst.maPointName; + mbPointNameOk = rLst.mbPointNameOk; + maGluePointName = rLst.maGluePointName; + mbSorted = rLst.mbSorted; + } + return *this; +} + +SdrMark* SdrMarkList::GetMark(size_t nNum) const +{ + return (nNum < maList.size()) ? maList[nNum].get() : nullptr; +} + +size_t SdrMarkList::FindObject(const SdrObject* pObj) const +{ + // Since relying on OrdNums is not allowed for the selection because objects in the + // selection may not be inserted in a list if they are e.g. modified ATM, i changed + // this loop to just look if the object pointer is in the selection. + + // Problem is that GetOrdNum() which is const, internally casts to non-const and + // hardly sets the OrdNum member of the object (nOrdNum) to 0 (ZERO) if the object + // is not inserted in an object list. + // Since this may be by purpose and necessary somewhere else i decided that it is + // less dangerous to change this method then changing SdrObject::GetOrdNum(). + if(pObj) + { + for(size_t a = 0; a < maList.size(); ++a) + { + if(maList[a]->GetMarkedSdrObj() == pObj) + { + return a; + } + } + } + + return SAL_MAX_SIZE; +} + +void SdrMarkList::InsertEntry(const SdrMark& rMark, bool bChkSort) +{ + SetNameDirty(); + const size_t nCount(maList.size()); + + if(!bChkSort || !mbSorted || nCount == 0) + { + if(!bChkSort) + mbSorted = false; + + maList.emplace_back(new SdrMark(rMark)); + } + else + { + SdrMark* pLast = GetMark(nCount - 1); + const SdrObject* pLastObj = pLast->GetMarkedSdrObj(); + const SdrObject* pNewObj = rMark.GetMarkedSdrObj(); + + if(pLastObj == pNewObj) + { + // This one already exists. + // Con1/Con2 Merging + if(rMark.IsCon1()) + pLast->SetCon1(true); + + if(rMark.IsCon2()) + pLast->SetCon2(true); + } + else + { + maList.emplace_back(new SdrMark(rMark)); + + // now check if the sort is ok + const SdrObjList* pLastOL = pLastObj!=nullptr ? pLastObj->getParentSdrObjListFromSdrObject() : nullptr; + const SdrObjList* pNewOL = pNewObj !=nullptr ? pNewObj->getParentSdrObjListFromSdrObject() : nullptr; + + if(pLastOL == pNewOL) + { + const sal_uLong nLastNum(pLastObj!=nullptr ? pLastObj->GetOrdNum() : 0); + const sal_uLong nNewNum(pNewObj !=nullptr ? pNewObj ->GetOrdNum() : 0); + + if(nNewNum < nLastNum) + { + // at some point, we have to sort + mbSorted = false; + } + } + else + { + // at some point, we have to sort + mbSorted = false; + } + } + } +} + +void SdrMarkList::DeleteMark(size_t nNum) +{ + SdrMark* pMark = GetMark(nNum); + DBG_ASSERT(pMark!=nullptr,"DeleteMark: MarkEntry not found."); + + if(pMark) + { + maList.erase(maList.begin() + nNum); + if (maList.empty()) + mbSorted = true; //we're empty, so can be considered sorted + SetNameDirty(); + } +} + +void SdrMarkList::ReplaceMark(const SdrMark& rNewMark, size_t nNum) +{ + SdrMark* pMark = GetMark(nNum); + DBG_ASSERT(pMark!=nullptr,"ReplaceMark: MarkEntry not found."); + + if(pMark) + { + SetNameDirty(); + maList[nNum].reset(new SdrMark(rNewMark)); + mbSorted = false; + } +} + +void SdrMarkList::Merge(const SdrMarkList& rSrcList, bool bReverse) +{ + const size_t nCount(rSrcList.maList.size()); + + if(rSrcList.mbSorted) + { + // merge without forcing a Sort in rSrcList + bReverse = false; + } + + if(!bReverse) + { + for(size_t i = 0; i < nCount; ++i) + { + SdrMark* pM = rSrcList.maList[i].get(); + InsertEntry(*pM); + } + } + else + { + for(size_t i = nCount; i > 0;) + { + --i; + SdrMark* pM = rSrcList.maList[i].get(); + InsertEntry(*pM); + } + } +} + +bool SdrMarkList::DeletePageView(const SdrPageView& rPV) +{ + bool bChgd(false); + + for(auto it = maList.begin(); it != maList.end(); ) + { + SdrMark* pMark = it->get(); + + if(pMark->GetPageView()==&rPV) + { + it = maList.erase(it); + SetNameDirty(); + bChgd = true; + } + else + ++it; + } + + return bChgd; +} + +bool SdrMarkList::InsertPageView(const SdrPageView& rPV) +{ + bool bChgd(false); + DeletePageView(rPV); // delete all of them, then append the entire page + const SdrObjList* pOL = rPV.GetObjList(); + + for (const rtl::Reference<SdrObject>& pObj : *pOL) + { + bool bDoIt(rPV.IsObjMarkable(pObj.get())); + + if(bDoIt) + { + maList.emplace_back(new SdrMark(pObj.get(), const_cast<SdrPageView*>(&rPV))); + SetNameDirty(); + bChgd = true; + } + } + + return bChgd; +} + +const OUString& SdrMarkList::GetMarkDescription() const +{ + const size_t nCount(GetMarkCount()); + + if(mbNameOk && 1 == nCount) + { + // if it's a single selection, cache only text frame + const SdrObject* pObj = GetMark(0)->GetMarkedSdrObj(); + const SdrTextObj* pTextObj = DynCastSdrTextObj( pObj ); + + if(!pTextObj || !pTextObj->IsTextFrame()) + { + const_cast<SdrMarkList*>(this)->mbNameOk = false; + } + } + + if(!mbNameOk) + { + SdrMark* pMark = GetMark(0); + OUString aNam; + + if(!nCount) + { + const_cast<SdrMarkList*>(this)->maMarkName = SvxResId(STR_ObjNameNoObj); + } + else if(1 == nCount) + { + if(pMark->GetMarkedSdrObj()) + { + aNam = pMark->GetMarkedSdrObj()->TakeObjNameSingul(); + } + } + else + { + if(pMark->GetMarkedSdrObj()) + { + aNam = pMark->GetMarkedSdrObj()->TakeObjNamePlural(); + bool bEq(true); + + for(size_t i = 1; i < GetMarkCount() && bEq; ++i) + { + SdrMark* pMark2 = GetMark(i); + OUString aStr1(pMark2->GetMarkedSdrObj()->TakeObjNamePlural()); + bEq = aNam == aStr1; + } + + if(!bEq) + { + aNam = SvxResId(STR_ObjNamePlural); + } + } + + aNam = OUString::number( nCount ) + " " + aNam; + } + + const_cast<SdrMarkList*>(this)->maMarkName = aNam; + const_cast<SdrMarkList*>(this)->mbNameOk = true; + } + + return maMarkName; +} + +const OUString& SdrMarkList::GetPointMarkDescription(bool bGlue) const +{ + bool& rNameOk = const_cast<bool&>(bGlue ? mbGluePointNameOk : mbPointNameOk); + OUString& rName = const_cast<OUString&>(bGlue ? maGluePointName : maPointName); + const size_t nMarkCount(GetMarkCount()); + size_t nMarkPtCnt(0); + size_t nMarkPtObjCnt(0); + size_t n1stMarkNum(SAL_MAX_SIZE); + + for(size_t nMarkNum = 0; nMarkNum < nMarkCount; ++nMarkNum) + { + const SdrMark* pMark = GetMark(nMarkNum); + const SdrUShortCont& rPts = bGlue ? pMark->GetMarkedGluePoints() : pMark->GetMarkedPoints(); + + if (!rPts.empty()) + { + if(n1stMarkNum == SAL_MAX_SIZE) + { + n1stMarkNum = nMarkNum; + } + + nMarkPtCnt += rPts.size(); + nMarkPtObjCnt++; + } + + if(nMarkPtObjCnt > 1 && rNameOk) + { + // preliminary decision + return rName; + } + } + + if(rNameOk && 1 == nMarkPtObjCnt) + { + // if it's a single selection, cache only text frame + const SdrObject* pObj = GetMark(0)->GetMarkedSdrObj(); + const SdrTextObj* pTextObj = DynCastSdrTextObj( pObj ); + + if(!pTextObj || !pTextObj->IsTextFrame()) + { + rNameOk = false; + } + } + + if(!nMarkPtObjCnt) + { + rName.clear(); + rNameOk = true; + } + else if(!rNameOk) + { + const SdrMark* pMark = GetMark(n1stMarkNum); + OUString aNam; + + if(1 == nMarkPtObjCnt) + { + if(pMark->GetMarkedSdrObj()) + { + aNam = pMark->GetMarkedSdrObj()->TakeObjNameSingul(); + } + } + else + { + if(pMark->GetMarkedSdrObj()) + { + aNam = pMark->GetMarkedSdrObj()->TakeObjNamePlural(); + } + + bool bEq(true); + + for(size_t i = n1stMarkNum + 1; i < GetMarkCount() && bEq; ++i) + { + const SdrMark* pMark2 = GetMark(i); + const SdrUShortCont& rPts = bGlue ? pMark2->GetMarkedGluePoints() : pMark2->GetMarkedPoints(); + + if (!rPts.empty() && pMark2->GetMarkedSdrObj()) + { + OUString aStr1(pMark2->GetMarkedSdrObj()->TakeObjNamePlural()); + bEq = aNam == aStr1; + } + } + + if(!bEq) + { + aNam = SvxResId(STR_ObjNamePlural); + } + + aNam = OUString::number( nMarkPtObjCnt ) + " " + aNam; + } + + OUString aStr1; + + if(1 == nMarkPtCnt) + { + aStr1 = SvxResId(bGlue ? STR_ViewMarkedGluePoint : STR_ViewMarkedPoint); + } + else + { + aStr1 = SvxResId(bGlue ? STR_ViewMarkedGluePoints : STR_ViewMarkedPoints); + aStr1 = aStr1.replaceFirst("%2", OUString::number( nMarkPtCnt )); + } + + aStr1 = aStr1.replaceFirst("%1", aNam); + rName = aStr1; + rNameOk = true; + } + + return rName; +} + +bool SdrMarkList::TakeBoundRect(SdrPageView const * pPV, tools::Rectangle& rRect) const +{ + bool bFnd(false); + tools::Rectangle aR; + + for(size_t i = 0; i < GetMarkCount(); ++i) + { + SdrMark* pMark = GetMark(i); + + if(!pPV || pMark->GetPageView() == pPV) + { + if(pMark->GetMarkedSdrObj()) + { + aR = pMark->GetMarkedSdrObj()->GetCurrentBoundRect(); + + if(bFnd) + { + rRect.Union(aR); + } + else + { + rRect = aR; + bFnd = true; + } + } + } + } + + return bFnd; +} + +bool SdrMarkList::TakeSnapRect(SdrPageView const * pPV, tools::Rectangle& rRect) const +{ + bool bFnd(false); + + for(size_t i = 0; i < GetMarkCount(); ++i) + { + SdrMark* pMark = GetMark(i); + + if(!pPV || pMark->GetPageView() == pPV) + { + if(pMark->GetMarkedSdrObj()) + { + tools::Rectangle aR(pMark->GetMarkedSdrObj()->GetSnapRect()); + + if(bFnd) + { + rRect.Union(aR); + } + else + { + rRect = aR; + bFnd = true; + } + } + } + } + + return bFnd; +} + + +namespace sdr +{ + ViewSelection::ViewSelection() + : mbEdgesOfMarkedNodesDirty(false) + { + } + + void ViewSelection::SetEdgesOfMarkedNodesDirty() + { + if(!mbEdgesOfMarkedNodesDirty) + { + mbEdgesOfMarkedNodesDirty = true; + maEdgesOfMarkedNodes.Clear(); + maMarkedEdgesOfMarkedNodes.Clear(); + maAllMarkedObjects.clear(); + } + } + + const SdrMarkList& ViewSelection::GetEdgesOfMarkedNodes() const + { + if(mbEdgesOfMarkedNodesDirty) + { + const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes(); + } + + return maEdgesOfMarkedNodes; + } + + const SdrMarkList& ViewSelection::GetMarkedEdgesOfMarkedNodes() const + { + if(mbEdgesOfMarkedNodesDirty) + { + const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes(); + } + + return maMarkedEdgesOfMarkedNodes; + } + + const std::vector<SdrObject*>& ViewSelection::GetAllMarkedObjects() const + { + if(mbEdgesOfMarkedNodesDirty) + const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes(); + + return maAllMarkedObjects; + } + + void ViewSelection::ImplCollectCompleteSelection(SdrObject* pObj) + { + if(!pObj) + return; + + bool bIsGroup(pObj->IsGroupObject()); + + if(bIsGroup && DynCastE3dObject(pObj) != nullptr && DynCastE3dScene(pObj) == nullptr) + { + bIsGroup = false; + } + + if(bIsGroup) + { + SdrObjList* pList = pObj->GetSubList(); + + for (const rtl::Reference<SdrObject>& pObj2 : *pList) + ImplCollectCompleteSelection(pObj2.get()); + } + + maAllMarkedObjects.push_back(pObj); + } + + void ViewSelection::ImpForceEdgesOfMarkedNodes() + { + if(!mbEdgesOfMarkedNodesDirty) + return; + + mbEdgesOfMarkedNodesDirty = false; + maMarkedObjectList.ForceSort(); + maEdgesOfMarkedNodes.Clear(); + maMarkedEdgesOfMarkedNodes.Clear(); + maAllMarkedObjects.clear(); + + // GetMarkCount after ForceSort + const size_t nMarkCount(maMarkedObjectList.GetMarkCount()); + + for(size_t a = 0; a < nMarkCount; ++a) + { + SdrObject* pCandidate = maMarkedObjectList.GetMark(a)->GetMarkedSdrObj(); + if(!pCandidate) + continue; + + // build transitive hull + ImplCollectCompleteSelection(pCandidate); + + // travel over broadcaster/listener to access edges connected to the selected object + const SfxBroadcaster* pBC = pCandidate->GetBroadcaster(); + if(!pBC) + continue; + + pBC->ForAllListeners( + [this, &pCandidate, &a] (SfxListener* pLst) + { + SdrEdgeObj* pEdge = dynamic_cast<SdrEdgeObj*>( pLst ); + + if(pEdge && pEdge->IsInserted() && pEdge->getSdrPageFromSdrObject() == pCandidate->getSdrPageFromSdrObject()) + { + SdrMark aM(pEdge, maMarkedObjectList.GetMark(a)->GetPageView()); + + if(pEdge->GetConnectedNode(true) == pCandidate) + { + aM.SetCon1(true); + } + + if(pEdge->GetConnectedNode(false) == pCandidate) + { + aM.SetCon2(true); + } + + if(SAL_MAX_SIZE == maMarkedObjectList.FindObject(pEdge)) + { + // check if it itself is selected + maEdgesOfMarkedNodes.InsertEntry(aM); + } + else + { + maMarkedEdgesOfMarkedNodes.InsertEntry(aM); + } + } + return false; + }); + } + maEdgesOfMarkedNodes.ForceSort(); + maMarkedEdgesOfMarkedNodes.ForceSort(); + } +} // end of namespace sdr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdmodel.cxx b/svx/source/svdraw/svdmodel.cxx new file mode 100644 index 0000000000..90fc769e20 --- /dev/null +++ b/svx/source/svdraw/svdmodel.cxx @@ -0,0 +1,2009 @@ +/* -*- 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/svdmodel.hxx> +#include <cassert> +#include <math.h> +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <unotools/configmgr.hxx> +#include <unotools/pathoptions.hxx> +#include <svl/whiter.hxx> +#include <svl/asiancfg.hxx> +#include <svx/compatflags.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xlndsit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xflgrit.hxx> +#include <svx/xflftrit.hxx> +#include <svx/xflhtit.hxx> +#include <svx/xlnstit.hxx> +#include <editeng/editeng.hxx> +#include <svx/xtable.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdpool.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <textchain.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdoutl.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/theme/IThemeColorChanger.hxx> +#include <svdoutlinercache.hxx> +#include <svx/sdasitm.hxx> +#include <officecfg/Office/Common.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <svl/style.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <editeng/eeitem.hxx> +#include <svl/itemset.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <memory> +#include <libxml/xmlwriter.h> +#include <sfx2/viewsh.hxx> +#include <o3tl/enumrange.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/UnitConversion.hxx> +#include <docmodel/theme/Theme.hxx> +#include <svx/ColorSets.hxx> +#include <svx/svditer.hxx> +#include <svx/svdoashp.hxx> + + +using namespace ::com::sun::star; + +struct SdrModelImpl +{ + SfxUndoManager* mpUndoManager; + SdrUndoFactory* mpUndoFactory; + bool mbAnchoredTextOverflowLegacy; // tdf#99729 compatibility flag + bool mbLegacyFontwork; // tdf#148000 compatibility flag + bool mbConnectorUseSnapRect; // tdf#149756 compatibility flag + bool mbIgnoreBreakAfterMultilineField; ///< tdf#148966 compatibility flag + std::shared_ptr<model::Theme> mpTheme; + std::shared_ptr<svx::IThemeColorChanger> mpThemeColorChanger; + + SdrModelImpl() + : mpUndoManager(nullptr) + , mpUndoFactory(nullptr) + , mbAnchoredTextOverflowLegacy(false) + , mbLegacyFontwork(false) + , mbConnectorUseSnapRect(false) + , mbIgnoreBreakAfterMultilineField(false) + , mpTheme(new model::Theme("Office")) + {} + + void initTheme() + { + auto const* pColorSet = svx::ColorSets::get().getColorSet(u"LibreOffice"); + if (pColorSet) + { + std::shared_ptr<model::ColorSet> pDefaultColorSet(new model::ColorSet(*pColorSet)); + mpTheme->setColorSet(pDefaultColorSet); + } + } +}; + + +SdrModel::SdrModel(SfxItemPool* pPool, comphelper::IEmbeddedHelper* pEmbeddedHelper, bool bDisablePropertyFiles) + : m_eObjUnit(SdrEngineDefaults::GetMapUnit()) + , m_eUIUnit(FieldUnit::MM) + , m_aUIScale(Fraction(1,1)) + , m_nUIUnitDecimalMark(0) + , m_pLayerAdmin(new SdrLayerAdmin) + , m_pItemPool(pPool) + , m_pEmbeddedHelper(pEmbeddedHelper) + , mnDefTextHgt(SdrEngineDefaults::GetFontHeight()) + , m_pRefOutDev(nullptr) + , m_pDefaultStyleSheet(nullptr) + , mpDefaultStyleSheetForSdrGrafObjAndSdrOle2Obj(nullptr) + , m_pLinkManager(nullptr) + , m_nUndoLevel(0) + , m_bIsWriter(true) + , m_bThemedControls(true) + , mbUndoEnabled(true) + , mbChanged(false) + , m_bPagNumsDirty(false) + , m_bMPgNumsDirty(false) + , m_bTransportContainer(false) + , m_bReadOnly(false) + , m_bTransparentTextFrames(false) + , m_bSwapGraphics(false) + , m_bPasteResize(false) + , m_bStarDrawPreviewMode(false) + , mbDisableTextEditUsesCommonUndoManager(false) + , mbVOCInvalidationIsReliable(false) + , m_nDefaultTabulator(0) + , m_nMaxUndoCount(16) + , m_pTextChain(new TextChain) + , mpImpl(new SdrModelImpl) + , mnCharCompressType(CharCompressType::NONE) + , mnHandoutPageCount(0) + , mbModelLocked(false) + , mbKernAsianPunctuation(false) + , mbAddExtLeading(false) + , mbInDestruction(false) +{ + if (!utl::ConfigManager::IsFuzzing()) + { + mnCharCompressType = static_cast<CharCompressType>( + officecfg::Office::Common::AsianLayout::CompressCharacterDistance::get()); + } + + if (m_pItemPool == nullptr) + { + m_pItemPool = new SdrItemPool(nullptr); + // Outliner doesn't have its own Pool, so use the EditEngine's + rtl::Reference<SfxItemPool> pOutlPool=EditEngine::CreatePool(); + // OutlinerPool as SecondaryPool of SdrPool + m_pItemPool->SetSecondaryPool(pOutlPool.get()); + // remember that I created both pools myself + m_bIsWriter = false; + } + m_pItemPool->SetDefaultMetric(m_eObjUnit); + +// using static SdrEngineDefaults only if default SvxFontHeight item is not available + const SfxPoolItem* pPoolItem = m_pItemPool->GetPoolDefaultItem( EE_CHAR_FONTHEIGHT ); + if (pPoolItem) + mnDefTextHgt = static_cast<const SvxFontHeightItem*>(pPoolItem)->GetHeight(); + + m_pItemPool->SetPoolDefaultItem( makeSdrTextWordWrapItem( false ) ); + + SetTextDefaults(); + m_pLayerAdmin->SetModel(this); + ImpSetUIUnit(); + + // can't create DrawOutliner OnDemand, because I can't get the Pool, + // then (only from 302 onwards!) + m_pDrawOutliner = SdrMakeOutliner(OutlinerMode::TextObject, *this); + ImpSetOutlinerDefaults(m_pDrawOutliner.get(), true); + + m_pHitTestOutliner = SdrMakeOutliner(OutlinerMode::TextObject, *this); + ImpSetOutlinerDefaults(m_pHitTestOutliner.get(), true); + + /* Start Text Chaining related code */ + // Initialize Chaining Outliner + m_pChainingOutliner = SdrMakeOutliner( OutlinerMode::TextObject, *this ); + ImpSetOutlinerDefaults(m_pChainingOutliner.get(), true); + + ImpCreateTables(bDisablePropertyFiles || utl::ConfigManager::IsFuzzing()); + + mpImpl->initTheme(); +} + +void SdrModel::implDtorClearModel() +{ + mbInDestruction = true; + + Broadcast(SdrHint(SdrHintKind::ModelCleared)); + + mpOutlinerCache.reset(); + + ClearUndoBuffer(); +#ifdef DBG_UTIL + SAL_WARN_IF(m_pCurrentUndoGroup, "svx", "In the Dtor of the SdrModel there is an open Undo left: \"" + << m_pCurrentUndoGroup->GetComment() << '\"'); +#endif + m_pCurrentUndoGroup.reset(); + + ClearModel(true); +} + +SdrModel::~SdrModel() +{ + implDtorClearModel(); + +#ifdef DBG_UTIL + // SdrObjectLifetimeWatchDog: + if(!maAllIncarnatedObjects.empty()) + { + SAL_WARN("svx", + "SdrModel::~SdrModel: Not all incarnations of SdrObjects deleted, possible memory leak"); + for (const auto & pObj : maAllIncarnatedObjects) + SAL_WARN("svx", "leaked instance of " << typeid(*pObj).name()); + } +#endif + + m_pLayerAdmin.reset(); + + m_pTextChain.reset(); + // Delete DrawOutliner only after deleting ItemPool, because ItemPool + // references Items of the DrawOutliner! + m_pChainingOutliner.reset(); + m_pHitTestOutliner.reset(); + m_pDrawOutliner.reset(); + + // delete StyleSheetPool, derived classes should not do this since + // the DrawingEngine may need it in its destructor + if( mxStyleSheetPool.is() ) + { + uno::Reference<lang::XComponent> xComponent( getXWeak( mxStyleSheetPool.get() ), uno::UNO_QUERY ); + if( xComponent.is() ) try + { + xComponent->dispose(); + } + catch (uno::RuntimeException&) + { + } + mxStyleSheetPool.clear(); + } + + mpForbiddenCharactersTable.reset(); + + delete mpImpl->mpUndoFactory; +} + +void SdrModel::SetSwapGraphics() +{ + m_bSwapGraphics = true; +} + +bool SdrModel::IsReadOnly() const +{ + return m_bReadOnly; +} + +void SdrModel::SetReadOnly(bool bYes) +{ + m_bReadOnly=bYes; +} + + +void SdrModel::SetMaxUndoActionCount(sal_uInt32 nCount) +{ + if (nCount<1) nCount=1; + m_nMaxUndoCount=nCount; + while (m_aUndoStack.size()>m_nMaxUndoCount) + m_aUndoStack.pop_back(); +} + +void SdrModel::ClearUndoBuffer() +{ + m_aUndoStack.clear(); + m_aRedoStack.clear(); +} + +bool SdrModel::HasUndoActions() const +{ + return !m_aUndoStack.empty(); +} + +bool SdrModel::HasRedoActions() const +{ + return !m_aRedoStack.empty(); +} + +void SdrModel::Undo() +{ + if( mpImpl->mpUndoManager ) + { + OSL_FAIL("svx::SdrModel::Undo(), method not supported with application undo manager!"); + } + else + { + if(HasUndoActions()) + { + SfxUndoAction* pDo = m_aUndoStack.front().get(); + const bool bWasUndoEnabled = mbUndoEnabled; + mbUndoEnabled = false; + pDo->Undo(); + std::unique_ptr<SfxUndoAction> p = std::move(m_aUndoStack.front()); + m_aUndoStack.pop_front(); + m_aRedoStack.emplace_front(std::move(p)); + mbUndoEnabled = bWasUndoEnabled; + } + } +} + +void SdrModel::Redo() +{ + if( mpImpl->mpUndoManager ) + { + OSL_FAIL("svx::SdrModel::Redo(), method not supported with application undo manager!"); + } + else + { + if(HasRedoActions()) + { + SfxUndoAction* pDo = m_aRedoStack.front().get(); + const bool bWasUndoEnabled = mbUndoEnabled; + mbUndoEnabled = false; + pDo->Redo(); + std::unique_ptr<SfxUndoAction> p = std::move(m_aRedoStack.front()); + m_aRedoStack.pop_front(); + m_aUndoStack.emplace_front(std::move(p)); + mbUndoEnabled = bWasUndoEnabled; + } + } +} + +void SdrModel::Repeat(SfxRepeatTarget& rView) +{ + if( mpImpl->mpUndoManager ) + { + OSL_FAIL("svx::SdrModel::Redo(), method not supported with application undo manager!"); + } + else + { + if(HasUndoActions()) + { + SfxUndoAction* pDo = m_aUndoStack.front().get(); + if(pDo->CanRepeat(rView)) + { + pDo->Repeat(rView); + } + } + } +} + +void SdrModel::ImpPostUndoAction(std::unique_ptr<SdrUndoAction> pUndo) +{ + DBG_ASSERT( mpImpl->mpUndoManager == nullptr, "svx::SdrModel::ImpPostUndoAction(), method not supported with application undo manager!" ); + if( !IsUndoEnabled() ) + return; + + if (m_aUndoLink) + { + m_aUndoLink(std::move(pUndo)); + } + else + { + m_aUndoStack.emplace_front(std::move(pUndo)); + while (m_aUndoStack.size()>m_nMaxUndoCount) + { + m_aUndoStack.pop_back(); + } + m_aRedoStack.clear(); + } +} + +void SdrModel::BegUndo() +{ + if( mpImpl->mpUndoManager ) + { + ViewShellId nViewShellId(-1); + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + nViewShellId = pViewShell->GetViewShellId(); + mpImpl->mpUndoManager->EnterListAction("","",0,nViewShellId); + m_nUndoLevel++; + } + else if( IsUndoEnabled() ) + { + if(!m_pCurrentUndoGroup) + { + m_pCurrentUndoGroup.reset(new SdrUndoGroup(*this)); + m_nUndoLevel=1; + } + else + { + m_nUndoLevel++; + } + } +} + +void SdrModel::BegUndo(const OUString& rComment) +{ + if( mpImpl->mpUndoManager ) + { + ViewShellId nViewShellId(-1); + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + nViewShellId = pViewShell->GetViewShellId(); + mpImpl->mpUndoManager->EnterListAction( rComment, "", 0, nViewShellId ); + m_nUndoLevel++; + } + else if( IsUndoEnabled() ) + { + BegUndo(); + if (m_nUndoLevel==1) + { + m_pCurrentUndoGroup->SetComment(rComment); + } + } +} + +void SdrModel::BegUndo(const OUString& rComment, const OUString& rObjDescr, SdrRepeatFunc eFunc) +{ + if( mpImpl->mpUndoManager ) + { + OUString aComment(rComment); + if( !aComment.isEmpty() && !rObjDescr.isEmpty() ) + { + aComment = aComment.replaceFirst("%1", rObjDescr); + } + ViewShellId nViewShellId(-1); + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + nViewShellId = pViewShell->GetViewShellId(); + mpImpl->mpUndoManager->EnterListAction( aComment,"",0,nViewShellId ); + m_nUndoLevel++; + } + else if( IsUndoEnabled() ) + { + BegUndo(); + if (m_nUndoLevel==1) + { + m_pCurrentUndoGroup->SetComment(rComment); + m_pCurrentUndoGroup->SetObjDescription(rObjDescr); + m_pCurrentUndoGroup->SetRepeatFunction(eFunc); + } + } +} + +void SdrModel::EndUndo() +{ + DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::EndUndo(): UndoLevel is already 0!"); + if( mpImpl->mpUndoManager ) + { + if( m_nUndoLevel ) + { + m_nUndoLevel--; + mpImpl->mpUndoManager->LeaveListAction(); + } + } + else + { + if(m_pCurrentUndoGroup!=nullptr && IsUndoEnabled()) + { + m_nUndoLevel--; + if(m_nUndoLevel==0) + { + if(m_pCurrentUndoGroup->GetActionCount()!=0) + { + ImpPostUndoAction(std::move(m_pCurrentUndoGroup)); + } + else + { + // was empty + m_pCurrentUndoGroup.reset(); + } + } + } + } +} + +void SdrModel::SetUndoComment(const OUString& rComment) +{ + DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::SetUndoComment(): UndoLevel is already 0!"); + + if( mpImpl->mpUndoManager ) + { + OSL_FAIL("svx::SdrModel::SetUndoComment(), method not supported with application undo manager!" ); + } + else if( IsUndoEnabled() && m_nUndoLevel==1) + { + m_pCurrentUndoGroup->SetComment(rComment); + } +} + +void SdrModel::SetUndoComment(const OUString& rComment, const OUString& rObjDescr) +{ + DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::SetUndoComment(): UndoLevel is already 0!"); + if( mpImpl->mpUndoManager ) + { + OSL_FAIL("svx::SdrModel::SetUndoComment(), method not supported with application undo manager!" ); + } + else + { + if (m_nUndoLevel==1) + { + m_pCurrentUndoGroup->SetComment(rComment); + m_pCurrentUndoGroup->SetObjDescription(rObjDescr); + } + } +} + +void SdrModel::AddUndo(std::unique_ptr<SdrUndoAction> pUndo) +{ + if( mpImpl->mpUndoManager ) + { + mpImpl->mpUndoManager->AddUndoAction( std::move(pUndo) ); + } + else if( IsUndoEnabled() ) + { + if (m_pCurrentUndoGroup) + { + m_pCurrentUndoGroup->AddAction(std::move(pUndo)); + } + else + { + ImpPostUndoAction(std::move(pUndo)); + } + } +} + +void SdrModel::EnableUndo( bool bEnable ) +{ + if( mpImpl->mpUndoManager ) + { + mpImpl->mpUndoManager->EnableUndo( bEnable ); + } + else + { + mbUndoEnabled = bEnable; + } +} + +bool SdrModel::IsUndoEnabled() const +{ + if( mpImpl->mpUndoManager ) + { + return mpImpl->mpUndoManager->IsUndoEnabled(); + } + else + { + return mbUndoEnabled; + } +} + +void SdrModel::ImpCreateTables(bool bDisablePropertyFiles) +{ + // use standard path for initial construction + const OUString aTablePath(!bDisablePropertyFiles ? SvtPathOptions().GetPalettePath() : ""); + + for( auto i : o3tl::enumrange<XPropertyListType>() ) + { + maProperties[i] = XPropertyList::CreatePropertyList(i, aTablePath, ""/*TODO?*/ ); + } +} + +void SdrModel::ClearModel(bool bCalledFromDestructor) +{ + if(bCalledFromDestructor) + { + mbInDestruction = true; + } + + sal_Int32 i; + // delete all drawing pages + sal_Int32 nCount=GetPageCount(); + for (i=nCount-1; i>=0; i--) + { + DeletePage( static_cast<sal_uInt16>(i) ); + } + maPages.clear(); + PageListChanged(); + + // delete all Masterpages + nCount=GetMasterPageCount(); + for(i=nCount-1; i>=0; i--) + { + DeleteMasterPage( static_cast<sal_uInt16>(i) ); + } + maMasterPages.clear(); + MasterPageListChanged(); + + m_pLayerAdmin->ClearLayers(); +} + +SdrModel* SdrModel::AllocModel() const +{ + SdrModel* pModel=new SdrModel(); + pModel->SetScaleUnit(m_eObjUnit); + return pModel; +} + +rtl::Reference<SdrPage> SdrModel::AllocPage(bool bMasterPage) +{ + return new SdrPage(*this,bMasterPage); +} + +void SdrModel::SetTextDefaults() const +{ + SetTextDefaults( m_pItemPool.get(), mnDefTextHgt ); +} + +void SdrModel::SetTextDefaults( SfxItemPool* pItemPool, sal_Int32 nDefTextHgt ) +{ + // set application-language specific dynamic pool language defaults + SvxFontItem aSvxFontItem( EE_CHAR_FONTINFO) ; + SvxFontItem aSvxFontItemCJK(EE_CHAR_FONTINFO_CJK); + SvxFontItem aSvxFontItemCTL(EE_CHAR_FONTINFO_CTL); + LanguageType nLanguage; + if (!utl::ConfigManager::IsFuzzing()) + nLanguage = Application::GetSettings().GetLanguageTag().getLanguageType(); + else + nLanguage = LANGUAGE_ENGLISH_US; + + // get DEFAULTFONT_LATIN_TEXT and set at pool as dynamic default + vcl::Font aFont(OutputDevice::GetDefaultFont(DefaultFontType::LATIN_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne)); + aSvxFontItem.SetFamily(aFont.GetFamilyType()); + aSvxFontItem.SetFamilyName(aFont.GetFamilyName()); + aSvxFontItem.SetStyleName(OUString()); + aSvxFontItem.SetPitch( aFont.GetPitch()); + aSvxFontItem.SetCharSet( aFont.GetCharSet() ); + pItemPool->SetPoolDefaultItem(aSvxFontItem); + + // get DEFAULTFONT_CJK_TEXT and set at pool as dynamic default + vcl::Font aFontCJK(OutputDevice::GetDefaultFont(DefaultFontType::CJK_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne)); + aSvxFontItemCJK.SetFamily( aFontCJK.GetFamilyType()); + aSvxFontItemCJK.SetFamilyName(aFontCJK.GetFamilyName()); + aSvxFontItemCJK.SetStyleName(OUString()); + aSvxFontItemCJK.SetPitch( aFontCJK.GetPitch()); + aSvxFontItemCJK.SetCharSet( aFontCJK.GetCharSet()); + pItemPool->SetPoolDefaultItem(aSvxFontItemCJK); + + // get DEFAULTFONT_CTL_TEXT and set at pool as dynamic default + vcl::Font aFontCTL(OutputDevice::GetDefaultFont(DefaultFontType::CTL_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne)); + aSvxFontItemCTL.SetFamily(aFontCTL.GetFamilyType()); + aSvxFontItemCTL.SetFamilyName(aFontCTL.GetFamilyName()); + aSvxFontItemCTL.SetStyleName(OUString()); + aSvxFontItemCTL.SetPitch( aFontCTL.GetPitch() ); + aSvxFontItemCTL.SetCharSet( aFontCTL.GetCharSet()); + pItemPool->SetPoolDefaultItem(aSvxFontItemCTL); + + // set dynamic FontHeight defaults + pItemPool->SetPoolDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT ) ); + pItemPool->SetPoolDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + pItemPool->SetPoolDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + // set FontColor defaults + pItemPool->SetPoolDefaultItem( SvxColorItem(SdrEngineDefaults::GetFontColor(), EE_CHAR_COLOR) ); +} + +SdrOutliner& SdrModel::GetDrawOutliner(const SdrTextObj* pObj) const +{ + m_pDrawOutliner->SetTextObj(pObj); + return *m_pDrawOutliner; +} + +SdrOutliner& SdrModel::GetChainingOutliner(const SdrTextObj* pObj) const +{ + m_pChainingOutliner->SetTextObj(pObj); + return *m_pChainingOutliner; +} + +const SdrTextObj* SdrModel::GetFormattingTextObj() const +{ + if (m_pDrawOutliner!=nullptr) { + return m_pDrawOutliner->GetTextObj(); + } + return nullptr; +} + +void SdrModel::ImpSetOutlinerDefaults( SdrOutliner* pOutliner, bool bInit ) +{ + // Initialization of the Outliners for drawing text and HitTest + if( bInit ) + { + pOutliner->EraseVirtualDevice(); + pOutliner->SetUpdateLayout(false); + pOutliner->SetEditTextObjectPool(m_pItemPool.get()); + pOutliner->SetDefTab(m_nDefaultTabulator); + } + + pOutliner->SetRefDevice(GetRefDevice()); + Outliner::SetForbiddenCharsTable(GetForbiddenCharsTable()); + pOutliner->SetAsianCompressionMode( mnCharCompressType ); + pOutliner->SetKernAsianPunctuation( IsKernAsianPunctuation() ); + pOutliner->SetAddExtLeading( IsAddExtLeading() ); + + if ( !GetRefDevice() ) + { + MapMode aMapMode(m_eObjUnit); + pOutliner->SetRefMapMode(aMapMode); + } +} + +void SdrModel::SetRefDevice(OutputDevice* pDev) +{ + m_pRefOutDev=pDev; + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); + RefDeviceChanged(); +} + +void SdrModel::ImpReformatAllTextObjects() +{ + if( isLocked() ) + return; + + sal_uInt16 nCount=GetMasterPageCount(); + sal_uInt16 nNum; + for (nNum=0; nNum<nCount; nNum++) { + GetMasterPage(nNum)->ReformatAllTextObjects(); + } + nCount=GetPageCount(); + for (nNum=0; nNum<nCount; nNum++) { + GetPage(nNum)->ReformatAllTextObjects(); + } +} + +/* steps over all available pages and sends notify messages to + all edge objects that are connected to other objects so that + they may reposition themselves +*/ +void SdrModel::ImpReformatAllEdgeObjects() +{ + if( isLocked() ) + return; + + sal_uInt16 nCount=GetMasterPageCount(); + sal_uInt16 nNum; + for (nNum=0; nNum<nCount; nNum++) + { + GetMasterPage(nNum)->ReformatAllEdgeObjects(); + } + nCount=GetPageCount(); + for (nNum=0; nNum<nCount; nNum++) + { + GetPage(nNum)->ReformatAllEdgeObjects(); + } +} + +uno::Reference<embed::XStorage> SdrModel::GetDocumentStorage() const +{ + uno::Reference<document::XStorageBasedDocument> const xSBD( + const_cast<SdrModel*>(this)->getUnoModel(), uno::UNO_QUERY); + if (!xSBD.is()) + { + SAL_WARN("svx", "no UNO model"); + return nullptr; + } + return xSBD->getDocumentStorage(); +} + +uno::Reference<io::XInputStream> +SdrModel::GetDocumentStream( OUString const& rURL, + ::comphelper::LifecycleProxy const & rProxy) const +{ + uno::Reference<embed::XStorage> const xStorage(GetDocumentStorage()); + if (!xStorage.is()) + { + SAL_WARN("svx", "no storage?"); + return nullptr; + } + try { + uno::Reference<io::XStream> const xStream( + ::comphelper::OStorageHelper::GetStreamAtPackageURL( + xStorage, rURL, embed::ElementModes::READ, rProxy)); + return (xStream.is()) ? xStream->getInputStream() : nullptr; + } + catch (container::NoSuchElementException const&) + { + SAL_INFO("svx", "not found"); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + return nullptr; +} + +// convert template attributes from the string into "hard" attributes +void SdrModel::BurnInStyleSheetAttributes() +{ + sal_uInt16 nCount=GetMasterPageCount(); + sal_uInt16 nNum; + for (nNum=0; nNum<nCount; nNum++) { + GetMasterPage(nNum)->BurnInStyleSheetAttributes(); + } + nCount=GetPageCount(); + for (nNum=0; nNum<nCount; nNum++) { + GetPage(nNum)->BurnInStyleSheetAttributes(); + } +} + +void SdrModel::RefDeviceChanged() +{ + Broadcast(SdrHint(SdrHintKind::RefDeviceChange)); + ImpReformatAllTextObjects(); +} + +void SdrModel::SetDefaultFontHeight(sal_Int32 nVal) +{ + if (nVal!=mnDefTextHgt) { + mnDefTextHgt=nVal; + ImpReformatAllTextObjects(); + } +} + +void SdrModel::SetDefaultTabulator(sal_uInt16 nVal) +{ + if (m_nDefaultTabulator!=nVal) { + m_nDefaultTabulator=nVal; + Outliner& rOutliner=GetDrawOutliner(); + rOutliner.SetDefTab(nVal); + Broadcast(SdrHint(SdrHintKind::DefaultTabChange)); + ImpReformatAllTextObjects(); + } +} + +void SdrModel::ImpSetUIUnit() +{ + if(0 == m_aUIScale.GetNumerator() || 0 == m_aUIScale.GetDenominator()) + { + m_aUIScale = Fraction(1,1); + } + + m_nUIUnitDecimalMark = 0; + + o3tl::Length eFrom = MapToO3tlLength(m_eObjUnit, o3tl::Length::invalid); + o3tl::Length eTo; + + switch (m_eUIUnit) + { + case FieldUnit::CHAR: + case FieldUnit::LINE: + eTo = o3tl::Length::invalid; + break; + case FieldUnit::PERCENT: + m_nUIUnitDecimalMark += 2; + [[fallthrough]]; + default: + eTo = FieldToO3tlLength(m_eUIUnit, o3tl::Length::invalid); + } // switch + + sal_Int32 nMul = 1, nDiv = 1; + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + { + const auto& [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo); + nMul = mul; + nDiv = div; + } + // #i89872# take Unit of Measurement into account + if(1 != m_aUIScale.GetDenominator() || 1 != m_aUIScale.GetNumerator()) + { + // divide by UIScale + nMul *= m_aUIScale.GetDenominator(); + nDiv *= m_aUIScale.GetNumerator(); + } + + // shorten trailing zeros for dividend + while(0 == (nMul % 10)) + { + m_nUIUnitDecimalMark--; + nMul /= 10; + } + + // shorten trailing zeros for divisor + while(0 == (nDiv % 10)) + { + m_nUIUnitDecimalMark++; + nDiv /= 10; + } + + // end preparations, set member values + m_aUIUnitFact = Fraction(sal_Int32(nMul), sal_Int32(nDiv)); + m_aUIUnitStr = GetUnitString(m_eUIUnit); +} + +void SdrModel::SetScaleUnit(MapUnit eMap) +{ + if (m_eObjUnit!=eMap) { + m_eObjUnit=eMap; + m_pItemPool->SetDefaultMetric(m_eObjUnit); + ImpSetUIUnit(); + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); + ImpReformatAllTextObjects(); + } +} + +void SdrModel::SetUIUnit(FieldUnit eUnit) +{ + if (m_eUIUnit!=eUnit) { + m_eUIUnit=eUnit; + ImpSetUIUnit(); + ImpReformatAllTextObjects(); + } +} + +void SdrModel::SetUIScale(const Fraction& rScale) +{ + if (m_aUIScale!=rScale) { + m_aUIScale=rScale; + ImpSetUIUnit(); + ImpReformatAllTextObjects(); + } +} + +void SdrModel::SetUIUnit(FieldUnit eUnit, const Fraction& rScale) +{ + if (m_eUIUnit!=eUnit || m_aUIScale!=rScale) { + m_eUIUnit=eUnit; + m_aUIScale=rScale; + ImpSetUIUnit(); + ImpReformatAllTextObjects(); + } +} + +OUString SdrModel::GetUnitString(FieldUnit eUnit) +{ + switch(eUnit) + { + default: + case FieldUnit::NONE : + case FieldUnit::CUSTOM : + return OUString(); + case FieldUnit::MM_100TH: + return OUString{"/100mm"}; + case FieldUnit::MM : + return OUString{"mm"}; + case FieldUnit::CM : + return OUString{"cm"}; + case FieldUnit::M : + return OUString{"m"}; + case FieldUnit::KM : + return OUString{"km"}; + case FieldUnit::TWIP : + return OUString{"twip"}; + case FieldUnit::POINT : + return OUString{"pt"}; + case FieldUnit::PICA : + return OUString{"pica"}; + case FieldUnit::INCH : + return OUString{"\""}; + case FieldUnit::FOOT : + return OUString{"ft"}; + case FieldUnit::MILE : + return OUString{"mile(s)"}; + case FieldUnit::PERCENT: + return OUString{"%"}; + } +} + +OUString SdrModel::GetMetricString(tools::Long nVal, bool bNoUnitChars, sal_Int32 nNumDigits) const +{ + // #i22167# + // change to double precision usage to not lose decimal places + const bool bNegative(nVal < 0); + SvtSysLocale aSysLoc; + const LocaleDataWrapper& rLoc(aSysLoc.GetLocaleData()); + double fLocalValue(double(nVal) * double(m_aUIUnitFact)); + + if(bNegative) + { + fLocalValue = -fLocalValue; + } + + if( -1 == nNumDigits ) + { + nNumDigits = LocaleDataWrapper::getNumDigits(); + } + + sal_Int32 nDecimalMark(m_nUIUnitDecimalMark); + + if(nDecimalMark > nNumDigits) + { + const sal_Int32 nDiff(nDecimalMark - nNumDigits); + const double fFactor(pow(10.0, static_cast<int>(nDiff))); + + fLocalValue /= fFactor; + nDecimalMark = nNumDigits; + } + else if(nDecimalMark < nNumDigits) + { + const sal_Int32 nDiff(nNumDigits - nDecimalMark); + const double fFactor(pow(10.0, static_cast<int>(nDiff))); + + fLocalValue *= fFactor; + nDecimalMark = nNumDigits; + } + + OUStringBuffer aBuf = OUString::number(static_cast<sal_Int32>(fLocalValue + 0.5)); + + if(nDecimalMark < 0) + { + // negative nDecimalMark (decimal point) means: add zeros + sal_Int32 nCount(-nDecimalMark); + + for(sal_Int32 i=0; i<nCount; i++) + aBuf.append('0'); + + nDecimalMark = 0; + } + + // the second condition needs to be <= since inside this loop + // also the leading zero is inserted. + if (nDecimalMark > 0 && aBuf.getLength() <= nDecimalMark) + { + // if necessary, add zeros before the decimal point + sal_Int32 nCount = nDecimalMark - aBuf.getLength(); + + if(nCount >= 0 && LocaleDataWrapper::isNumLeadingZero()) + nCount++; + + for(sal_Int32 i=0; i<nCount; i++) + aBuf.insert(0, '0'); + } + + const sal_Unicode cDec( rLoc.getNumDecimalSep()[0] ); + + // insert the decimal mark character + sal_Int32 nBeforeDecimalMark = aBuf.getLength() - nDecimalMark; + + if(nDecimalMark > 0) + aBuf.insert(nBeforeDecimalMark, cDec); + + if(!LocaleDataWrapper::isNumTrailingZeros()) + { + sal_Int32 aPos=aBuf.getLength()-1; + + // Remove all trailing zeros. + while (aPos>=0 && aBuf[aPos]=='0') + --aPos; + + // Remove decimal if it's the last character. + if (aPos>=0 && aBuf[aPos]==cDec) + --aPos; + + // Adjust aPos to index first char to be truncated, if any + if (++aPos<aBuf.getLength()) + aBuf.truncate(aPos); + } + + // if necessary, add separators before every third digit + if( nBeforeDecimalMark > 3 ) + { + const OUString& aThoSep( rLoc.getNumThousandSep() ); + if ( !aThoSep.isEmpty() ) + { + sal_Unicode cTho( aThoSep[0] ); + sal_Int32 i(nBeforeDecimalMark - 3); + + while(i > 0) + { + aBuf.insert(i, cTho); + i -= 3; + } + } + } + + if (aBuf.isEmpty()) + aBuf.append("0"); + + if(bNegative) + { + aBuf.insert(0, "-"); + } + + if(!bNoUnitChars) + aBuf.append(m_aUIUnitStr); + + return aBuf.makeStringAndClear(); +} + +OUString SdrModel::GetAngleString(Degree100 nAngle) +{ + bool bNeg = nAngle < 0_deg100; + + if(bNeg) + nAngle = -nAngle; + + OUStringBuffer aBuf; + aBuf.append(static_cast<sal_Int32>(nAngle)); + + SvtSysLocale aSysLoc; + const LocaleDataWrapper& rLoc = aSysLoc.GetLocaleData(); + sal_Int32 nCount = 2; + + if(LocaleDataWrapper::isNumLeadingZero()) + nCount++; + + while(aBuf.getLength() < nCount) + aBuf.insert(0, '0'); + + aBuf.insert(aBuf.getLength()-2, rLoc.getNumDecimalSep()[0]); + + if(bNeg) + aBuf.insert(0, '-'); + + aBuf.append(DEGREE_CHAR); + + return aBuf.makeStringAndClear(); +} + +OUString SdrModel::GetPercentString(const Fraction& rVal) +{ + sal_Int32 nMul(rVal.GetNumerator()); + sal_Int32 nDiv(rVal.GetDenominator()); + bool bNeg {false}; + + if (nDiv < 0) + { + bNeg = !bNeg; + nDiv = -nDiv; + } + + if (nMul < 0) + { + bNeg = !bNeg; + nMul = -nMul; + } + + sal_Int32 nPct = ((nMul*100) + nDiv/2)/nDiv; + + if (bNeg) + nPct = -nPct; + + return OUString::number(nPct) + "%"; +} + +void SdrModel::SetChanged(bool bFlg) +{ + mbChanged = bFlg; +} + +void SdrModel::RecalcPageNums(bool bMaster) +{ + if(bMaster) + { + sal_uInt16 nCount=sal_uInt16(maMasterPages.size()); + sal_uInt16 i; + for (i=0; i<nCount; i++) { + SdrPage* pPg = maMasterPages[i].get(); + pPg->SetPageNum(i); + } + m_bMPgNumsDirty=false; + } + else + { + sal_uInt16 nCount=sal_uInt16(maPages.size()); + sal_uInt16 i; + for (i=0; i<nCount; i++) { + SdrPage* pPg = maPages[i].get(); + pPg->SetPageNum(i); + } + m_bPagNumsDirty=false; + } +} + +void SdrModel::InsertPage(SdrPage* pPage, sal_uInt16 nPos) +{ + sal_uInt16 nCount = GetPageCount(); + if (nPos > nCount) + nPos = nCount; + + maPages.insert(maPages.begin() + nPos, pPage); + PageListChanged(); + pPage->SetInserted(); + pPage->SetPageNum(nPos); + + if (mbMakePageObjectsNamesUnique) + pPage->MakePageObjectsNamesUnique(); + + if (nPos<nCount) m_bPagNumsDirty=true; + SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, pPage); + Broadcast(aHint); +} + +void SdrModel::DeletePage(sal_uInt16 nPgNum) +{ + RemovePage(nPgNum); +} + +rtl::Reference<SdrPage> SdrModel::RemovePage(sal_uInt16 nPgNum) +{ + rtl::Reference<SdrPage> pPg = maPages[nPgNum]; + maPages.erase(maPages.begin()+nPgNum); + PageListChanged(); + if (pPg) { + pPg->SetInserted(false); + } + m_bPagNumsDirty=true; + SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, pPg.get()); + Broadcast(aHint); + return pPg; +} + +void SdrModel::MovePage(sal_uInt16 nPgNum, sal_uInt16 nNewPos) +{ + rtl::Reference<SdrPage> pPg = std::move(maPages[nPgNum]); + if (pPg) { + maPages.erase(maPages.begin()+nPgNum); // shortcut to avoid two broadcasts + PageListChanged(); + pPg->SetInserted(false); + InsertPage(pPg.get(), nNewPos); + } + else + RemovePage(nPgNum); +} + +void SdrModel::InsertMasterPage(SdrPage* pPage, sal_uInt16 nPos) +{ + sal_uInt16 nCount=GetMasterPageCount(); + if (nPos>nCount) nPos=nCount; + maMasterPages.insert(maMasterPages.begin()+nPos,pPage); + MasterPageListChanged(); + pPage->SetInserted(); + pPage->SetPageNum(nPos); + + if (nPos<nCount) { + m_bMPgNumsDirty=true; + } + + SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, pPage); + Broadcast(aHint); +} + +void SdrModel::DeleteMasterPage(sal_uInt16 nPgNum) +{ + RemoveMasterPage(nPgNum); +} + +rtl::Reference<SdrPage> SdrModel::RemoveMasterPage(sal_uInt16 nPgNum) +{ + rtl::Reference<SdrPage> pRetPg = std::move(maMasterPages[nPgNum]); + maMasterPages.erase(maMasterPages.begin()+nPgNum); + MasterPageListChanged(); + + if(pRetPg) + { + // Now delete the links from the normal drawing pages to the deleted master page. + sal_uInt16 nPageCnt(GetPageCount()); + + for(sal_uInt16 np(0); np < nPageCnt; np++) + { + GetPage(np)->TRG_ImpMasterPageRemoved(*pRetPg); + } + + pRetPg->SetInserted(false); + } + + m_bMPgNumsDirty=true; + SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, pRetPg.get()); + Broadcast(aHint); + return pRetPg; +} + +void SdrModel::MoveMasterPage(sal_uInt16 nPgNum, sal_uInt16 nNewPos) +{ + rtl::Reference<SdrPage> pPg = std::move(maMasterPages[nPgNum]); + maMasterPages.erase(maMasterPages.begin()+nPgNum); + MasterPageListChanged(); + if (pPg) { + pPg->SetInserted(false); + maMasterPages.insert(maMasterPages.begin()+nNewPos,pPg); + MasterPageListChanged(); + } + m_bMPgNumsDirty=true; + SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, pPg.get()); + Broadcast(aHint); +} + + +void SdrModel::CopyPages(sal_uInt16 nFirstPageNum, sal_uInt16 nLastPageNum, + sal_uInt16 nDestPos, + bool bUndo, bool bMoveNoCopy) +{ + if( bUndo && !IsUndoEnabled() ) + bUndo = false; + + if( bUndo ) + BegUndo(SvxResId(STR_UndoMergeModel)); + + sal_uInt16 nPageCnt=GetPageCount(); + sal_uInt16 nMaxPage=nPageCnt; + + if (nMaxPage!=0) + nMaxPage--; + if (nFirstPageNum>nMaxPage) + nFirstPageNum=nMaxPage; + if (nLastPageNum>nMaxPage) + nLastPageNum =nMaxPage; + bool bReverse=nLastPageNum<nFirstPageNum; + if (nDestPos>nPageCnt) + nDestPos=nPageCnt; + + // at first, save the pointers of the affected pages in an array + sal_uInt16 nPageNum=nFirstPageNum; + sal_uInt16 nCopyCnt=((!bReverse)?(nLastPageNum-nFirstPageNum):(nFirstPageNum-nLastPageNum))+1; + std::unique_ptr<SdrPage*[]> pPagePtrs(new SdrPage*[nCopyCnt]); + sal_uInt16 nCopyNum; + for(nCopyNum=0; nCopyNum<nCopyCnt; nCopyNum++) + { + pPagePtrs[nCopyNum]=GetPage(nPageNum); + if (bReverse) + nPageNum--; + else + nPageNum++; + } + + // now copy the pages + sal_uInt16 nDestNum=nDestPos; + for (nCopyNum=0; nCopyNum<nCopyCnt; nCopyNum++) + { + rtl::Reference<SdrPage> pPg = pPagePtrs[nCopyNum]; + sal_uInt16 nPageNum2=pPg->GetPageNum(); + if (!bMoveNoCopy) + { + const SdrPage* pPg1=GetPage(nPageNum2); + + // Clone to local model + pPg = pPg1->CloneSdrPage(*this); + + InsertPage(pPg.get(), nDestNum); + if (bUndo) + AddUndo(GetSdrUndoFactory().CreateUndoCopyPage(*pPg)); + nDestNum++; + } + else + { + // TODO: Move is untested! + if (nDestNum>nPageNum2) + nDestNum--; + + if(bUndo) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*GetPage(nPageNum2),nPageNum2,nDestNum)); + + pPg=RemovePage(nPageNum2); + InsertPage(pPg.get(), nDestNum); + nDestNum++; + } + + if(bReverse) + nPageNum2--; + else + nPageNum2++; + } + + pPagePtrs.reset(); + if(bUndo) + EndUndo(); +} + +void SdrModel::Merge(SdrModel& rSourceModel, + sal_uInt16 nFirstPageNum, sal_uInt16 nLastPageNum, + sal_uInt16 nDestPos, + bool bMergeMasterPages, bool bAllMasterPages, + bool bUndo, bool bTreadSourceAsConst) +{ + if (&rSourceModel==this) + { + CopyPages(nFirstPageNum,nLastPageNum,nDestPos,bUndo,!bTreadSourceAsConst); + return; + } + + if( bUndo && !IsUndoEnabled() ) + bUndo = false; + + if (bUndo) + BegUndo(SvxResId(STR_UndoMergeModel)); + + sal_uInt16 nSrcPageCnt=rSourceModel.GetPageCount(); + sal_uInt16 nSrcMasterPageCnt=rSourceModel.GetMasterPageCount(); + sal_uInt16 nDstMasterPageCnt=GetMasterPageCount(); + bool bInsPages=(nFirstPageNum<nSrcPageCnt || nLastPageNum<nSrcPageCnt); + sal_uInt16 nMaxSrcPage=nSrcPageCnt; if (nMaxSrcPage!=0) nMaxSrcPage--; + if (nFirstPageNum>nMaxSrcPage) nFirstPageNum=nMaxSrcPage; + if (nLastPageNum>nMaxSrcPage) nLastPageNum =nMaxSrcPage; + bool bReverse=nLastPageNum<nFirstPageNum; + + std::unique_ptr<sal_uInt16[]> pMasterMap; + std::unique_ptr<bool[]> pMasterNeed; + sal_uInt16 nMasterNeed=0; + if (bMergeMasterPages && nSrcMasterPageCnt!=0) { + // determine which MasterPages from rSrcModel we need + pMasterMap.reset(new sal_uInt16[nSrcMasterPageCnt]); + pMasterNeed.reset(new bool[nSrcMasterPageCnt]); + memset(pMasterMap.get(),0xFF,nSrcMasterPageCnt*sizeof(sal_uInt16)); + if (bAllMasterPages) { + memset(pMasterNeed.get(), true, nSrcMasterPageCnt * sizeof(bool)); + } else { + memset(pMasterNeed.get(), false, nSrcMasterPageCnt * sizeof(bool)); + sal_uInt16 nStart= bReverse ? nLastPageNum : nFirstPageNum; + sal_uInt16 nEnd= bReverse ? nFirstPageNum : nLastPageNum; + for (sal_uInt16 i=nStart; i<=nEnd; i++) { + const SdrPage* pPg=rSourceModel.GetPage(i); + if(pPg->TRG_HasMasterPage()) + { + SdrPage& rMasterPage = pPg->TRG_GetMasterPage(); + sal_uInt16 nMPgNum(rMasterPage.GetPageNum()); + + if(nMPgNum < nSrcMasterPageCnt) + { + pMasterNeed[nMPgNum] = true; + } + } + } + } + // now determine the Mapping of the MasterPages + sal_uInt16 nCurrentMaPagNum=nDstMasterPageCnt; + for (sal_uInt16 i=0; i<nSrcMasterPageCnt; i++) { + if (pMasterNeed[i]) { + pMasterMap[i]=nCurrentMaPagNum; + nCurrentMaPagNum++; + nMasterNeed++; + } + } + } + + // get the MasterPages + if (pMasterMap && pMasterNeed && nMasterNeed!=0) { + for (sal_uInt16 i=nSrcMasterPageCnt; i>0;) { + i--; + if (pMasterNeed[i]) + { + // Always Clone to new model + const SdrPage* pPg1(rSourceModel.GetMasterPage(i)); + rtl::Reference<SdrPage> pPg = pPg1->CloneSdrPage(*this); + + if(!bTreadSourceAsConst) + { + // if requested, delete original/modify original model + rSourceModel.RemoveMasterPage(i); + } + + if (pPg!=nullptr) { + // Now append all of them to the end of the DstModel. + // Don't use InsertMasterPage(), because everything is + // inconsistent until all are in. + maMasterPages.insert(maMasterPages.begin()+nDstMasterPageCnt, pPg); + MasterPageListChanged(); + pPg->SetInserted(); + m_bMPgNumsDirty=true; + if (bUndo) AddUndo(GetSdrUndoFactory().CreateUndoNewPage(*pPg)); + } else { + OSL_FAIL("SdrModel::Merge(): MasterPage not found in SourceModel."); + } + } + } + } + + // get the drawing pages + if (bInsPages) { + sal_uInt16 nSourcePos=nFirstPageNum; + sal_uInt16 nMergeCount=sal_uInt16(std::abs(static_cast<tools::Long>(static_cast<tools::Long>(nFirstPageNum)-nLastPageNum))+1); + if (nDestPos>GetPageCount()) nDestPos=GetPageCount(); + while (nMergeCount>0) + { + // Always Clone to new model + const SdrPage* pPg1(rSourceModel.GetPage(nSourcePos)); + rtl::Reference<SdrPage> pPg = pPg1->CloneSdrPage(*this); + + if(!bTreadSourceAsConst) + { + // if requested, delete original/modify original model + rSourceModel.RemovePage(nSourcePos); + } + + if (pPg!=nullptr) { + InsertPage(pPg.get(),nDestPos); + if (bUndo) AddUndo(GetSdrUndoFactory().CreateUndoNewPage(*pPg)); + + if(pPg->TRG_HasMasterPage()) + { + SdrPage& rMasterPage = pPg->TRG_GetMasterPage(); + sal_uInt16 nMaPgNum(rMasterPage.GetPageNum()); + + if (bMergeMasterPages) + { + sal_uInt16 nNewNum(0xFFFF); + + if(pMasterMap) + { + nNewNum = pMasterMap[nMaPgNum]; + } + + if(nNewNum != 0xFFFF) + { + // tdf#90357 here pPg and the to-be-set new masterpage are parts of the new model + // already, but the currently set masterpage is part of the old model. Remove master + // page from already cloned page to prevent creating wrong undo action that can + // eventually crash the app. + // Do *not* remove it directly after cloning - the old masterpage is still needed + // later to find the new to-be-set masterpage. + pPg->TRG_ClearMasterPage(); + + if(bUndo) + { + AddUndo(GetSdrUndoFactory().CreateUndoPageChangeMasterPage(*pPg)); + } + + pPg->TRG_SetMasterPage(*GetMasterPage(nNewNum)); + } + DBG_ASSERT(nNewNum!=0xFFFF,"SdrModel::Merge(): Something is crooked with the mapping of the MasterPages."); + } else { + if (nMaPgNum>=nDstMasterPageCnt) { + // This is outside of the original area of the MasterPage of the DstModel. + pPg->TRG_ClearMasterPage(); + } + } + } + + } else { + OSL_FAIL("SdrModel::Merge(): Drawing page not found in SourceModel."); + } + nDestPos++; + if (bReverse) nSourcePos--; + else if (bTreadSourceAsConst) nSourcePos++; + nMergeCount--; + } + } + + pMasterMap.reset(); + pMasterNeed.reset(); + + m_bMPgNumsDirty=true; + m_bPagNumsDirty=true; + + SetChanged(); + // TODO: Missing: merging and mapping of layers + // at the objects as well as at the MasterPageDescriptors + if (bUndo) EndUndo(); +} + +void SdrModel::SetStarDrawPreviewMode(bool bPreview) +{ + if (!bPreview && m_bStarDrawPreviewMode && GetPageCount()) + { + // Resetting is not allowed, because the Model might not be loaded completely + SAL_WARN("svx", "SdrModel::SetStarDrawPreviewMode(): Resetting not allowed, because Model might not be complete."); + } + else + { + m_bStarDrawPreviewMode = bPreview; + } +} + +void SdrModel::setTheme(std::shared_ptr<model::Theme> const& pTheme) +{ + mpImpl->mpTheme = pTheme; +} + +std::shared_ptr<model::Theme> const& SdrModel::getTheme() const +{ + return mpImpl->mpTheme; +} + +uno::Reference< frame::XModel > const & SdrModel::getUnoModel() +{ + if( !mxUnoModel.is() ) + mxUnoModel = createUnoModel(); + + return mxUnoModel; +} + +void SdrModel::setUnoModel(const uno::Reference<frame::XModel>& xModel) +{ + mxUnoModel = xModel; +} + +void SdrModel::adaptSizeAndBorderForAllPages( + const Size& /*rNewSize*/, + tools::Long /*nLeft*/, + tools::Long /*nRight*/, + tools::Long /*nUpper*/, + tools::Long /*nLower*/) +{ + // base implementation does currently nothing. It may be added if needed, + // but we are on SdrModel level here, thus probably have not enough information + // to do this for higher-level (derived) Models (e.g. Draw/Impress) +} + +uno::Reference< frame::XModel > SdrModel::createUnoModel() +{ + OSL_FAIL( "SdrModel::createUnoModel() - base implementation should not be called!" ); + return nullptr; +} + +void SdrModel::setLock( bool bLock ) +{ + if( mbModelLocked != bLock ) + { + // #i120437# need to set first, else ImpReformatAllEdgeObjects will do nothing + mbModelLocked = bLock; + + if( !bLock ) + { + ImpReformatAllEdgeObjects(); + } + } +} + + +void SdrModel::MigrateItemSet( const SfxItemSet* pSourceSet, SfxItemSet* pDestSet, SdrModel* pNewModelel ) +{ + assert(pNewModelel != nullptr); + if( !(pSourceSet && pDestSet && (pSourceSet != pDestSet )) ) + return; + + SfxWhichIter aWhichIter(*pSourceSet); + sal_uInt16 nWhich(aWhichIter.FirstWhich()); + const SfxPoolItem *pPoolItem; + + while(nWhich) + { + if(SfxItemState::SET == aWhichIter.GetItemState(false, &pPoolItem)) + { + std::unique_ptr<SfxPoolItem> pResultItem; + + switch( nWhich ) + { + case XATTR_FILLBITMAP: + pResultItem = static_cast<const XFillBitmapItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_LINEDASH: + pResultItem = static_cast<const XLineDashItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_LINESTART: + pResultItem = static_cast<const XLineStartItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_LINEEND: + pResultItem = static_cast<const XLineEndItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_FILLGRADIENT: + pResultItem = static_cast<const XFillGradientItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_FILLFLOATTRANSPARENCE: + // allow all kinds of XFillFloatTransparenceItem to be set + pResultItem = static_cast<const XFillFloatTransparenceItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + case XATTR_FILLHATCH: + pResultItem = static_cast<const XFillHatchItem*>(pPoolItem)->checkForUniqueItem( pNewModelel ); + break; + } + + // set item + if( pResultItem ) + pDestSet->Put(std::move(pResultItem)); + else + pDestSet->Put(*pPoolItem); + } + nWhich = aWhichIter.NextWhich(); + } +} + + +void SdrModel::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + mpForbiddenCharactersTable = xForbiddenChars; + + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); +} + + +void SdrModel::SetCharCompressType( CharCompressType nType ) +{ + if( nType != mnCharCompressType ) + { + mnCharCompressType = nType; + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); + } +} + +void SdrModel::SetKernAsianPunctuation( bool bEnabled ) +{ + if( mbKernAsianPunctuation != bEnabled ) + { + mbKernAsianPunctuation = bEnabled; + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); + } +} + +void SdrModel::SetAddExtLeading( bool bEnabled ) +{ + if( mbAddExtLeading != bEnabled ) + { + mbAddExtLeading = bEnabled; + ImpSetOutlinerDefaults( m_pDrawOutliner.get() ); + ImpSetOutlinerDefaults( m_pHitTestOutliner.get() ); + } +} + +void SdrModel::SetCompatibilityFlag(SdrCompatibilityFlag eFlag, bool bEnabled) +{ + switch (eFlag) + { + case SdrCompatibilityFlag::AnchoredTextOverflowLegacy: + mpImpl->mbAnchoredTextOverflowLegacy = bEnabled; + break; + case SdrCompatibilityFlag::LegacyFontwork: + mpImpl->mbLegacyFontwork = bEnabled; + break; + case SdrCompatibilityFlag::ConnectorUseSnapRect: + mpImpl->mbConnectorUseSnapRect = bEnabled; + break; + case SdrCompatibilityFlag::IgnoreBreakAfterMultilineField: + mpImpl->mbIgnoreBreakAfterMultilineField = bEnabled; + break; + } +} + +bool SdrModel::GetCompatibilityFlag(SdrCompatibilityFlag eFlag) const +{ + switch (eFlag) + { + case SdrCompatibilityFlag::AnchoredTextOverflowLegacy: + return mpImpl->mbAnchoredTextOverflowLegacy; + case SdrCompatibilityFlag::LegacyFontwork: + return mpImpl->mbLegacyFontwork; + case SdrCompatibilityFlag::ConnectorUseSnapRect: + return mpImpl->mbConnectorUseSnapRect; + case SdrCompatibilityFlag::IgnoreBreakAfterMultilineField: + return mpImpl->mbIgnoreBreakAfterMultilineField; + default: + return false; + } +} + +void SdrModel::ReformatAllTextObjects() +{ + ImpReformatAllTextObjects(); +} + +std::unique_ptr<SdrOutliner> SdrModel::createOutliner( OutlinerMode nOutlinerMode ) +{ + if( !mpOutlinerCache ) + mpOutlinerCache.reset(new SdrOutlinerCache(this)); + + return mpOutlinerCache->createOutliner( nOutlinerMode ); +} + +std::vector<SdrOutliner*> SdrModel::GetActiveOutliners() const +{ + std::vector< SdrOutliner* > aRet(mpOutlinerCache ? mpOutlinerCache->GetActiveOutliners() : std::vector< SdrOutliner* >()); + aRet.push_back(m_pDrawOutliner.get()); + aRet.push_back(m_pHitTestOutliner.get()); + + return aRet; +} + +void SdrModel::disposeOutliner( std::unique_ptr<SdrOutliner> pOutliner ) +{ + if( mpOutlinerCache ) + mpOutlinerCache->disposeOutliner( std::move(pOutliner) ); +} + +SvxNumType SdrModel::GetPageNumType() const +{ + return SVX_NUM_ARABIC; +} + +void SdrModel::ReadUserDataSequenceValue(const beans::PropertyValue* pValue) +{ + if (pValue->Name == "AnchoredTextOverflowLegacy") + { + bool bBool = false; + if (pValue->Value >>= bBool) + { + mpImpl->mbAnchoredTextOverflowLegacy = bBool; + } + } + else if (pValue->Name == "ConnectorUseSnapRect") + { + bool bBool = false; + if (pValue->Value >>= bBool) + { + mpImpl->mbConnectorUseSnapRect = bBool; + } + } + else if (pValue->Name == "LegacySingleLineFontwork") + { + bool bBool = false; + if ((pValue->Value >>= bBool) && mpImpl->mbLegacyFontwork != bBool) + { + mpImpl->mbLegacyFontwork = bBool; + // tdf#148000 hack: reset all CustomShape geometry as they may depend on this property + // Ideally this ReadUserDataSequenceValue should be called before geometry creation + // Once the calling order will be fixed, this hack will not be needed. + for (size_t i = 0; i < maPages.size(); ++i) + { + if (const SdrPage* pPage = maPages[i].get()) + { + SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups); + while (aIter.IsMore()) + { + SdrObject* pTempObj = aIter.Next(); + if (SdrObjCustomShape* pShape = dynamic_cast<SdrObjCustomShape*>(pTempObj)) + { + pShape->InvalidateRenderGeometry(); + } + } + } + } + } + } + else if (pValue->Name == "IgnoreBreakAfterMultilineField") + { + bool bBool = false; + if (pValue->Value >>= bBool) + { + mpImpl->mbIgnoreBreakAfterMultilineField = bBool; + } + } +} + +void SdrModel::WriteUserDataSequence(uno::Sequence <beans::PropertyValue>& rValues) +{ + std::vector< std::pair< OUString, uno::Any > > aUserData + { + { "AnchoredTextOverflowLegacy", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::AnchoredTextOverflowLegacy)) }, + { "LegacySingleLineFontwork", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::LegacyFontwork)) }, + { "ConnectorUseSnapRect", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect)) }, + { "IgnoreBreakAfterMultilineField", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField)) } + }; + + const sal_Int32 nOldLength = rValues.getLength(); + rValues.realloc(nOldLength + aUserData.size()); + + beans::PropertyValue* pValue = &(rValues.getArray()[nOldLength]); + + for (const auto &aIter : aUserData) + { + pValue->Name = aIter.first; + pValue->Value = aIter.second; + ++pValue; + } +} + +const SdrPage* SdrModel::GetPage(sal_uInt16 nPgNum) const +{ + return nPgNum < maPages.size() ? maPages[nPgNum].get() : nullptr; +} + +SdrPage* SdrModel::GetPage(sal_uInt16 nPgNum) +{ + return nPgNum < maPages.size() ? maPages[nPgNum].get() : nullptr; +} + +sal_uInt16 SdrModel::GetPageCount() const +{ + return sal_uInt16(maPages.size()); +} + +void SdrModel::PageListChanged() +{ +} + +TextChain *SdrModel::GetTextChain() const +{ + return m_pTextChain.get(); +} + +const SdrPage* SdrModel::GetMasterPage(sal_uInt16 nPgNum) const +{ + DBG_ASSERT(nPgNum < maMasterPages.size(), "SdrModel::GetMasterPage: Access out of range (!)"); + return maMasterPages[nPgNum].get(); +} + +SdrPage* SdrModel::GetMasterPage(sal_uInt16 nPgNum) +{ + DBG_ASSERT(nPgNum < maMasterPages.size(), "SdrModel::GetMasterPage: Access out of range (!)"); + return maMasterPages[nPgNum].get(); +} + +sal_uInt16 SdrModel::GetMasterPageCount() const +{ + return sal_uInt16(maMasterPages.size()); +} + +void SdrModel::MasterPageListChanged() +{ +} + +void SdrModel::SetSdrUndoManager( SfxUndoManager* pUndoManager ) +{ + mpImpl->mpUndoManager = pUndoManager; +} + +SfxUndoManager* SdrModel::GetSdrUndoManager() const +{ + return mpImpl->mpUndoManager; +} + +SdrUndoFactory& SdrModel::GetSdrUndoFactory() const +{ + if( !mpImpl->mpUndoFactory ) + mpImpl->mpUndoFactory = new SdrUndoFactory; + return *mpImpl->mpUndoFactory; +} + +void SdrModel::SetSdrUndoFactory( SdrUndoFactory* pUndoFactory ) +{ + if( pUndoFactory && (pUndoFactory != mpImpl->mpUndoFactory) ) + { + delete mpImpl->mpUndoFactory; + mpImpl->mpUndoFactory = pUndoFactory; + } +} + +void SdrModel::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrModel")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("maMasterPages")); + for (size_t i = 0; i < maMasterPages.size(); ++i) + { + if (const SdrPage* pPage = maMasterPages[i].get()) + { + pPage->dumpAsXml(pWriter); + } + } + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("maPages")); + for (size_t i = 0; i < maPages.size(); ++i) + { + if (const SdrPage* pPage = maPages[i].get()) + { + pPage->dumpAsXml(pWriter); + } + } + (void)xmlTextWriterEndElement(pWriter); + + if (mpImpl->mpTheme) + { + mpImpl->mpTheme->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +const uno::Sequence<sal_Int8>& SdrModel::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theSdrModelUnoTunnelImplementationId; + return theSdrModelUnoTunnelImplementationId.getSeq(); +} + + +SdrHint::SdrHint(SdrHintKind eNewHint) +: SfxHint(SfxHintId::ThisIsAnSdrHint), + meHint(eNewHint), + mpObj(nullptr), + mpPage(nullptr) +{ +} + +SdrHint::SdrHint(SdrHintKind eNewHint, const SdrObject& rNewObj) +: SfxHint(SfxHintId::ThisIsAnSdrHint), + meHint(eNewHint), + mpObj(&rNewObj), + mpPage(rNewObj.getSdrPageFromSdrObject()) +{ +} + +SdrHint::SdrHint(SdrHintKind eNewHint, const SdrPage* pPage) +: SfxHint(SfxHintId::ThisIsAnSdrHint), + meHint(eNewHint), + mpObj(nullptr), + mpPage(pPage) +{ +} + +SdrHint::SdrHint(SdrHintKind eNewHint, const SdrObject& rNewObj, const SdrPage* pPage) +: SfxHint(SfxHintId::ThisIsAnSdrHint), + meHint(eNewHint), + mpObj(&rNewObj), + mpPage(pPage) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdmrkv.cxx b/svx/source/svdraw/svdmrkv.cxx new file mode 100644 index 0000000000..b0784449f1 --- /dev/null +++ b/svx/source/svdraw/svdmrkv.cxx @@ -0,0 +1,2735 @@ +/* -*- 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/svdmrkv.hxx> +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdomedia.hxx> + +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <rtl/strbuf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflgrit.hxx> +#include "gradtrns.hxx" +#include <svx/xflftrit.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdundo.hxx> +#include <svx/svdopath.hxx> +#include <svx/scene3d.hxx> +#include <svx/svdovirt.hxx> +#include <sdr/overlay/overlayrollingrectangle.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdr/overlay/overlayselection.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrhittesthelper.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <vcl/window.hxx> +#include <o3tl/string_view.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/lokcomponenthelpers.hxx> +#include <sfx2/viewsh.hxx> +#include <svtools/optionsdrawinglayer.hxx> + +#include <array> + +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +#include <boost/property_tree/json_parser.hpp> + +using namespace com::sun::star; + +// Migrate Marking of Objects, Points and GluePoints + +class ImplMarkingOverlay +{ + // The OverlayObjects + sdr::overlay::OverlayObjectList maObjects; + + // The remembered second position in logical coordinates + basegfx::B2DPoint maSecondPosition; + + // A flag to remember if the action is for unmarking. + bool mbUnmarking : 1; + +public: + ImplMarkingOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, bool bUnmarking); + + // The OverlayObjects are cleared using the destructor of OverlayObjectList. + // That destructor calls clear() at the list which removes all objects from the + // OverlayManager and deletes them. + + void SetSecondPosition(const basegfx::B2DPoint& rNewPosition); + bool IsUnmarking() const { return mbUnmarking; } +}; + +ImplMarkingOverlay::ImplMarkingOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, bool bUnmarking) +: maSecondPosition(rStartPos), + mbUnmarking(bUnmarking) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; // We do client-side object manipulation with the Kit API + + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + std::unique_ptr<sdr::overlay::OverlayRollingRectangleStriped> pNew(new sdr::overlay::OverlayRollingRectangleStriped( + rStartPos, rStartPos, false)); + xTargetOverlay->add(*pNew); + maObjects.append(std::move(pNew)); + } + } +} + +void ImplMarkingOverlay::SetSecondPosition(const basegfx::B2DPoint& rNewPosition) +{ + if(rNewPosition != maSecondPosition) + { + // apply to OverlayObjects + for(sal_uInt32 a(0); a < maObjects.count(); a++) + { + sdr::overlay::OverlayRollingRectangleStriped& rCandidate = static_cast< sdr::overlay::OverlayRollingRectangleStriped&>(maObjects.getOverlayObject(a)); + rCandidate.setSecondPosition(rNewPosition); + } + + // remember new position + maSecondPosition = rNewPosition; + } +} + +class MarkingSubSelectionOverlay +{ + sdr::overlay::OverlayObjectList maObjects; + +public: + MarkingSubSelectionOverlay(const SdrPaintView& rView, std::vector<basegfx::B2DRectangle> const & rSelections) + { + if (comphelper::LibreOfficeKit::isActive()) + return; // We do client-side object manipulation with the Kit API + + for (sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + const Color aHighlightColor = SvtOptionsDrawinglayer::getHilightColor(); + + std::unique_ptr<sdr::overlay::OverlaySelection> pNew = + std::make_unique<sdr::overlay::OverlaySelection>( + sdr::overlay::OverlayType::Transparent, + aHighlightColor, std::vector(rSelections), false); + + xTargetOverlay->add(*pNew); + maObjects.append(std::move(pNew)); + } + } + } +}; + +SdrMarkView::SdrMarkView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrSnapView(rSdrModel, pOut) + , mpMarkedObj(nullptr) + , mpMarkedPV(nullptr) + , maHdlList(this) + , meDragMode(SdrDragMode::Move) + , meEditMode(SdrViewEditMode::Edit) + , meEditMode0(SdrViewEditMode::Edit) + , mbDesignMode(false) + , mbForceFrameHandles(false) + , mbPlusHdlAlways(false) + , mbInsPolyPoint(false) + , mbMarkedObjRectDirty(false) + , mbMrkPntDirty(false) + , mbMarkedPointsRectsDirty(false) + , mbMarkHandlesHidden(false) + , mbNegativeX(false) +{ + + BrkMarkObj(); + BrkMarkPoints(); + BrkMarkGluePoints(); + + StartListening(rSdrModel); +} + +SdrMarkView::~SdrMarkView() +{ + // Migrate selections + BrkMarkObj(); + BrkMarkPoints(); + BrkMarkGluePoints(); +} + +void SdrMarkView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + SdrHintKind eKind=pSdrHint->GetKind(); + if (eKind==SdrHintKind::ObjectChange || eKind==SdrHintKind::ObjectInserted || eKind==SdrHintKind::ObjectRemoved) + { + mbMarkedObjRectDirty=true; + mbMarkedPointsRectsDirty=true; + } + } + SdrSnapView::Notify(rBC,rHint); +} + +void SdrMarkView::ModelHasChanged() +{ + SdrPaintView::ModelHasChanged(); + GetMarkedObjectListWriteAccess().SetNameDirty(); + mbMarkedObjRectDirty=true; + mbMarkedPointsRectsDirty=true; + // Example: Obj is selected and maMarkedObjectList is sorted. + // In another View 2, the ObjOrder is changed (e. g. MovToTop()) + // Then we need to re-sort MarkList. + GetMarkedObjectListWriteAccess().SetUnsorted(); + SortMarkedObjects(); + mbMrkPntDirty=true; + UndirtyMrkPnt(); + SdrView* pV=static_cast<SdrView*>(this); + if (pV!=nullptr && !pV->IsDragObj() && !pV->IsInsObjPoint()) { + AdjustMarkHdl(); + } + + if (comphelper::LibreOfficeKit::isActive()) + modelHasChangedLOKit(); +} + +void SdrMarkView::modelHasChangedLOKit() +{ + if (GetMarkedObjectCount() <= 0) + return; + + //TODO: Is MarkedObjRect valid at this point? + tools::Rectangle aSelection(GetMarkedObjRect()); + tools::Rectangle* pResultSelection; + if (aSelection.IsEmpty()) + pResultSelection = nullptr; + else + { + sal_uInt32 nTotalPaintWindows = this->PaintWindowCount(); + if (nTotalPaintWindows == 1) + { + const OutputDevice* pOut = this->GetFirstOutputDevice(); + const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr; + if (pWin && pWin->IsChart()) + { + const vcl::Window* pViewShellWindow = GetSfxViewShell()->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin)) + { + Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pWin->PixelToLogic(aOffsetPx); + aSelection.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + } + + // In case the map mode is in 100th MM, then need to convert the coordinates over to twips for LOK. + if (mpMarkedPV) + { + if (OutputDevice* pOutputDevice = mpMarkedPV->GetView().GetFirstOutputDevice()) + { + if (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip); + } + } + + pResultSelection = &aSelection; + + if (mbNegativeX) + { + // Convert to positive X doc-coordinates + tools::Long nTmp = aSelection.Left(); + aSelection.SetLeft(-aSelection.Right()); + aSelection.SetRight(-nTmp); + } + } + + if (SfxViewShell* pViewShell = GetSfxViewShell()) + SfxLokHelper::notifyInvalidation(pViewShell, pResultSelection); +} + +bool SdrMarkView::IsAction() const +{ + return SdrSnapView::IsAction() || IsMarkObj() || IsMarkPoints() || IsMarkGluePoints(); +} + +void SdrMarkView::MovAction(const Point& rPnt) +{ + SdrSnapView::MovAction(rPnt); + + if(IsMarkObj()) + { + MovMarkObj(rPnt); + } + else if(IsMarkPoints()) + { + MovMarkPoints(rPnt); + } + else if(IsMarkGluePoints()) + { + MovMarkGluePoints(rPnt); + } +} + +void SdrMarkView::EndAction() +{ + if(IsMarkObj()) + { + EndMarkObj(); + } + else if(IsMarkPoints()) + { + EndMarkPoints(); + } + else if(IsMarkGluePoints()) + { + EndMarkGluePoints(); + } + + SdrSnapView::EndAction(); +} + +void SdrMarkView::BckAction() +{ + SdrSnapView::BckAction(); + BrkMarkObj(); + BrkMarkPoints(); + BrkMarkGluePoints(); +} + +void SdrMarkView::BrkAction() +{ + SdrSnapView::BrkAction(); + BrkMarkObj(); + BrkMarkPoints(); + BrkMarkGluePoints(); +} + +void SdrMarkView::TakeActionRect(tools::Rectangle& rRect) const +{ + if(IsMarkObj() || IsMarkPoints() || IsMarkGluePoints()) + { + rRect = tools::Rectangle(maDragStat.GetStart(), maDragStat.GetNow()); + } + else + { + SdrSnapView::TakeActionRect(rRect); + } +} + + +void SdrMarkView::ClearPageView() +{ + UnmarkAllObj(); + SdrSnapView::ClearPageView(); +} + +void SdrMarkView::HideSdrPage() +{ + bool bMrkChg(false); + + SdrPageView* pPageView = GetSdrPageView(); + if (pPageView) + { + // break all creation actions when hiding page (#75081#) + BrkAction(); + + // Discard all selections on this page + bMrkChg = GetMarkedObjectListWriteAccess().DeletePageView(*pPageView); + } + + SdrSnapView::HideSdrPage(); + + if(bMrkChg) + { + MarkListHasChanged(); + AdjustMarkHdl(); + } +} + + +void SdrMarkView::BegMarkObj(const Point& rPnt, bool bUnmark) +{ + BrkAction(); + + DBG_ASSERT(!mpMarkObjOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkObjOverlay (!)"); + + basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y()); + mpMarkObjOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark)); + + maDragStat.Reset(rPnt); + maDragStat.NextPoint(); + maDragStat.SetMinMove(mnMinMovLog); +} + +void SdrMarkView::MovMarkObj(const Point& rPnt) +{ + if(IsMarkObj() && maDragStat.CheckMinMoved(rPnt)) + { + maDragStat.NextMove(rPnt); + DBG_ASSERT(mpMarkObjOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y()); + mpMarkObjOverlay->SetSecondPosition(aNewPos); + } +} + +bool SdrMarkView::EndMarkObj() +{ + bool bRetval(false); + + if(IsMarkObj()) + { + if(maDragStat.IsMinMoved()) + { + tools::Rectangle aRect(maDragStat.GetStart(), maDragStat.GetNow()); + aRect.Normalize(); + MarkObj(aRect, mpMarkObjOverlay->IsUnmarking()); + bRetval = true; + } + + // cleanup + BrkMarkObj(); + } + + return bRetval; +} + +void SdrMarkView::BrkMarkObj() +{ + if(IsMarkObj()) + { + DBG_ASSERT(mpMarkObjOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + mpMarkObjOverlay.reset(); + } +} + + +bool SdrMarkView::BegMarkPoints(const Point& rPnt, bool bUnmark) +{ + if(HasMarkablePoints()) + { + BrkAction(); + + DBG_ASSERT(!mpMarkPointsOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkPointsOverlay (!)"); + basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y()); + mpMarkPointsOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark)); + + maDragStat.Reset(rPnt); + maDragStat.NextPoint(); + maDragStat.SetMinMove(mnMinMovLog); + + return true; + } + + return false; +} + +void SdrMarkView::MovMarkPoints(const Point& rPnt) +{ + if(IsMarkPoints() && maDragStat.CheckMinMoved(rPnt)) + { + maDragStat.NextMove(rPnt); + + DBG_ASSERT(mpMarkPointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y()); + mpMarkPointsOverlay->SetSecondPosition(aNewPos); + } +} + +bool SdrMarkView::EndMarkPoints() +{ + bool bRetval(false); + + if(IsMarkPoints()) + { + if(maDragStat.IsMinMoved()) + { + tools::Rectangle aRect(maDragStat.GetStart(), maDragStat.GetNow()); + aRect.Normalize(); + MarkPoints(&aRect, mpMarkPointsOverlay->IsUnmarking()); + + bRetval = true; + } + + // cleanup + BrkMarkPoints(); + } + + return bRetval; +} + +void SdrMarkView::BrkMarkPoints() +{ + if(IsMarkPoints()) + { + DBG_ASSERT(mpMarkPointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + mpMarkPointsOverlay.reset(); + } +} + + +bool SdrMarkView::BegMarkGluePoints(const Point& rPnt, bool bUnmark) +{ + if(HasMarkableGluePoints()) + { + BrkAction(); + + DBG_ASSERT(!mpMarkGluePointsOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkGluePointsOverlay (!)"); + + basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y()); + mpMarkGluePointsOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark)); + maDragStat.Reset(rPnt); + maDragStat.NextPoint(); + maDragStat.SetMinMove(mnMinMovLog); + + return true; + } + + return false; +} + +void SdrMarkView::MovMarkGluePoints(const Point& rPnt) +{ + if(IsMarkGluePoints() && maDragStat.CheckMinMoved(rPnt)) + { + maDragStat.NextMove(rPnt); + + DBG_ASSERT(mpMarkGluePointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y()); + mpMarkGluePointsOverlay->SetSecondPosition(aNewPos); + } +} + +void SdrMarkView::EndMarkGluePoints() +{ + if(IsMarkGluePoints()) + { + if(maDragStat.IsMinMoved()) + { + tools::Rectangle aRect(maDragStat.GetStart(),maDragStat.GetNow()); + aRect.Normalize(); + MarkGluePoints(&aRect, mpMarkGluePointsOverlay->IsUnmarking()); + } + + // cleanup + BrkMarkGluePoints(); + } +} + +void SdrMarkView::BrkMarkGluePoints() +{ + if(IsMarkGluePoints()) + { + DBG_ASSERT(mpMarkGluePointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + mpMarkGluePointsOverlay.reset(); + } +} + +bool SdrMarkView::MarkableObjectsExceed( int n ) const +{ + SdrPageView* pPV = GetSdrPageView(); + if (!pPV) + return false; + + SdrObjList* pOL=pPV->GetObjList(); + for (const rtl::Reference<SdrObject>& pObj : *pOL) + if (IsObjMarkable(pObj.get(),pPV) && --n<0) + return true; + + return false; +} + +void SdrMarkView::hideMarkHandles() +{ + if(!mbMarkHandlesHidden) + { + mbMarkHandlesHidden = true; + AdjustMarkHdl(); + } +} + +void SdrMarkView::showMarkHandles() +{ + if(mbMarkHandlesHidden) + { + mbMarkHandlesHidden = false; + AdjustMarkHdl(); + } +} + +bool SdrMarkView::ImpIsFrameHandles() const +{ + const size_t nMarkCount=GetMarkedObjectCount(); + bool bFrmHdl=nMarkCount>static_cast<size_t>(mnFrameHandlesLimit) || mbForceFrameHandles; + bool bStdDrag=meDragMode==SdrDragMode::Move; + if (nMarkCount==1 && bStdDrag && bFrmHdl) + { + const SdrObject* pObj=GetMarkedObjectByIndex(0); + if (pObj && pObj->GetObjInventor()==SdrInventor::Default) + { + SdrObjKind nIdent=pObj->GetObjIdentifier(); + if (nIdent==SdrObjKind::Line || nIdent==SdrObjKind::Edge || nIdent==SdrObjKind::Caption || nIdent==SdrObjKind::Measure || nIdent==SdrObjKind::CustomShape || nIdent==SdrObjKind::Table ) + { + bFrmHdl=false; + } + } + } + if (!bStdDrag && !bFrmHdl) { + // all other drag modes only with FrameHandles + bFrmHdl=true; + if (meDragMode==SdrDragMode::Rotate) { + // when rotating, use ObjOwn drag, if there's at least 1 PolyObj + for (size_t nMarkNum=0; nMarkNum<nMarkCount && bFrmHdl; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + bFrmHdl=!pObj->IsPolyObj(); + } + } + } + if (!bFrmHdl) { + // FrameHandles, if at least 1 Obj can't do SpecialDrag + for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bFrmHdl; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + bFrmHdl=!pObj->hasSpecialDrag(); + } + } + + // no FrameHdl for crop + if(bFrmHdl && SdrDragMode::Crop == meDragMode) + { + bFrmHdl = false; + } + + return bFrmHdl; +} + +namespace +{ +std::u16string_view lcl_getDragMethodServiceName( std::u16string_view rCID ) +{ + std::u16string_view aRet; + + size_t nIndexStart = rCID.find( u"DragMethod=" ); + if( nIndexStart != std::u16string_view::npos ) + { + nIndexStart = rCID.find( '=', nIndexStart ); + if( nIndexStart != std::u16string_view::npos ) + { + nIndexStart++; + size_t nNextSlash = rCID.find( '/', nIndexStart ); + if( nNextSlash != std::u16string_view::npos ) + { + sal_Int32 nIndexEnd = nNextSlash; + size_t nNextColon = rCID.find( ':', nIndexStart ); + if( nNextColon == std::u16string_view::npos || nNextColon < nNextSlash ) + nIndexEnd = nNextColon; + aRet = rCID.substr(nIndexStart,nIndexEnd-nIndexStart); + } + } + } + return aRet; +} + +std::u16string_view lcl_getDragParameterString( std::u16string_view rCID ) +{ + std::u16string_view aRet; + + size_t nIndexStart = rCID.find( u"DragParameter=" ); + if( nIndexStart != std::u16string_view::npos ) + { + nIndexStart = rCID.find( '=', nIndexStart ); + if( nIndexStart != std::u16string_view::npos ) + { + nIndexStart++; + size_t nNextSlash = rCID.find( '/', nIndexStart ); + if( nNextSlash != std::u16string_view::npos ) + { + sal_Int32 nIndexEnd = nNextSlash; + size_t nNextColon = rCID.find( ':', nIndexStart ); + if( nNextColon == std::u16string_view::npos || nNextColon < nNextSlash ) + nIndexEnd = nNextColon; + aRet = rCID.substr(nIndexStart,nIndexEnd-nIndexStart); + } + } + } + return aRet; +} +} // anonymous namespace + +bool SdrMarkView::dumpGluePointsToJSON(boost::property_tree::ptree& rTree) +{ + bool result = false; + tools::Long nSignX = mbNegativeX ? -1 : 1; + if (OutputDevice* pOutDev = mpMarkedPV ? mpMarkedPV->GetView().GetFirstOutputDevice() : nullptr) + { + bool bConvertUnit = false; + if (pOutDev->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + bConvertUnit = true; + const SdrObjList* pOL = mpMarkedPV->GetObjList(); + if (!pOL) + return false; + boost::property_tree::ptree elements; + for (const rtl::Reference<SdrObject>& pObj : *pOL) + { + if (!pObj) + continue; + if (pObj == GetMarkedObjectByIndex(0)) + continue; + const SdrGluePointList* pGPL = pObj->GetGluePointList(); + bool VertexObject = !(pGPL && pGPL->GetCount()); + const size_t count = !VertexObject ? pGPL->GetCount() : 4; + boost::property_tree::ptree object; + boost::property_tree::ptree points; + for (size_t i = 0; i < count; ++i) + { + boost::property_tree::ptree node; + boost::property_tree::ptree point; + const SdrGluePoint& rGP = !VertexObject ? (*pGPL)[i] : pObj->GetVertexGluePoint(i); + Point rPoint = rGP.GetAbsolutePos(*pObj); + if (bConvertUnit) + { + rPoint = o3tl::convert(rPoint, o3tl::Length::mm100, o3tl::Length::twip); + } + point.put("x", nSignX * rPoint.getX()); + point.put("y", rPoint.getY()); + node.add_child("point", point); + points.push_back(std::make_pair("", node)); + } + basegfx::B2DVector aGridOffset(0.0, 0.0); + Point objLogicRectTopLeft = pObj->GetLogicRect().TopLeft(); + if(getPossibleGridOffsetForPosition(aGridOffset, basegfx::B2DPoint(objLogicRectTopLeft.X(), objLogicRectTopLeft.Y()), GetSdrPageView())) + { + Point p(aGridOffset.getX(), aGridOffset.getY()); + if (bConvertUnit) + { + p = o3tl::convert(p, o3tl::Length::mm100, o3tl::Length::twip); + } + boost::property_tree::ptree gridOffset; + gridOffset.put("x", nSignX * p.getX()); + gridOffset.put("y", p.getY()); + object.add_child("gridoffset", gridOffset); + } + object.put("ordnum", pObj->GetOrdNum()); + object.add_child("gluepoints", points); + elements.push_back(std::make_pair("", object)); + result = true; + } + rTree.add_child("shapes", elements); + } + return result; +} + +void SdrMarkView::SetMarkHandlesForLOKit(tools::Rectangle const & rRect, const SfxViewShell* pOtherShell) +{ + SfxViewShell* pViewShell = GetSfxViewShell(); + + tools::Rectangle aSelection(rRect); + tools::Long nSignX = mbNegativeX ? -1 : 1; + bool bIsChart = false; + Point addLogicOffset(0, 0); + bool convertMapMode = false; + if (!rRect.IsEmpty()) + { + sal_uInt32 nTotalPaintWindows = this->PaintWindowCount(); + if (nTotalPaintWindows == 1) + { + const OutputDevice* pOut = this->GetFirstOutputDevice(); + const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr; + if (pWin && pWin->IsChart()) + { + bIsChart = true; + const vcl::Window* pViewShellWindow = GetSfxViewShell()->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin)) + { + Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow); + if (mbNegativeX && AllSettings::GetLayoutRTL()) + { + // mbNegativeX is set only for Calc in RTL mode. + // If global RTL flag is set, vcl-window X offset of chart window is + // mirrored w.r.t parent window rectangle. This needs to be reverted. + aOffsetPx.setX(pViewShellWindow->GetOutOffXPixel() + pViewShellWindow->GetSizePixel().Width() + - pWin->GetOutOffXPixel() - pWin->GetSizePixel().Width()); + } + Point aLogicOffset = pWin->PixelToLogic(aOffsetPx); + addLogicOffset = aLogicOffset; + aSelection.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + } + } + + if (!aSelection.IsEmpty()) + { + // In case the map mode is in 100th MM, then need to convert the coordinates over to twips for LOK. + if (mpMarkedPV) + { + if (OutputDevice* pOutputDevice = mpMarkedPV->GetView().GetFirstOutputDevice()) + { + if (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip); + convertMapMode = true; + } + } + } + + // hide the text selection too + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); + } + + { + OStringBuffer aExtraInfo; + OString sSelectionText; + OString sSelectionTextView; + boost::property_tree::ptree aTableJsonTree; + boost::property_tree::ptree aGluePointsTree; + const bool bMediaObj = (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Media); + bool bTableSelection = false; + bool bConnectorSelection = false; + + if (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Table) + { + auto& rTableObject = dynamic_cast<sdr::table::SdrTableObj&>(*mpMarkedObj); + bTableSelection = rTableObject.createTableEdgesJson(aTableJsonTree); + } + if (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Edge) + { + bConnectorSelection = dumpGluePointsToJSON(aGluePointsTree); + } + + if (GetMarkedObjectCount()) + { + SdrMark* pM = GetSdrMarkByIndex(0); + SdrObject* pO = pM->GetMarkedSdrObj(); + Degree100 nRotAngle = pO->GetRotateAngle(); + // true if we are dealing with a RotGrfFlyFrame + // (SwVirtFlyDrawObj with a SwGrfNode) + bool bWriterGraphic = pO->HasLimitedRotation(); + + OString handleArrayStr; + + aExtraInfo.append("{\"id\":\"" + + OString::number(reinterpret_cast<sal_IntPtr>(pO)) + + "\",\"type\":" + + OString::number(static_cast<sal_Int32>(pO->GetObjIdentifier()))); + + // In core, the gridOffset is calculated based on the LogicRect's TopLeft coordinate + // In online, we have the SnapRect and we calculate it based on its TopLeft coordinate + // SnapRect's TopLeft and LogicRect's TopLeft match unless there is rotation + // but the rotation is not applied to the LogicRect. Therefore, + // what we calculate in online does not match with the core in case of the rotation. + // Here we can send the correct gridOffset in the selection callback, this way + // whether the shape is rotated or not, we will always have the correct gridOffset + // Note that the gridOffset is calculated from the first selected obj + basegfx::B2DVector aGridOffset(0.0, 0.0); + if(getPossibleGridOffsetForSdrObject(aGridOffset, GetMarkedObjectByIndex(0), GetSdrPageView())) + { + Point p(aGridOffset.getX(), aGridOffset.getY()); + if (convertMapMode) + p = o3tl::convert(p, o3tl::Length::mm100, o3tl::Length::twip); + aExtraInfo.append(",\"gridOffsetX\":" + + OString::number(nSignX * p.getX()) + + ",\"gridOffsetY\":" + + OString::number(p.getY())); + } + + if (bWriterGraphic) + { + aExtraInfo.append(", \"isWriterGraphic\": true"); + } + else if (bIsChart) + { + LokChartHelper aChartHelper(pViewShell); + css::uno::Reference<css::frame::XController>& xChartController = aChartHelper.GetXController(); + css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier( xChartController, uno::UNO_QUERY); + if (xSelectionSupplier.is()) + { + uno::Any aSel = xSelectionSupplier->getSelection(); + OUString aValue; + if (aSel >>= aValue) + { + OString aObjectCID(aValue.getStr(), aValue.getLength(), osl_getThreadTextEncoding()); + const std::vector<OString> aProps{"Draggable"_ostr, "Resizable"_ostr, "Rotatable"_ostr}; + for (const auto& rProp: aProps) + { + sal_Int32 nPos = aObjectCID.indexOf(rProp); + if (nPos == -1) continue; + nPos += rProp.getLength() + 1; // '=' + if (aExtraInfo.getLength() > 2) // != "{ " + aExtraInfo.append(", "); + aExtraInfo.append("\"is" + rProp + "\": " + + OString::boolean(aObjectCID[nPos] == '1')); + } + + std::u16string_view sDragMethod = lcl_getDragMethodServiceName(aValue); + if (sDragMethod == u"PieSegmentDragging") + { + // old initial offset inside the CID returned by xSelectionSupplier->getSelection() + // after a pie segment dragging; using SdrObject::GetName for getting a CID with the updated offset + aValue = pO->GetName(); + std::u16string_view sDragParameters = lcl_getDragParameterString(aValue); + if (!sDragParameters.empty()) + { + aExtraInfo.append(", \"dragInfo\": { " + "\"dragMethod\": \"" + + OUString(sDragMethod).toUtf8() + + "\""); + + sal_Int32 nStartIndex = 0; + std::array<int, 5> aDragParameters; + for (auto& rParam : aDragParameters) + { + std::u16string_view sParam = o3tl::getToken(sDragParameters, 0, ',', nStartIndex); + if (sParam.empty()) + break; + rParam = o3tl::toInt32(sParam); + } + + // initial offset in % + if (aDragParameters[0] < 0) + aDragParameters[0] = 0; + else if (aDragParameters[0] > 100) + aDragParameters[0] = 100; + + aExtraInfo.append(", \"initialOffset\": " + + OString::number(static_cast<sal_Int32>(aDragParameters[0]))); + + // drag direction constraint + Point aMinPos(aDragParameters[1], aDragParameters[2]); + Point aMaxPos(aDragParameters[3], aDragParameters[4]); + Point aDragDirection = aMaxPos - aMinPos; + aDragDirection = o3tl::convert(aDragDirection, o3tl::Length::mm100, o3tl::Length::twip); + + aExtraInfo.append(", \"dragDirection\": [" + + aDragDirection.toString() + + "]"); + + // polygon approximating the pie segment or donut segment + if (pO->GetObjIdentifier() == SdrObjKind::PathFill) + { + const basegfx::B2DPolyPolygon aPolyPolygon(pO->TakeXorPoly()); + if (aPolyPolygon.count() == 1) + { + const basegfx::B2DPolygon aPolygon = aPolyPolygon.getB2DPolygon(0); + if (sal_uInt32 nPolySize = aPolygon.count()) + { + const OutputDevice* pOut = this->GetFirstOutputDevice(); + const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr; + const vcl::Window* pViewShellWindow = pViewShell->GetEditWindowForActiveOLEObj(); + if (pWin && pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin)) + { + // in the following code escaping sequences used inside raw literal strings + // are for making them understandable by the JSON parser + + Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pWin->PixelToLogic(aOffsetPx); + OString sPolygonElem("<polygon points=\\\""_ostr); + for (sal_uInt32 nIndex = 0; nIndex < nPolySize; ++nIndex) + { + const basegfx::B2DPoint aB2Point = aPolygon.getB2DPoint(nIndex); + Point aPoint(aB2Point.getX(), aB2Point.getY()); + aPoint.Move(aLogicOffset.getX(), aLogicOffset.getY()); + if (mbNegativeX) + aPoint.setX(-aPoint.X()); + if (nIndex > 0) + sPolygonElem += " "; + sPolygonElem += aPoint.toString(); + } + sPolygonElem += R"elem(\" style=\"stroke: none; fill: rgb(114,159,207); fill-opacity: 0.8\"/>)elem"; + + OString sSVGElem = R"elem(<svg version=\"1.2\" width=\")elem" + + OString::number(aSelection.GetWidth() / 100.0) + + R"elem(mm\" height=\")elem" + + OString::number(aSelection.GetHeight() / 100.0) + + R"elem(mm\" viewBox=\")elem" + + aSelection.toString() + + R"elem(\" preserveAspectRatio=\"xMidYMid\" xmlns=\"http://www.w3.org/2000/svg\">)elem"; + + aExtraInfo.append(", \"svg\": \"" + + sSVGElem + + "\\n " + + sPolygonElem + + "\\n</svg>" + "\""); // svg + } + } + } + } + aExtraInfo.append("}"); // dragInfo + } + } + } + } + } + if (!bTableSelection && !pOtherShell && maHdlList.GetHdlCount()) + { + boost::property_tree::ptree responseJSON; + boost::property_tree::ptree others; + boost::property_tree::ptree anchor; + boost::property_tree::ptree rectangle; + boost::property_tree::ptree poly; + boost::property_tree::ptree custom; + boost::property_tree::ptree nodes; + for (size_t i = 0; i < maHdlList.GetHdlCount(); i++) + { + SdrHdl *pHdl = maHdlList.GetHdl(i); + boost::property_tree::ptree child; + boost::property_tree::ptree point; + sal_Int32 kind = static_cast<sal_Int32>(pHdl->GetKind()); + child.put("id", pHdl->GetObjHdlNum()); + child.put("kind", kind); + child.put("pointer", static_cast<sal_Int32>(pHdl->GetPointer())); + Point pHdlPos = pHdl->GetPos(); + pHdlPos.Move(addLogicOffset.getX(), addLogicOffset.getY()); + if (convertMapMode) + { + pHdlPos = o3tl::convert(pHdlPos, o3tl::Length::mm100, o3tl::Length::twip); + } + point.put("x", pHdlPos.getX()); + point.put("y", pHdlPos.getY()); + child.add_child("point", point); + const auto node = std::make_pair("", child); + boost::property_tree::ptree* selectedNode = nullptr; + if (kind >= static_cast<sal_Int32>(SdrHdlKind::UpperLeft) && kind <= static_cast<sal_Int32>(SdrHdlKind::LowerRight)) + { + selectedNode = &rectangle; + } + else if (kind == static_cast<sal_Int32>(SdrHdlKind::Poly)) + { + selectedNode = &poly; + } + else if (kind == static_cast<sal_Int32>(SdrHdlKind::CustomShape1)) + { + selectedNode = &custom; + } + else if (kind == static_cast<sal_Int32>(SdrHdlKind::Anchor) || kind == static_cast<sal_Int32>(SdrHdlKind::Anchor_TR)) + { + if (getSdrModelFromSdrView().IsWriter()) + selectedNode = &anchor; + else + // put it to others as we don't render them except in writer + selectedNode = &others; + } + else + { + selectedNode = &others; + } + std::string sKind = std::to_string(kind); + boost::optional< boost::property_tree::ptree& > kindNode = selectedNode->get_child_optional(sKind.c_str()); + if (!kindNode) + { + boost::property_tree::ptree newChild; + newChild.push_back(node); + selectedNode->add_child(sKind.c_str(), newChild); + } + else + kindNode.get().push_back(node); + } + nodes.add_child("rectangle", rectangle); + nodes.add_child("poly", poly); + nodes.add_child("custom", custom); + nodes.add_child("anchor", anchor); + nodes.add_child("others", others); + responseJSON.add_child("kinds", nodes); + std::stringstream aStream; + boost::property_tree::write_json(aStream, responseJSON, /*pretty=*/ false); + handleArrayStr = ", \"handles\":"_ostr; + handleArrayStr = handleArrayStr + aStream.str().c_str(); + if (bConnectorSelection) + { + aStream.str(""); + boost::property_tree::write_json(aStream, aGluePointsTree, /*pretty=*/ false); + handleArrayStr = handleArrayStr + ", \"GluePoints\":"; + handleArrayStr = handleArrayStr + aStream.str().c_str(); + } + } + + if (mbNegativeX) + { + tools::Rectangle aNegatedRect(aSelection); + aNegatedRect.SetLeft(-aNegatedRect.Left()); + aNegatedRect.SetRight(-aNegatedRect.Right()); + aNegatedRect.Normalize(); + sSelectionText = aNegatedRect.toString() + + ", " + OString::number(nRotAngle.get()); + } + else + { + sSelectionText = aSelection.toString() + + ", " + OString::number(nRotAngle.get()); + } + + if (!aExtraInfo.isEmpty()) + { + sSelectionTextView = sSelectionText + ", " + aExtraInfo + "}"; + + if (bMediaObj && pOtherShell == nullptr) + { + // Add the URL only if we have a Media Object and + // we are the selecting view. + SdrMediaObj* mediaObj = dynamic_cast<SdrMediaObj*>(mpMarkedObj); + if (mediaObj) + aExtraInfo.append(", \"url\": \"" + mediaObj->getTempURL().toUtf8() + "\""); + } + + aExtraInfo.append(handleArrayStr + + "}"); + sSelectionText += ", " + aExtraInfo; + } + } + + if (sSelectionText.isEmpty()) + { + sSelectionText = "EMPTY"_ostr; + sSelectionTextView = "EMPTY"_ostr; + if (!pOtherShell) + pViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, OString()); + } + + if (bTableSelection) + { + boost::property_tree::ptree aTableRectangle; + aTableRectangle.put("x", aSelection.Left()); + aTableRectangle.put("y", aSelection.Top()); + aTableRectangle.put("width", aSelection.GetWidth()); + aTableRectangle.put("height", aSelection.GetHeight()); + aTableJsonTree.push_back(std::make_pair("rectangle", aTableRectangle)); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTableJsonTree); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, OString(aStream.str())); + } + else if (!getSdrModelFromSdrView().IsWriter()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, "{}"_ostr); + } + + if (pOtherShell) + { + // Another shell wants to know about our existing + // selection. + if (pViewShell != pOtherShell) + SfxLokHelper::notifyOtherView(pViewShell, pOtherShell, LOK_CALLBACK_GRAPHIC_VIEW_SELECTION, "selection", sSelectionTextView); + } + else + { + // We have a new selection, so both pViewShell and the + // other views want to know about it. + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_GRAPHIC_SELECTION, sSelectionText); + + SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_GRAPHIC_VIEW_SELECTION, "selection", sSelectionTextView); + } + } +} + +void SdrMarkView::SetMarkHandles(SfxViewShell* pOtherShell) +{ + // remember old focus handle values to search for it again + const SdrHdl* pSaveOldFocusHdl = maHdlList.GetFocusHdl(); + bool bSaveOldFocus(false); + sal_uInt32 nSavePolyNum(0), nSavePointNum(0); + SdrHdlKind eSaveKind(SdrHdlKind::Move); + SdrObject* pSaveObj = nullptr; + + mpMarkingSubSelectionOverlay.reset(); + + if(pSaveOldFocusHdl + && pSaveOldFocusHdl->GetObj() + && dynamic_cast<const SdrPathObj*>(pSaveOldFocusHdl->GetObj()) != nullptr + && (pSaveOldFocusHdl->GetKind() == SdrHdlKind::Poly || pSaveOldFocusHdl->GetKind() == SdrHdlKind::BezierWeight)) + { + bSaveOldFocus = true; + nSavePolyNum = pSaveOldFocusHdl->GetPolyNum(); + nSavePointNum = pSaveOldFocusHdl->GetPointNum(); + pSaveObj = pSaveOldFocusHdl->GetObj(); + eSaveKind = pSaveOldFocusHdl->GetKind(); + } + + // delete/clear all handles. This will always be done, even with areMarkHandlesHidden() + maHdlList.Clear(); + maHdlList.SetRotateShear(meDragMode==SdrDragMode::Rotate); + maHdlList.SetDistortShear(meDragMode==SdrDragMode::Shear); + mpMarkedObj=nullptr; + mpMarkedPV=nullptr; + + // are handles enabled at all? Create only then + if(areMarkHandlesHidden()) + return; + + // There can be multiple mark views, but we're only interested in the one that has a window associated. + const bool bTiledRendering = comphelper::LibreOfficeKit::isActive() && GetFirstOutputDevice() && GetFirstOutputDevice()->GetOutDevType() == OUTDEV_WINDOW; + + const size_t nMarkCount=GetMarkedObjectCount(); + bool bStdDrag=meDragMode==SdrDragMode::Move; + bool bSingleTextObjMark=false; + bool bLimitedRotation(false); + + if (nMarkCount==1) + { + mpMarkedObj=GetMarkedObjectByIndex(0); + + if(nullptr != mpMarkedObj) + { + bSingleTextObjMark = + DynCastSdrTextObj( mpMarkedObj) != nullptr && + static_cast<SdrTextObj*>(mpMarkedObj)->IsTextFrame(); + + // RotGrfFlyFrame: we may have limited rotation + bLimitedRotation = SdrDragMode::Rotate == meDragMode && mpMarkedObj->HasLimitedRotation(); + } + } + + bool bFrmHdl=ImpIsFrameHandles(); + + if (nMarkCount>0) + { + mpMarkedPV=GetSdrPageViewOfMarkedByIndex(0); + + for (size_t nMarkNum=0; nMarkNum<nMarkCount && (mpMarkedPV!=nullptr || !bFrmHdl); ++nMarkNum) + { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + + if (mpMarkedPV!=pM->GetPageView()) + { + mpMarkedPV=nullptr; + } + } + } + + SfxViewShell* pViewShell = GetSfxViewShell(); + + // check if text edit or ole is active and handles need to be suppressed. This may be the case + // when a single object is selected + // Using a strict return statement is okay here; no handles means *no* handles. + if(mpMarkedObj) + { + // formerly #i33755#: If TextEdit is active the EditEngine will directly paint + // to the window, so suppress Overlay and handles completely; a text frame for + // the active text edit will be painted by the repaint mechanism in + // SdrObjEditView::ImpPaintOutlinerView in this case. This needs to be reworked + // in the future + // Also formerly #122142#: Pretty much the same for SdrCaptionObj's in calc. + if(static_cast<SdrView*>(this)->IsTextEdit()) + { + const SdrTextObj* pSdrTextObj = DynCastSdrTextObj(mpMarkedObj); + + if (pSdrTextObj && pSdrTextObj->IsInEditMode()) + { + if (!bTiledRendering) + return; + } + } + + // formerly #i118524#: if inplace activated OLE is selected, suppress handles + const SdrOle2Obj* pSdrOle2Obj = dynamic_cast< const SdrOle2Obj* >(mpMarkedObj); + + if(pSdrOle2Obj && (pSdrOle2Obj->isInplaceActive() || pSdrOle2Obj->isUiActive())) + { + return; + } + + if (!maSubSelectionList.empty()) + { + mpMarkingSubSelectionOverlay = std::make_unique<MarkingSubSelectionOverlay>(*this, maSubSelectionList); + } + } + + tools::Rectangle aRect(GetMarkedObjRect()); + + if (bFrmHdl) + { + if(!aRect.IsEmpty()) + { + // otherwise nothing is found + const size_t nSiz0(maHdlList.GetHdlCount()); + + if( bSingleTextObjMark ) + { + mpMarkedObj->AddToHdlList(maHdlList); + } + else + { + const bool bWdt0(aRect.Left() == aRect.Right()); + const bool bHgt0(aRect.Top() == aRect.Bottom()); + + if (bWdt0 && bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft)); + } + else if (!bStdDrag && (bWdt0 || bHgt0)) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft)); + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomRight(), SdrHdlKind::LowerRight)); + } + else + { + if (!bWdt0 && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft)); + } + + if (!bLimitedRotation && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopCenter(), SdrHdlKind::Upper)); + } + + if (!bWdt0 && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopRight(), SdrHdlKind::UpperRight)); + } + + if (!bLimitedRotation && !bWdt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.LeftCenter(), SdrHdlKind::Left )); + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.RightCenter(), SdrHdlKind::Right)); + } + + if (!bWdt0 && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomLeft(), SdrHdlKind::LowerLeft)); + } + + if (!bLimitedRotation && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomCenter(), SdrHdlKind::Lower)); + } + + if (!bWdt0 && !bHgt0) + { + maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomRight(), SdrHdlKind::LowerRight)); + } + } + } + + // Diagram selection visualization support + // Caution: CppunitTest_sd_tiledrendering shows that mpMarkedObj *can* actually be nullptr (!) + if(nullptr != mpMarkedObj && mpMarkedObj->isDiagram()) + { + mpMarkedObj->AddToHdlList(maHdlList); + } + + const size_t nSiz1(maHdlList.GetHdlCount()); + + // moved setting the missing parameters at SdrHdl here from the + // single loop above (bSingleTextObjMark), this was missing all + // the time. Setting SdrObject is now required to correctly get + // the View-Dependent evtl. GridOffset adapted + for (size_t i=nSiz0; i<nSiz1; ++i) + { + SdrHdl* pHdl=maHdlList.GetHdl(i); + pHdl->SetObj(mpMarkedObj); + pHdl->SetPageView(mpMarkedPV); + pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0)); + } + } + } + else + { + bool bDone(false); + + // moved crop handling to non-frame part and the handle creation to SdrGrafObj + if(1 == nMarkCount && mpMarkedObj && SdrDragMode::Crop == meDragMode) + { + // Default addCropHandles from SdrObject does nothing. When pMarkedObj is SdrGrafObj, previous + // behaviour occurs (code in svx/source/svdraw/svdograf.cxx). When pMarkedObj is SwVirtFlyDrawObj + // writer takes the responsibility of adding handles (code in sw/source/core/draw/dflyobj.cxx) + const size_t nSiz0(maHdlList.GetHdlCount()); + mpMarkedObj->addCropHandles(maHdlList); + const size_t nSiz1(maHdlList.GetHdlCount()); + + // Was missing: Set infos at SdrCropHdl + for (size_t i=nSiz0; i<nSiz1; ++i) + { + SdrHdl* pHdl=maHdlList.GetHdl(i); + pHdl->SetObj(mpMarkedObj); + pHdl->SetPageView(mpMarkedPV); + pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0)); + } + + bDone = true; + } + + if(!bDone) + { + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) + { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPageView* pPV=pM->GetPageView(); + const size_t nSiz0=maHdlList.GetHdlCount(); + pObj->AddToHdlList(maHdlList); + const size_t nSiz1=maHdlList.GetHdlCount(); + bool bPoly=pObj->IsPolyObj(); + const SdrUShortCont& rMrkPnts = pM->GetMarkedPoints(); + for (size_t i=nSiz0; i<nSiz1; ++i) + { + SdrHdl* pHdl=maHdlList.GetHdl(i); + pHdl->SetObj(pObj); + pHdl->SetPageView(pPV); + pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0)); + + if (bPoly) + { + bool bSelected= rMrkPnts.find( sal_uInt16(i-nSiz0) ) != rMrkPnts.end(); + pHdl->SetSelected(bSelected); + if (mbPlusHdlAlways || bSelected) + { + SdrHdlList plusList(nullptr); + pObj->AddToPlusHdlList(plusList, *pHdl); + sal_uInt32 nPlusHdlCnt=plusList.GetHdlCount(); + for (sal_uInt32 nPlusNum=0; nPlusNum<nPlusHdlCnt; nPlusNum++) + { + SdrHdl* pPlusHdl=plusList.GetHdl(nPlusNum); + pPlusHdl->SetObj(pObj); + pPlusHdl->SetPageView(pPV); + pPlusHdl->SetPlusHdl(true); + } + plusList.MoveTo(maHdlList); + } + } + } + } + } + } + + // GluePoint handles + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) + { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + if (!pGPL) + continue; + + SdrPageView* pPV=pM->GetPageView(); + const SdrUShortCont& rMrkGlue=pM->GetMarkedGluePoints(); + for (sal_uInt16 nId : rMrkGlue) + { + //nNum changed to nNumGP because already used in for loop + sal_uInt16 nNumGP=pGPL->FindGluePoint(nId); + if (nNumGP!=SDRGLUEPOINT_NOTFOUND) + { + const SdrGluePoint& rGP=(*pGPL)[nNumGP]; + Point aPos(rGP.GetAbsolutePos(*pObj)); + std::unique_ptr<SdrHdl> pGlueHdl(new SdrHdl(aPos,SdrHdlKind::Glue)); + pGlueHdl->SetObj(pObj); + pGlueHdl->SetPageView(pPV); + pGlueHdl->SetObjHdlNum(nId); + maHdlList.AddHdl(std::move(pGlueHdl)); + } + } + } + + // rotation point/axis of reflection + if(!bLimitedRotation) + { + AddDragModeHdl(meDragMode); + } + + // sort handles + maHdlList.Sort(); + + // add custom handles (used by other apps, e.g. AnchorPos) + AddCustomHdl(); + + // moved it here to access all the handles for callback. + if (bTiledRendering && pViewShell) + { + SetMarkHandlesForLOKit(aRect, pOtherShell); + } + + // try to restore focus handle index from remembered values + if(!bSaveOldFocus) + return; + + for(size_t a = 0; a < maHdlList.GetHdlCount(); ++a) + { + SdrHdl* pCandidate = maHdlList.GetHdl(a); + + if(pCandidate->GetObj() + && pCandidate->GetObj() == pSaveObj + && pCandidate->GetKind() == eSaveKind + && pCandidate->GetPolyNum() == nSavePolyNum + && pCandidate->GetPointNum() == nSavePointNum) + { + maHdlList.SetFocusHdl(pCandidate); + break; + } + } +} + +void SdrMarkView::AddCustomHdl() +{ + // add custom handles (used by other apps, e.g. AnchorPos) +} + +void SdrMarkView::SetDragMode(SdrDragMode eMode) +{ + SdrDragMode eMode0=meDragMode; + meDragMode=eMode; + if (meDragMode==SdrDragMode::Resize) meDragMode=SdrDragMode::Move; + if (meDragMode!=eMode0) { + ForceRefToMarked(); + SetMarkHandles(nullptr); + { + if (AreObjectsMarked()) MarkListHasChanged(); + } + } +} + +void SdrMarkView::AddDragModeHdl(SdrDragMode eMode) +{ + switch(eMode) + { + case SdrDragMode::Rotate: + { + // add rotation center + maHdlList.AddHdl(std::make_unique<SdrHdl>(maRef1, SdrHdlKind::Ref1)); + break; + } + case SdrDragMode::Mirror: + { + // add axis of reflection + std::unique_ptr<SdrHdl> pHdl3(new SdrHdl(maRef2, SdrHdlKind::Ref2)); + std::unique_ptr<SdrHdl> pHdl2(new SdrHdl(maRef1, SdrHdlKind::Ref1)); + std::unique_ptr<SdrHdl> pHdl1(new SdrHdlLine(*pHdl2, *pHdl3, SdrHdlKind::MirrorAxis)); + + pHdl1->SetObjHdlNum(1); // for sorting + pHdl2->SetObjHdlNum(2); // for sorting + pHdl3->SetObjHdlNum(3); // for sorting + + maHdlList.AddHdl(std::move(pHdl1)); // line comes first, so it is the last in HitTest + maHdlList.AddHdl(std::move(pHdl2)); + maHdlList.AddHdl(std::move(pHdl3)); + + break; + } + case SdrDragMode::Transparence: + { + // add interactive transparency handle + const size_t nMarkCount = GetMarkedObjectCount(); + if(nMarkCount == 1) + { + SdrObject* pObj = GetMarkedObjectByIndex(0); + SdrModel& rModel = GetModel(); + const SfxItemSet& rSet = pObj->GetMergedItemSet(); + + if(SfxItemState::SET != rSet.GetItemState(XATTR_FILLFLOATTRANSPARENCE, false)) + { + // add this item, it's not yet there + XFillFloatTransparenceItem aNewItem(rSet.Get(XATTR_FILLFLOATTRANSPARENCE)); + basegfx::BGradient aGrad = aNewItem.GetGradientValue(); + + aNewItem.SetEnabled(true); + aGrad.SetStartIntens(100); + aGrad.SetEndIntens(100); + aNewItem.SetGradientValue(aGrad); + + // add undo to allow user to take back this step + if (rModel.IsUndoEnabled()) + { + rModel.BegUndo(SvxResId(SIP_XA_FILLTRANSPARENCE)); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoAttrObject(*pObj)); + rModel.EndUndo(); + } + + SfxItemSet aNewSet(rModel.GetItemPool()); + aNewSet.Put(aNewItem); + pObj->SetMergedItemSetAndBroadcast(aNewSet); + } + + // set values and transform to vector set + GradTransVector aGradTransVector; + GradTransGradient aGradTransGradient; + + aGradTransGradient.aGradient = rSet.Get(XATTR_FILLFLOATTRANSPARENCE).GetGradientValue(); + GradTransformer::GradToVec(aGradTransGradient, aGradTransVector, pObj); + + // build handles + const Point aTmpPos1(basegfx::fround(aGradTransVector.maPositionA.getX()), basegfx::fround(aGradTransVector.maPositionA.getY())); + const Point aTmpPos2(basegfx::fround(aGradTransVector.maPositionB.getX()), basegfx::fround(aGradTransVector.maPositionB.getY())); + std::unique_ptr<SdrHdlColor> pColHdl1(new SdrHdlColor(aTmpPos1, aGradTransVector.aCol1, SDR_HANDLE_COLOR_SIZE_NORMAL, true)); + std::unique_ptr<SdrHdlColor> pColHdl2(new SdrHdlColor(aTmpPos2, aGradTransVector.aCol2, SDR_HANDLE_COLOR_SIZE_NORMAL, true)); + std::unique_ptr<SdrHdlGradient> pGradHdl(new SdrHdlGradient(aTmpPos1, aTmpPos2, false)); + DBG_ASSERT(pColHdl1 && pColHdl2 && pGradHdl, "Could not get all necessary handles!"); + + // link them + pGradHdl->SetColorHandles(pColHdl1.get(), pColHdl2.get()); + pGradHdl->SetObj(pObj); + pColHdl1->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl)); + pColHdl2->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl)); + + // insert them + maHdlList.AddHdl(std::move(pColHdl1)); + maHdlList.AddHdl(std::move(pColHdl2)); + maHdlList.AddHdl(std::move(pGradHdl)); + } + break; + } + case SdrDragMode::Gradient: + { + // add interactive gradient handle + const size_t nMarkCount = GetMarkedObjectCount(); + if(nMarkCount == 1) + { + SdrObject* pObj = GetMarkedObjectByIndex(0); + const SfxItemSet& rSet = pObj->GetMergedItemSet(); + drawing::FillStyle eFillStyle = rSet.Get(XATTR_FILLSTYLE).GetValue(); + + if(eFillStyle == drawing::FillStyle_GRADIENT) + { + // set values and transform to vector set + GradTransVector aGradTransVector; + GradTransGradient aGradTransGradient; + Size aHdlSize(15, 15); + + aGradTransGradient.aGradient = rSet.Get(XATTR_FILLGRADIENT).GetGradientValue(); + GradTransformer::GradToVec(aGradTransGradient, aGradTransVector, pObj); + + // build handles + const Point aTmpPos1(basegfx::fround(aGradTransVector.maPositionA.getX()), basegfx::fround(aGradTransVector.maPositionA.getY())); + const Point aTmpPos2(basegfx::fround(aGradTransVector.maPositionB.getX()), basegfx::fround(aGradTransVector.maPositionB.getY())); + std::unique_ptr<SdrHdlColor> pColHdl1(new SdrHdlColor(aTmpPos1, aGradTransVector.aCol1, aHdlSize, false)); + std::unique_ptr<SdrHdlColor> pColHdl2(new SdrHdlColor(aTmpPos2, aGradTransVector.aCol2, aHdlSize, false)); + std::unique_ptr<SdrHdlGradient> pGradHdl(new SdrHdlGradient(aTmpPos1, aTmpPos2, true)); + DBG_ASSERT(pColHdl1 && pColHdl2 && pGradHdl, "Could not get all necessary handles!"); + + // link them + pGradHdl->SetColorHandles(pColHdl1.get(), pColHdl2.get()); + pGradHdl->SetObj(pObj); + pColHdl1->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl)); + pColHdl2->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl)); + + // insert them + maHdlList.AddHdl(std::move(pColHdl1)); + maHdlList.AddHdl(std::move(pColHdl2)); + maHdlList.AddHdl(std::move(pGradHdl)); + } + } + break; + } + case SdrDragMode::Crop: + { + // TODO + break; + } + default: break; + } +} + +/** handle mouse over effects for handles */ +bool SdrMarkView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if(maHdlList.GetHdlCount()) + { + SdrHdl* pMouseOverHdl = nullptr; + if( !rMEvt.IsLeaveWindow() && pWin ) + { + Point aMDPos( pWin->PixelToLogic( rMEvt.GetPosPixel() ) ); + pMouseOverHdl = PickHandle(aMDPos); + } + + // notify last mouse over handle that he lost the mouse + const size_t nHdlCount = maHdlList.GetHdlCount(); + + for(size_t nHdl = 0; nHdl < nHdlCount; ++nHdl) + { + SdrHdl* pCurrentHdl = GetHdl(nHdl); + if( pCurrentHdl->mbMouseOver ) + { + if( pCurrentHdl != pMouseOverHdl ) + { + pCurrentHdl->mbMouseOver = false; + pCurrentHdl->onMouseLeave(); + } + break; + } + } + + // notify current mouse over handle + if( pMouseOverHdl ) + { + pMouseOverHdl->mbMouseOver = true; + pMouseOverHdl->onMouseEnter(rMEvt); + } + } + return SdrSnapView::MouseMove(rMEvt, pWin); +} + +bool SdrMarkView::RequestHelp(const HelpEvent& rHEvt) +{ + if (maHdlList.GetHdlCount()) + { + const size_t nHdlCount = maHdlList.GetHdlCount(); + + for (size_t nHdl = 0; nHdl < nHdlCount; ++nHdl) + { + SdrHdl* pCurrentHdl = GetHdl(nHdl); + if (pCurrentHdl->mbMouseOver) + { + pCurrentHdl->onHelpRequest(); + return true; + } + } + } + return SdrSnapView::RequestHelp(rHEvt); +} + +void SdrMarkView::ForceRefToMarked() +{ + switch(meDragMode) + { + case SdrDragMode::Rotate: + { + tools::Rectangle aR(GetMarkedObjRect()); + maRef1 = aR.Center(); + + break; + } + + case SdrDragMode::Mirror: + { + // first calculate the length of the axis of reflection + tools::Long nOutMin=0; + tools::Long nOutMax=0; + tools::Long nMinLen=0; + tools::Long nObjDst=0; + tools::Long nOutHgt=0; + OutputDevice* pOut=GetFirstOutputDevice(); + if (pOut!=nullptr) { + // minimum length: 50 pixels + nMinLen=pOut->PixelToLogic(Size(0,50)).Height(); + // 20 pixels distance to the Obj for the reference point + nObjDst=pOut->PixelToLogic(Size(0,20)).Height(); + // MinY/MaxY + // margin = minimum length = 10 pixels + tools::Long nDst=pOut->PixelToLogic(Size(0,10)).Height(); + nOutMin=-pOut->GetMapMode().GetOrigin().Y(); + nOutMax=pOut->GetOutputSize().Height()-1+nOutMin; + nOutMin+=nDst; + nOutMax-=nDst; + // absolute minimum length, however, is 10 pixels + if (nOutMax-nOutMin<nDst) { + nOutMin+=nOutMax+1; + nOutMin/=2; + nOutMin-=(nDst+1)/2; + nOutMax=nOutMin+nDst; + } + nOutHgt=nOutMax-nOutMin; + // otherwise minimum length = 1/4 OutHgt + tools::Long nTemp=nOutHgt/4; + if (nTemp>nMinLen) nMinLen=nTemp; + } + + tools::Rectangle aR(GetMarkedObjBoundRect()); + Point aCenter(aR.Center()); + tools::Long nMarkHgt=aR.GetHeight()-1; + tools::Long nHgt=nMarkHgt+nObjDst*2; // 20 pixels overlapping above and below + if (nHgt<nMinLen) nHgt=nMinLen; // minimum length 50 pixels or 1/4 OutHgt, respectively + + tools::Long nY1=aCenter.Y()-(nHgt+1)/2; + tools::Long nY2=nY1+nHgt; + + if (pOut!=nullptr && nMinLen>nOutHgt) nMinLen=nOutHgt; // TODO: maybe shorten this a little + + if (pOut!=nullptr) { // now move completely into the visible area + if (nY1<nOutMin) { + nY1=nOutMin; + if (nY2<nY1+nMinLen) nY2=nY1+nMinLen; + } + if (nY2>nOutMax) { + nY2=nOutMax; + if (nY1>nY2-nMinLen) nY1=nY2-nMinLen; + } + } + + maRef1.setX(aCenter.X() ); + maRef1.setY(nY1 ); + maRef2.setX(aCenter.X() ); + maRef2.setY(nY2 ); + + break; + } + + case SdrDragMode::Transparence: + case SdrDragMode::Gradient: + case SdrDragMode::Crop: + { + tools::Rectangle aRect(GetMarkedObjBoundRect()); + maRef1 = aRect.TopLeft(); + maRef2 = aRect.BottomRight(); + break; + } + default: break; + } +} + +void SdrMarkView::SetRef1(const Point& rPt) +{ + if(meDragMode == SdrDragMode::Rotate || meDragMode == SdrDragMode::Mirror) + { + maRef1 = rPt; + SdrHdl* pH = maHdlList.GetHdl(SdrHdlKind::Ref1); + if(pH) + pH->SetPos(rPt); + } +} + +void SdrMarkView::SetRef2(const Point& rPt) +{ + if(meDragMode == SdrDragMode::Mirror) + { + maRef2 = rPt; + SdrHdl* pH = maHdlList.GetHdl(SdrHdlKind::Ref2); + if(pH) + pH->SetPos(rPt); + } +} + +SfxViewShell* SdrMarkView::GetSfxViewShell() const +{ + return SfxViewShell::Current(); +} + +void SdrMarkView::CheckMarked() +{ + for (size_t nm=GetMarkedObjectCount(); nm>0;) { + --nm; + SdrMark* pM = GetSdrMarkByIndex(nm); + SdrObject* pObj = pM->GetMarkedSdrObj(); + SdrPageView* pPV = pM->GetPageView(); + bool bRaus = !pObj || !pPV->IsObjMarkable(pObj); + if (bRaus) + { + GetMarkedObjectListWriteAccess().DeleteMark(nm); + } + else + { + if (!IsGluePointEditMode()) { // selected gluepoints only in GlueEditMode + SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + rPts.clear(); + } + } + } + + // at least reset the remembered BoundRect to prevent handle + // generation if bForceFrameHandles is TRUE. + mbMarkedObjRectDirty = true; +} + +void SdrMarkView::SetMarkRects() +{ + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + pPV->SetHasMarkedObj(GetMarkedObjectList().TakeSnapRect(pPV, pPV->MarkSnap())); + GetMarkedObjectList().TakeBoundRect(pPV, pPV->MarkBound()); + } +} + +void SdrMarkView::SetFrameHandles(bool bOn) +{ + if (bOn!=mbForceFrameHandles) { + bool bOld=ImpIsFrameHandles(); + mbForceFrameHandles=bOn; + bool bNew=ImpIsFrameHandles(); + if (bNew!=bOld) { + AdjustMarkHdl(); + MarkListHasChanged(); + } + } +} + +void SdrMarkView::SetEditMode(SdrViewEditMode eMode) +{ + if (eMode==meEditMode) return; + + bool bGlue0=meEditMode==SdrViewEditMode::GluePointEdit; + bool bEdge0=static_cast<SdrCreateView*>(this)->IsEdgeTool(); + meEditMode0=meEditMode; + meEditMode=eMode; + bool bGlue1=meEditMode==SdrViewEditMode::GluePointEdit; + bool bEdge1=static_cast<SdrCreateView*>(this)->IsEdgeTool(); + // avoid flickering when switching between GlueEdit and EdgeTool + if (bGlue1 && !bGlue0) ImpSetGlueVisible2(bGlue1); + if (bEdge1!=bEdge0) ImpSetGlueVisible3(bEdge1); + if (!bGlue1 && bGlue0) ImpSetGlueVisible2(bGlue1); + if (bGlue0 && !bGlue1) UnmarkAllGluePoints(); +} + + +bool SdrMarkView::IsObjMarkable(SdrObject const * pObj, SdrPageView const * pPV) const +{ + if (pObj) + { + if (pObj->IsMarkProtect() || + (!mbDesignMode && pObj->IsUnoObj())) + { + // object not selectable or + // SdrUnoObj not in DesignMode + return false; + } + } + return pPV==nullptr || pPV->IsObjMarkable(pObj); +} + +bool SdrMarkView::IsMarkedObjHit(const Point& rPnt, short nTol) const +{ + bool bRet=false; + nTol=ImpGetHitTolLogic(nTol,nullptr); + for (size_t nm=0; nm<GetMarkedObjectCount() && !bRet; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + bRet = nullptr != CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pM->GetPageView(),SdrSearchOptions::NONE,nullptr); + } + return bRet; +} + +SdrHdl* SdrMarkView::PickHandle(const Point& rPnt) const +{ + if (mbSomeObjChgdFlag) { // recalculate handles, if necessary + FlushComeBackTimer(); + } + return maHdlList.IsHdlListHit(rPnt); +} + +bool SdrMarkView::MarkObj(const Point& rPnt, short nTol, bool bToggle, bool bDeep) +{ + SdrPageView* pPV; + nTol=ImpGetHitTolLogic(nTol,nullptr); + SdrSearchOptions nOptions=SdrSearchOptions::PICKMARKABLE; + if (bDeep) nOptions=nOptions|SdrSearchOptions::DEEP; + SdrObject* pObj = PickObj(rPnt, static_cast<sal_uInt16>(nTol), pPV, nOptions); + if (pObj) { + bool bUnmark=bToggle && IsObjMarked(pObj); + MarkObj(pObj,pPV,bUnmark); + } + return pObj != nullptr; +} + +bool SdrMarkView::MarkNextObj(bool bPrev) +{ + SdrPageView* pPageView = GetSdrPageView(); + + if(!pPageView) + { + return false; + } + + SortMarkedObjects(); + const size_t nMarkCount=GetMarkedObjectCount(); + size_t nChgMarkNum = SAL_MAX_SIZE; // number of the MarkEntry we want to replace + size_t nSearchObjNum = bPrev ? 0 : SAL_MAX_SIZE; + if (nMarkCount!=0) { + nChgMarkNum=bPrev ? 0 : nMarkCount-1; + SdrMark* pM=GetSdrMarkByIndex(nChgMarkNum); + OSL_ASSERT(pM!=nullptr); + if (pM->GetMarkedSdrObj() != nullptr) + nSearchObjNum = pM->GetMarkedSdrObj()->GetNavigationPosition(); + } + + SdrObject* pMarkObj=nullptr; + SdrObjList* pSearchObjList=pPageView->GetObjList(); + const size_t nObjCount = pSearchObjList->GetObjCount(); + if (nObjCount!=0) { + if (nSearchObjNum>nObjCount) nSearchObjNum=nObjCount; + while (pMarkObj==nullptr && ((!bPrev && nSearchObjNum>0) || (bPrev && nSearchObjNum<nObjCount))) + { + if (!bPrev) + nSearchObjNum--; + SdrObject* pSearchObj = pSearchObjList->GetObjectForNavigationPosition(nSearchObjNum); + if (IsObjMarkable(pSearchObj,pPageView)) + { + if (TryToFindMarkedObject(pSearchObj)==SAL_MAX_SIZE) + { + pMarkObj=pSearchObj; + } + } + if (bPrev) nSearchObjNum++; + } + } + + if(!pMarkObj) + { + return false; + } + + if (nChgMarkNum!=SAL_MAX_SIZE) + { + GetMarkedObjectListWriteAccess().DeleteMark(nChgMarkNum); + } + MarkObj(pMarkObj,pPageView); // also calls MarkListHasChanged(), AdjustMarkHdl() + return true; +} + +bool SdrMarkView::MarkNextObj(const Point& rPnt, short nTol, bool bPrev) +{ + SortMarkedObjects(); + nTol=ImpGetHitTolLogic(nTol,nullptr); + SdrMark* pTopMarkHit=nullptr; + SdrMark* pBtmMarkHit=nullptr; + size_t nTopMarkHit=0; + size_t nBtmMarkHit=0; + // find topmost of the selected objects that is hit by rPnt + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=nMarkCount; nm>0 && pTopMarkHit==nullptr;) { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + if(CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pM->GetPageView(),SdrSearchOptions::NONE,nullptr)) + { + pTopMarkHit=pM; + nTopMarkHit=nm; + } + } + // nothing found, in this case, just select an object + if (pTopMarkHit==nullptr) return MarkObj(rPnt,sal_uInt16(nTol)); + + SdrObject* pTopObjHit=pTopMarkHit->GetMarkedSdrObj(); + SdrObjList* pObjList=pTopObjHit->getParentSdrObjListFromSdrObject(); + SdrPageView* pPV=pTopMarkHit->GetPageView(); + // find lowermost of the selected objects that is hit by rPnt + // and is placed on the same PageView as pTopMarkHit + for (size_t nm=0; nm<nMarkCount && pBtmMarkHit==nullptr; ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrPageView* pPV2=pM->GetPageView(); + if (pPV2==pPV && CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pPV2,SdrSearchOptions::NONE,nullptr)) + { + pBtmMarkHit=pM; + nBtmMarkHit=nm; + } + } + if (pBtmMarkHit==nullptr) { pBtmMarkHit=pTopMarkHit; nBtmMarkHit=nTopMarkHit; } + SdrObject* pBtmObjHit=pBtmMarkHit->GetMarkedSdrObj(); + const size_t nObjCount = pObjList->GetObjCount(); + + size_t nSearchBeg(0); + E3dScene* pScene(nullptr); + SdrObject* pObjHit(bPrev ? pBtmObjHit : pTopObjHit); + bool bRemap = + nullptr != dynamic_cast< const E3dCompoundObject* >(pObjHit); + if (bRemap) + { + pScene = DynCastE3dScene(pObjHit->getParentSdrObjectFromSdrObject()); + bRemap = nullptr != pScene; + } + + if(bPrev) + { + sal_uInt32 nOrdNumBtm(pBtmObjHit->GetOrdNum()); + + if(bRemap) + { + nOrdNumBtm = pScene->RemapOrdNum(nOrdNumBtm); + } + + nSearchBeg = nOrdNumBtm + 1; + } + else + { + sal_uInt32 nOrdNumTop(pTopObjHit->GetOrdNum()); + + if(bRemap) + { + nOrdNumTop = pScene->RemapOrdNum(nOrdNumTop); + } + + nSearchBeg = nOrdNumTop; + } + + size_t no=nSearchBeg; + SdrObject* pFndObj=nullptr; + while (pFndObj==nullptr && ((!bPrev && no>0) || (bPrev && no<nObjCount))) { + if (!bPrev) no--; + SdrObject* pObj; + + if(bRemap) + { + pObj = pObjList->GetObj(pScene->RemapOrdNum(no)); + } + else + { + pObj = pObjList->GetObj(no); + } + + if (CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pObj,pPV,SdrSearchOptions::TESTMARKABLE,nullptr)) + { + if (TryToFindMarkedObject(pObj)==SAL_MAX_SIZE) { + pFndObj=pObj; + } else { + // TODO: for performance reasons set on to Top or Btm, if necessary + } + } + if (bPrev) no++; + } + if (pFndObj!=nullptr) + { + GetMarkedObjectListWriteAccess().DeleteMark(bPrev?nBtmMarkHit:nTopMarkHit); + GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pFndObj,pPV)); + MarkListHasChanged(); + AdjustMarkHdl(); + } + return pFndObj!=nullptr; +} + +void SdrMarkView::MarkObj(const tools::Rectangle& rRect, bool bUnmark) +{ + bool bFnd=false; + tools::Rectangle aR(rRect); + SdrObjList* pObjList; + BrkAction(); + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + pObjList=pPV->GetObjList(); + tools::Rectangle aFrm1(aR); + for (const rtl::Reference<SdrObject>& pObj : *pObjList) { + tools::Rectangle aRect(pObj->GetCurrentBoundRect()); + if (aFrm1.Contains(aRect)) { + if (!bUnmark) { + if (IsObjMarkable(pObj.get(),pPV)) + { + GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pObj.get(),pPV)); + bFnd=true; + } + } else { + const size_t nPos=TryToFindMarkedObject(pObj.get()); + if (nPos!=SAL_MAX_SIZE) + { + GetMarkedObjectListWriteAccess().DeleteMark(nPos); + bFnd=true; + } + } + } + } + } + if (bFnd) { + SortMarkedObjects(); + MarkListHasChanged(); + AdjustMarkHdl(); + } +} + +namespace { + +void collectUIInformation(const SdrObject* pObj) +{ + EventDescription aDescription; + aDescription.aAction = "SELECT"; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "CurrentApp"; + + if (!pObj->GetName().isEmpty()) + aDescription.aParameters = {{"OBJECT", pObj->GetName()}}; + else + aDescription.aParameters = {{"OBJECT", "Unnamed_Obj_" + OUString::number(pObj->GetOrdNum())}}; + + UITestLogger::getInstance().logEvent(aDescription); +} + +} + + void SdrMarkView::MarkObj(SdrObject* pObj, SdrPageView* pPV, bool bUnmark, bool bDoNoSetMarkHdl, + std::vector<basegfx::B2DRectangle> && rSubSelections) +{ + if (!(pObj!=nullptr && pPV!=nullptr && IsObjMarkable(pObj, pPV))) + return; + + BrkAction(); + if (!bUnmark) + { + GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pObj,pPV)); + collectUIInformation(pObj); + } + else + { + const size_t nPos=TryToFindMarkedObject(pObj); + if (nPos!=SAL_MAX_SIZE) + { + GetMarkedObjectListWriteAccess().DeleteMark(nPos); + } + } + + maSubSelectionList = std::move(rSubSelections); + + if (!bDoNoSetMarkHdl) { + MarkListHasChanged(); + AdjustMarkHdl(); + } +} + +bool SdrMarkView::IsObjMarked(SdrObject const * pObj) const +{ + return TryToFindMarkedObject(pObj)!=SAL_MAX_SIZE; +} + +sal_uInt16 SdrMarkView::GetMarkHdlSizePixel() const +{ + return maHdlList.GetHdlSize()*2+1; +} + +void SdrMarkView::SetMarkHdlSizePixel(sal_uInt16 nSiz) +{ + if (nSiz<3) nSiz=3; + nSiz/=2; + if (nSiz!=maHdlList.GetHdlSize()) { + maHdlList.SetHdlSize(nSiz); + } +} + +bool SdrMarkView::getPossibleGridOffsetForSdrObject( + basegfx::B2DVector& rOffset, + const SdrObject* pObj, + const SdrPageView* pPV) const +{ + if(nullptr == pObj || nullptr == pPV) + { + return false; + } + + const OutputDevice* pOutputDevice(GetFirstOutputDevice()); + + if(nullptr == pOutputDevice) + { + return false; + } + + const SdrPageWindow* pSdrPageWindow(pPV->FindPageWindow(*pOutputDevice)); + + if(nullptr == pSdrPageWindow) + { + return false; + } + + const sdr::contact::ObjectContact& rObjectContact(pSdrPageWindow->GetObjectContact()); + + if(!rObjectContact.supportsGridOffsets()) + { + return false; + } + + const sdr::contact::ViewObjectContact& rVOC(pObj->GetViewContact().GetViewObjectContact( + const_cast<sdr::contact::ObjectContact&>(rObjectContact))); + + rOffset = rVOC.getGridOffset(); + + return !rOffset.equalZero(); +} + +bool SdrMarkView::getPossibleGridOffsetForPosition( + basegfx::B2DVector& rOffset, + const basegfx::B2DPoint& rPoint, + const SdrPageView* pPV) const +{ + if(nullptr == pPV) + { + return false; + } + + const OutputDevice* pOutputDevice(GetFirstOutputDevice()); + + if(nullptr == pOutputDevice) + { + return false; + } + + const SdrPageWindow* pSdrPageWindow(pPV->FindPageWindow(*pOutputDevice)); + + if(nullptr == pSdrPageWindow) + { + return false; + } + + const sdr::contact::ObjectContact& rObjectContact(pSdrPageWindow->GetObjectContact()); + + if(!rObjectContact.supportsGridOffsets()) + { + return false; + } + + rObjectContact.calculateGridOffsetForB2DRange(rOffset, basegfx::B2DRange(rPoint)); + + return !rOffset.equalZero(); +} + +SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObject* pObj, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay) const +{ + if(((nOptions & SdrSearchOptions::IMPISMASTER) && pObj->IsNotVisibleAsMaster()) || (!pObj->IsVisible())) + { + return nullptr; + } + + const bool bCheckIfMarkable(nOptions & SdrSearchOptions::TESTMARKABLE); + const bool bDeep(nOptions & SdrSearchOptions::DEEP); + const bool bOLE(dynamic_cast< const SdrOle2Obj* >(pObj) != nullptr); + auto pTextObj = DynCastSdrTextObj( pObj); + const bool bTXT(pTextObj && pTextObj->IsTextFrame()); + SdrObject* pRet=nullptr; + tools::Rectangle aRect(pObj->GetCurrentBoundRect()); + + // add possible GridOffset to up-to-now view-independent BoundRect data + basegfx::B2DVector aGridOffset(0.0, 0.0); + if(getPossibleGridOffsetForSdrObject(aGridOffset, pObj, pPV)) + { + aRect += Point( + basegfx::fround(aGridOffset.getX()), + basegfx::fround(aGridOffset.getY())); + } + + double nTol2(nTol); + + // double tolerance for OLE, text frames and objects in + // active text edit + if(bOLE || bTXT || pObj==static_cast<const SdrObjEditView*>(this)->GetTextEditObject()) + { + nTol2*=2; + } + + aRect.AdjustLeft( -nTol2 ); // add 1 tolerance for all objects + aRect.AdjustTop( -nTol2 ); + aRect.AdjustRight(nTol2 ); + aRect.AdjustBottom(nTol2 ); + + if (aRect.Contains(rPnt)) + { + if (!bCheckIfMarkable || IsObjMarkable(pObj,pPV)) + { + SdrObjList* pOL=pObj->GetSubList(); + + if (pOL!=nullptr && pOL->GetObjCount()!=0) + { + SdrObject* pTmpObj; + // adjustment hit point for virtual objects + Point aPnt( rPnt ); + + if ( auto pVirtObj = dynamic_cast<const SdrVirtObj*>( pObj) ) + { + Point aOffset = pVirtObj->GetOffset(); + aPnt.Move( -aOffset.X(), -aOffset.Y() ); + } + + pRet=CheckSingleSdrObjectHit(aPnt,nTol,pOL,pPV,nOptions,pMVisLay,pTmpObj); + } + else + { + if(!pMVisLay || pMVisLay->IsSet(pObj->GetLayer())) + { + pRet = SdrObjectPrimitiveHit(*pObj, rPnt, {nTol2, nTol2}, *pPV, &pPV->GetVisibleLayers(), false); + } + } + } + } + + if (!bDeep && pRet!=nullptr) + { + pRet=pObj; + } + + return pRet; +} + +SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObjList const * pOL, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay, SdrObject*& rpRootObj) const +{ + return (*this).CheckSingleSdrObjectHit(rPnt,nTol,pOL,pPV,nOptions,pMVisLay,rpRootObj,nullptr); +} +SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObjList const * pOL, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay, SdrObject*& rpRootObj,const SdrMarkList * pMarkList) const +{ + SdrObject* pRet=nullptr; + rpRootObj=nullptr; + if (!pOL) + return nullptr; + const E3dScene* pRemapScene = DynCastE3dScene(pOL->getSdrObjectFromSdrObjList()); + const size_t nObjCount(pOL->GetObjCount()); + size_t nObjNum(nObjCount); + + while (pRet==nullptr && nObjNum>0) + { + nObjNum--; + SdrObject* pObj; + + if(pRemapScene) + { + pObj = pOL->GetObj(pRemapScene->RemapOrdNum(nObjNum)); + } + else + { + pObj = pOL->GetObj(nObjNum); + } + if (nOptions & SdrSearchOptions::BEFOREMARK) + { + if (pMarkList!=nullptr) + { + if ((*pMarkList).FindObject(pObj)!=SAL_MAX_SIZE) + { + return nullptr; + } + } + } + pRet=CheckSingleSdrObjectHit(rPnt,nTol,pObj,pPV,nOptions,pMVisLay); + if (pRet!=nullptr) rpRootObj=pObj; + } + return pRet; +} + +SdrObject* SdrMarkView::PickObj(const Point& rPnt, short nTol, SdrPageView*& rpPV, SdrSearchOptions nOptions) const +{ + return PickObj(rPnt, nTol, rpPV, nOptions, nullptr); +} + +SdrObject* SdrMarkView::PickObj(const Point& rPnt, short nTol, SdrPageView*& rpPV, SdrSearchOptions nOptions, SdrObject** ppRootObj, bool* pbHitPassDirect) const +{ // TODO: lacks a Pass2,Pass3 + SortMarkedObjects(); + if (ppRootObj!=nullptr) *ppRootObj=nullptr; + if (pbHitPassDirect!=nullptr) *pbHitPassDirect=true; + SdrObject* pRet = nullptr; + rpPV=nullptr; + bool bMarked(nOptions & SdrSearchOptions::MARKED); + bool bMasters=!bMarked && bool(nOptions & SdrSearchOptions::ALSOONMASTER); + // nOptions & SdrSearchOptions::NEXT: n.i. + // nOptions & SdrSearchOptions::PASS2BOUND: n.i. + // nOptions & SdrSearchOptions::PASS3NEAREST// n.i. + if (nTol<0) nTol=ImpGetHitTolLogic(nTol,nullptr); + SdrObject* pObj=nullptr; + SdrObject* pHitObj=nullptr; + SdrPageView* pPV=nullptr; + if (static_cast<const SdrObjEditView*>(this)->IsTextEditFrameHit(rPnt)) { + pObj=static_cast<const SdrObjEditView*>(this)->GetTextEditObject(); + pHitObj=pObj; + pPV=static_cast<const SdrObjEditView*>(this)->GetTextEditPageView(); + } + if (bMarked) { + const size_t nMrkCnt=GetMarkedObjectCount(); + size_t nMrkNum=nMrkCnt; + while (pHitObj==nullptr && nMrkNum>0) { + nMrkNum--; + SdrMark* pM=GetSdrMarkByIndex(nMrkNum); + pObj=pM->GetMarkedSdrObj(); + pPV=pM->GetPageView(); + pHitObj=CheckSingleSdrObjectHit(rPnt,nTol,pObj,pPV,nOptions,nullptr); + } + } + else + { + pPV = GetSdrPageView(); + + if(pPV) + { + SdrPage* pPage=pPV->GetPage(); + sal_uInt16 nPgCount=1; + + if(bMasters && pPage->TRG_HasMasterPage()) + { + nPgCount++; + } + bool bWholePage(nOptions & SdrSearchOptions::WHOLEPAGE); + bool bExtraPassForWholePage=bWholePage && pPage!=pPV->GetObjList(); + if (bExtraPassForWholePage) nPgCount++; // First search in AktObjList, then on the entire page + sal_uInt16 nPgNum=nPgCount; + while (pHitObj==nullptr && nPgNum>0) { + SdrSearchOptions nTmpOptions=nOptions; + nPgNum--; + const SdrLayerIDSet* pMVisLay=nullptr; + SdrObjList* pObjList=nullptr; + if (pbHitPassDirect!=nullptr) *pbHitPassDirect = true; + if (nPgNum>=nPgCount-1 || (bExtraPassForWholePage && nPgNum>=nPgCount-2)) + { + pObjList=pPV->GetObjList(); + if (bExtraPassForWholePage && nPgNum==nPgCount-2) { + pObjList=pPage; + if (pbHitPassDirect!=nullptr) *pbHitPassDirect = false; + } + } + else + { + // otherwise MasterPage + SdrPage& rMasterPage = pPage->TRG_GetMasterPage(); + pMVisLay = &pPage->TRG_GetMasterPageVisibleLayers(); + pObjList = &rMasterPage; + + if (pbHitPassDirect!=nullptr) *pbHitPassDirect = false; + nTmpOptions=nTmpOptions | SdrSearchOptions::IMPISMASTER; + } + pHitObj=CheckSingleSdrObjectHit(rPnt,nTol,pObjList,pPV,nTmpOptions,pMVisLay,pObj,&(GetMarkedObjectList())); + } + } + } + if (pHitObj!=nullptr) { + if (ppRootObj!=nullptr) *ppRootObj=pObj; + if (nOptions & SdrSearchOptions::DEEP) pObj=pHitObj; + if (nOptions & SdrSearchOptions::TESTTEXTEDIT) { + if (!pObj->HasTextEdit() || pPV->GetLockedLayers().IsSet(pObj->GetLayer())) { + pObj=nullptr; + } + } + if (pObj!=nullptr && (nOptions & SdrSearchOptions::TESTMACRO)) { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos=rPnt; + aHitRec.nTol=nTol; + aHitRec.pVisiLayer=&pPV->GetVisibleLayers(); + aHitRec.pPageView=pPV; + if (!pObj->HasMacro() || !pObj->IsMacroHit(aHitRec)) pObj=nullptr; + } + if (pObj!=nullptr) { + pRet=pObj; + rpPV=pPV; + } + } + return pRet; +} + +bool SdrMarkView::PickMarkedObj(const Point& rPnt, SdrObject*& rpObj, SdrPageView*& rpPV, SdrSearchOptions nOptions) const +{ + SortMarkedObjects(); + const bool bBoundCheckOn2ndPass(nOptions & SdrSearchOptions::PASS2BOUND); + rpObj=nullptr; + rpPV=nullptr; + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nMarkNum=nMarkCount; nMarkNum>0;) { + --nMarkNum; + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrPageView* pPV=pM->GetPageView(); + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (CheckSingleSdrObjectHit(rPnt,mnHitTolLog,pObj,pPV,SdrSearchOptions::TESTMARKABLE,nullptr)) { + rpObj=pObj; + rpPV=pPV; + return true; + } + } + if (bBoundCheckOn2ndPass) { + for (size_t nMarkNum=nMarkCount; nMarkNum>0;) { + --nMarkNum; + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrPageView* pPV=pM->GetPageView(); + SdrObject* pObj=pM->GetMarkedSdrObj(); + tools::Rectangle aRect(pObj->GetCurrentBoundRect()); + aRect.AdjustLeft( -mnHitTolLog ); + aRect.AdjustTop( -mnHitTolLog ); + aRect.AdjustRight(mnHitTolLog ); + aRect.AdjustBottom(mnHitTolLog ); + if (aRect.Contains(rPnt)) { + rpObj=pObj; + rpPV=pPV; + return true; + } + } + } + return false; +} + + +void SdrMarkView::UnmarkAllObj(SdrPageView const * pPV) +{ + if (GetMarkedObjectCount()==0) + return; + + BrkAction(); + if (pPV!=nullptr) + { + GetMarkedObjectListWriteAccess().DeletePageView(*pPV); + } + else + { + GetMarkedObjectListWriteAccess().Clear(); + } + mpMarkedObj=nullptr; + mpMarkedPV=nullptr; + MarkListHasChanged(); + AdjustMarkHdl(); +} + +void SdrMarkView::MarkAllObj(SdrPageView* pPV) +{ + BrkAction(); + + if(!pPV) + { + pPV = GetSdrPageView(); + } + + // #i69171# pPV may still be NULL if there is no SDrPageView (!), e.g. when inserting + // other files + if(pPV) + { + const bool bMarkChg(GetMarkedObjectListWriteAccess().InsertPageView(*pPV)); + + if(bMarkChg) + { + MarkListHasChanged(); + } + } + + if(GetMarkedObjectCount()) + { + AdjustMarkHdl(); + } +} + +void SdrMarkView::AdjustMarkHdl(SfxViewShell* pOtherShell) +{ + CheckMarked(); + SetMarkRects(); + SetMarkHandles(pOtherShell); +} + +// BoundRect in model coordinates, no GridOffset added +tools::Rectangle SdrMarkView::GetMarkedObjBoundRect() const +{ + tools::Rectangle aRect; + for (size_t nm=0; nm<GetMarkedObjectCount(); ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO=pM->GetMarkedSdrObj(); + tools::Rectangle aR1(pO->GetCurrentBoundRect()); + if (aRect.IsEmpty()) aRect=aR1; + else aRect.Union(aR1); + } + return aRect; +} + +// ObjRect in model coordinates, no GridOffset added +const tools::Rectangle& SdrMarkView::GetMarkedObjRect() const +{ + if (mbMarkedObjRectDirty) { + const_cast<SdrMarkView*>(this)->mbMarkedObjRectDirty=false; + tools::Rectangle aRect; + for (size_t nm=0; nm<GetMarkedObjectCount(); ++nm) { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pO = pM->GetMarkedSdrObj(); + if (!pO) + continue; + tools::Rectangle aR1(pO->GetSnapRect()); + if (aRect.IsEmpty()) aRect=aR1; + else aRect.Union(aR1); + } + const_cast<SdrMarkView*>(this)->maMarkedObjRect=aRect; + } + return maMarkedObjRect; +} + + +OUString SdrMarkView::ImpGetDescriptionString(TranslateId pStrCacheID, ImpGetDescriptionOptions nOpt) const +{ + OUString sStr = SvxResId(pStrCacheID); + const sal_Int32 nPos = sStr.indexOf("%1"); + + if(nPos != -1) + { + if(nOpt == ImpGetDescriptionOptions::POINTS) + { + sStr = sStr.replaceAt(nPos, 2, GetDescriptionOfMarkedPoints()); + } + else if(nOpt == ImpGetDescriptionOptions::GLUEPOINTS) + { + sStr = sStr.replaceAt(nPos, 2, GetDescriptionOfMarkedGluePoints()); + } + else + { + sStr = sStr.replaceAt(nPos, 2, GetDescriptionOfMarkedObjects()); + } + } + + return sStr.replaceFirst("%2", "0"); +} + + +void SdrMarkView::EnterMarkedGroup() +{ + // We enter only the first group found (in only one PageView), because + // PageView::EnterGroup calls an AdjustMarkHdl. + // TODO: I'll have to prevent that via a flag. + SdrPageView* pPV = GetSdrPageView(); + + if(!pPV) + return; + + bool bEnter=false; + for (size_t nm = GetMarkedObjectCount(); nm > 0 && !bEnter;) + { + --nm; + SdrMark* pM=GetSdrMarkByIndex(nm); + if (pM->GetPageView()==pPV) { + SdrObject* pObj=pM->GetMarkedSdrObj(); + if (pObj->IsGroupObject()) { + if (pPV->EnterGroup(pObj)) { + bEnter=true; + } + } + } + } +} + + +void SdrMarkView::MarkListHasChanged() +{ + GetMarkedObjectListWriteAccess().SetNameDirty(); + maSdrViewSelection.SetEdgesOfMarkedNodesDirty(); + + mbMarkedObjRectDirty=true; + mbMarkedPointsRectsDirty=true; + bool bOneEdgeMarked=false; + if (GetMarkedObjectCount()==1) { + const SdrObject* pObj=GetMarkedObjectByIndex(0); + if (pObj->GetObjInventor()==SdrInventor::Default) { + bOneEdgeMarked = pObj->GetObjIdentifier() == SdrObjKind::Edge; + } + } + ImpSetGlueVisible4(bOneEdgeMarked); +} + + +void SdrMarkView::SetMoveOutside(bool bOn) +{ + maHdlList.SetMoveOutside(bOn); +} + +void SdrMarkView::SetDesignMode( bool bOn ) +{ + if ( mbDesignMode != bOn ) + { + mbDesignMode = bOn; + SdrPageView* pPageView = GetSdrPageView(); + if ( pPageView ) + pPageView->SetDesignMode( bOn ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdmrkv1.cxx b/svx/source/svdraw/svdmrkv1.cxx new file mode 100644 index 0000000000..9c732262bc --- /dev/null +++ b/svx/source/svdraw/svdmrkv1.cxx @@ -0,0 +1,547 @@ +/* -*- 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/svdmrkv.hxx> +#include <svx/svdpagv.hxx> +#include <osl/diagnose.h> + + +// Point Selection + + +bool SdrMarkView::HasMarkablePoints() const +{ + ForceUndirtyMrkPnt(); + bool bRet=false; + if (!ImpIsFrameHandles()) { + const size_t nMarkCount=GetMarkedObjectCount(); + if (nMarkCount<=static_cast<size_t>(mnFrameHandlesLimit)) { + for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bRet; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + bRet=pObj->IsPolyObj(); + } + } + } + return bRet; +} + +sal_Int32 SdrMarkView::GetMarkablePointCount() const +{ + ForceUndirtyMrkPnt(); + sal_Int32 nCount=0; + if (!ImpIsFrameHandles()) { + const size_t nMarkCount=GetMarkedObjectCount(); + if (nMarkCount<=static_cast<size_t>(mnFrameHandlesLimit)) { + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + if (pObj->IsPolyObj()) { + nCount+=pObj->GetPointCount(); + } + } + } + } + return nCount; +} + +bool SdrMarkView::HasMarkedPoints() const +{ + ForceUndirtyMrkPnt(); + bool bRet=false; + if (!ImpIsFrameHandles()) { + const size_t nMarkCount=GetMarkedObjectCount(); + if (nMarkCount<=static_cast<size_t>(mnFrameHandlesLimit)) { + for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bRet; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrUShortCont& rPts = pM->GetMarkedPoints(); + bRet = !rPts.empty(); + } + } + } + return bRet; +} + +bool SdrMarkView::IsPointMarkable(const SdrHdl& rHdl) const +{ + return !ImpIsFrameHandles() && !rHdl.IsPlusHdl() && rHdl.GetKind()!=SdrHdlKind::Glue && rHdl.GetKind()!=SdrHdlKind::SmartTag && rHdl.GetObj()!=nullptr && rHdl.GetObj()->IsPolyObj(); +} + +bool SdrMarkView::MarkPointHelper(SdrHdl* pHdl, SdrMark* pMark, bool bUnmark) +{ + return ImpMarkPoint( pHdl, pMark, bUnmark ); +} + +bool SdrMarkView::ImpMarkPoint(SdrHdl* pHdl, SdrMark* pMark, bool bUnmark) +{ + if (pHdl==nullptr || pHdl->IsPlusHdl() || pHdl->GetKind()==SdrHdlKind::Glue) + return false; + + if (pHdl->IsSelected() != bUnmark) + return false; + + SdrObject* pObj=pHdl->GetObj(); + if (pObj==nullptr || !pObj->IsPolyObj()) + return false; + + if (pMark==nullptr) + { + const size_t nMarkNum=TryToFindMarkedObject(pObj); + if (nMarkNum==SAL_MAX_SIZE) + return false; + pMark=GetSdrMarkByIndex(nMarkNum); + } + const sal_uInt32 nHdlNum(pHdl->GetObjHdlNum()); + SdrUShortCont& rPts=pMark->GetMarkedPoints(); + if (!bUnmark) + { + rPts.insert(static_cast<sal_uInt16>(nHdlNum)); + } + else + { + SdrUShortCont::const_iterator it = rPts.find( static_cast<sal_uInt16>(nHdlNum) ); + if (it != rPts.end()) + { + rPts.erase(it); + } + else + { + return false; // error case! + } + } + + pHdl->SetSelected(!bUnmark); + if (!mbPlusHdlAlways) + { + if (!bUnmark) + { + SdrHdlList plusList(nullptr); + pObj->AddToPlusHdlList(plusList, *pHdl); + sal_uInt32 nCount(plusList.GetHdlCount()); + for (sal_uInt32 i=0; i<nCount; i++) + { + SdrHdl* pPlusHdl=plusList.GetHdl(i); + pPlusHdl->SetObj(pObj); + pPlusHdl->SetPageView(pMark->GetPageView()); + pPlusHdl->SetPlusHdl(true); + } + plusList.MoveTo(maHdlList); + } + else + { + for (size_t i = maHdlList.GetHdlCount(); i>0;) + { + --i; + SdrHdl* pPlusHdl=maHdlList.GetHdl(i); + if (pPlusHdl->IsPlusHdl() && pPlusHdl->GetSourceHdlNum()==nHdlNum) + { + maHdlList.RemoveHdl(i); + } + } + } + } + + maHdlList.Sort(); + + return true; +} + + +bool SdrMarkView::MarkPoint(SdrHdl& rHdl, bool bUnmark) +{ + ForceUndirtyMrkPnt(); + bool bRet=false; + const SdrObject* pObj=rHdl.GetObj(); + if (IsPointMarkable(rHdl) && rHdl.IsSelected()==bUnmark) { + const size_t nMarkNum=TryToFindMarkedObject(pObj); + if (nMarkNum!=SAL_MAX_SIZE) { + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + if (ImpMarkPoint(&rHdl,pM,bUnmark)) { + MarkListHasChanged(); + bRet=true; + } + } + } + + return bRet; +} + +bool SdrMarkView::MarkPoints(const tools::Rectangle* pRect, bool bUnmark) +{ + ForceUndirtyMrkPnt(); + bool bChgd=false; + SortMarkedObjects(); + const SdrObject* pObj0=nullptr; + const SdrPageView* pPV0=nullptr; + SdrMark* pM=nullptr; + maHdlList.Sort(); + const size_t nHdlCnt=maHdlList.GetHdlCount(); + for (size_t nHdlNum=nHdlCnt; nHdlNum>0;) { + --nHdlNum; + SdrHdl* pHdl=maHdlList.GetHdl(nHdlNum); + if (IsPointMarkable(*pHdl) && pHdl->IsSelected()==bUnmark) { + const SdrObject* pObj=pHdl->GetObj(); + const SdrPageView* pPV=pHdl->GetPageView(); + if (pObj!=pObj0 || pPV!=pPV0 || pM==nullptr) { // This section is for optimization, + const size_t nMarkNum=TryToFindMarkedObject(pObj); // so ImpMarkPoint() doesn't always + if (nMarkNum!=SAL_MAX_SIZE) { // have to search the object in the MarkList. + pM=GetSdrMarkByIndex(nMarkNum); + pObj0=pObj; + pPV0=pPV; + } else { +#ifdef DBG_UTIL + if (pObj->IsInserted()) { + OSL_FAIL("SdrMarkView::MarkPoints(const Rectangle* pRect): Selected object not found."); + } +#endif + pM=nullptr; + } + } + Point aPos(pHdl->GetPos()); + if (pM!=nullptr && (pRect==nullptr || pRect->Contains(aPos))) { + if (ImpMarkPoint(pHdl,pM,bUnmark)) bChgd=true; + } + } + } + if (bChgd) { + MarkListHasChanged(); + } + + return bChgd; +} + +void SdrMarkView::MarkNextPoint() +{ + ForceUndirtyMrkPnt(); + SortMarkedObjects(); +} + +const tools::Rectangle& SdrMarkView::GetMarkedPointsRect() const +{ + ForceUndirtyMrkPnt(); + if (mbMarkedPointsRectsDirty) ImpSetPointsRects(); + return maMarkedPointsRect; +} + +void SdrMarkView::SetPlusHandlesAlwaysVisible(bool bOn) +{ // TODO: Optimize HandlePaint! + ForceUndirtyMrkPnt(); + if (bOn!=mbPlusHdlAlways) { + mbPlusHdlAlways=bOn; + SetMarkHandles(nullptr); + MarkListHasChanged(); + } +} + + +// ImpSetPointsRects() is for PolyPoints and GluePoints! + + +void SdrMarkView::ImpSetPointsRects() const +{ + tools::Rectangle aPnts; + tools::Rectangle aGlue; + const size_t nHdlCnt=maHdlList.GetHdlCount(); + for (size_t nHdlNum=0; nHdlNum<nHdlCnt; ++nHdlNum) { + const SdrHdl* pHdl=maHdlList.GetHdl(nHdlNum); + SdrHdlKind eKind=pHdl->GetKind(); + if ((eKind==SdrHdlKind::Poly && pHdl->IsSelected()) || eKind==SdrHdlKind::Glue) { + Point aPt(pHdl->GetPos()); + tools::Rectangle& rR=eKind==SdrHdlKind::Glue ? aGlue : aPnts; + if (rR.IsEmpty()) { + rR=tools::Rectangle(aPt,aPt); + } else { + if (aPt.X()<rR.Left ()) rR.SetLeft(aPt.X() ); + if (aPt.X()>rR.Right ()) rR.SetRight(aPt.X() ); + if (aPt.Y()<rR.Top ()) rR.SetTop(aPt.Y() ); + if (aPt.Y()>rR.Bottom()) rR.SetBottom(aPt.Y() ); + } + } + } + const_cast<SdrMarkView*>(this)->maMarkedPointsRect=aPnts; + const_cast<SdrMarkView*>(this)->maMarkedGluePointsRect=aGlue; + const_cast<SdrMarkView*>(this)->mbMarkedPointsRectsDirty=false; +} + + +// UndirtyMrkPnt() is for PolyPoints and GluePoints! + + +void SdrMarkView::UndirtyMrkPnt() const +{ + bool bChg=false; + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) { + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + // PolyPoints + { + SdrUShortCont& rPts = pM->GetMarkedPoints(); + if (pObj->IsPolyObj()) { + // Remove invalid selected points, that is, all + // entries above the number of points in the object. + sal_uInt32 nMax(pObj->GetPointCount()); + + SdrUShortCont::const_iterator it = rPts.lower_bound(nMax); + if( it != rPts.end() ) + { + rPts.erase(it, rPts.end()); + bChg = true; + } + } + else + { + if (!rPts.empty()) + { + // only fail *if* there are marked points + OSL_FAIL("SdrMarkView::UndirtyMrkPnt(): Selected points on an object that is not a PolyObj!"); + rPts.clear(); + bChg = true; + } + } + } + + // GluePoints + { + SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + if (pGPL!=nullptr) { + // Remove invalid selected gluepoints, that is, all entries + // (IDs) that aren't contained in the GluePointList of the + // object + for(SdrUShortCont::const_iterator it = rPts.begin(); it != rPts.end(); ) + { + sal_uInt16 nId=*it; + if (pGPL->FindGluePoint(nId)==SDRGLUEPOINT_NOTFOUND) { + it = rPts.erase(it); + bChg=true; + } + else + ++it; + } + } else { + if (!rPts.empty()) { + rPts.clear(); // object doesn't have any gluepoints (any more) + bChg=true; + } + } + } + } + if (bChg) const_cast<SdrMarkView*>(this)->mbMarkedPointsRectsDirty=true; + const_cast<SdrMarkView*>(this)->mbMrkPntDirty=false; +} + + +bool SdrMarkView::HasMarkableGluePoints() const +{ + bool bRet=false; + if (IsGluePointEditMode()) { + ForceUndirtyMrkPnt(); + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bRet; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + + // #i38892# + if(pGPL && pGPL->GetCount()) + { + for(sal_uInt16 a(0); !bRet && a < pGPL->GetCount(); a++) + { + if((*pGPL)[a].IsUserDefined()) + { + bRet = true; + } + } + } + } + } + return bRet; +} + +bool SdrMarkView::HasMarkedGluePoints() const +{ + ForceUndirtyMrkPnt(); + bool bRet=false; + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bRet; ++nMarkNum) { + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + bRet = !rPts.empty(); + } + return bRet; +} + +bool SdrMarkView::MarkGluePoints(const tools::Rectangle* pRect, bool bUnmark) +{ + if (!IsGluePointEditMode() && !bUnmark) return false; + ForceUndirtyMrkPnt(); + bool bChgd=false; + SortMarkedObjects(); + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum) { + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + if (bUnmark && pRect==nullptr) { // UnmarkAll + if (!rPts.empty()) { + rPts.clear(); + bChgd=true; + } + } else { + if (pGPL!=nullptr) { + sal_uInt16 nGluePointCnt=pGPL->GetCount(); + for (sal_uInt16 nGPNum=0; nGPNum<nGluePointCnt; nGPNum++) { + const SdrGluePoint& rGP=(*pGPL)[nGPNum]; + + // #i38892# + if(rGP.IsUserDefined()) + { + Point aPos(rGP.GetAbsolutePos(*pObj)); + if (pRect==nullptr || pRect->Contains(aPos)) { + bool bContains = rPts.find( rGP.GetId() ) != rPts.end(); + if (!bUnmark && !bContains) { + bChgd=true; + rPts.insert(rGP.GetId()); + } + if (bUnmark && bContains) { + bChgd=true; + rPts.erase(rGP.GetId()); + } + } + } + } + } + } + } + if (bChgd) { + AdjustMarkHdl(); + MarkListHasChanged(); + } + return bChgd; +} + +bool SdrMarkView::PickGluePoint(const Point& rPnt, SdrObject*& rpObj, sal_uInt16& rnId, SdrPageView*& rpPV) const +{ + rpObj=nullptr; rpPV=nullptr; rnId=0; + if (!IsGluePointEditMode()) return false; + OutputDevice* pOut=mpActualOutDev.get(); + if (pOut==nullptr) pOut=GetFirstOutputDevice(); + if (pOut==nullptr) return false; + SortMarkedObjects(); + const size_t nMarkCount=GetMarkedObjectCount(); + size_t nMarkNum=nMarkCount; + while (nMarkNum>0) { + nMarkNum--; + const SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPageView* pPV=pM->GetPageView(); + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + if (pGPL!=nullptr) { + sal_uInt16 nNum=pGPL->HitTest(rPnt,*pOut,pObj); + if (nNum!=SDRGLUEPOINT_NOTFOUND) + { + // #i38892# + const SdrGluePoint& rCandidate = (*pGPL)[nNum]; + + if(rCandidate.IsUserDefined()) + { + rpObj=pObj; + rnId=(*pGPL)[nNum].GetId(); + rpPV=pPV; + return true; + } + } + } + } + return false; +} + +bool SdrMarkView::MarkGluePoint(const SdrObject* pObj, sal_uInt16 nId, bool bUnmark) +{ + if (!IsGluePointEditMode()) return false; + ForceUndirtyMrkPnt(); + bool bChgd=false; + if (pObj!=nullptr) { + const size_t nMarkPos=TryToFindMarkedObject(pObj); + if (nMarkPos!=SAL_MAX_SIZE) { + SdrMark* pM=GetSdrMarkByIndex(nMarkPos); + SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + bool bContains = rPts.find( nId ) != rPts.end(); + if (!bUnmark && !bContains) { + bChgd=true; + rPts.insert(nId); + } + if (bUnmark && bContains) { + bChgd=true; + rPts.erase(nId); + } + } else { + // TODO: implement implicit selection of objects + } + } + if (bChgd) { + AdjustMarkHdl(); + MarkListHasChanged(); + } + return bChgd; +} + +bool SdrMarkView::IsGluePointMarked(const SdrObject* pObj, sal_uInt16 nId) const +{ + ForceUndirtyMrkPnt(); + bool bRet=false; + const size_t nPos=TryToFindMarkedObject(pObj); // casting to NonConst + if (nPos!=SAL_MAX_SIZE) { + const SdrMark* pM=GetSdrMarkByIndex(nPos); + const SdrUShortCont& rPts = pM->GetMarkedGluePoints(); + bRet = rPts.find( nId ) != rPts.end(); + } + return bRet; +} + +SdrHdl* SdrMarkView::GetGluePointHdl(const SdrObject* pObj, sal_uInt16 nId) const +{ + ForceUndirtyMrkPnt(); + const size_t nHdlCnt=maHdlList.GetHdlCount(); + for (size_t nHdlNum=0; nHdlNum<nHdlCnt; ++nHdlNum) { + SdrHdl* pHdl=maHdlList.GetHdl(nHdlNum); + if (pHdl->GetObj()==pObj && + pHdl->GetKind()==SdrHdlKind::Glue && + pHdl->GetObjHdlNum()==nId ) return pHdl; + } + return nullptr; +} + +void SdrMarkView::MarkNextGluePoint() +{ + ForceUndirtyMrkPnt(); + SortMarkedObjects(); +} + +const tools::Rectangle& SdrMarkView::GetMarkedGluePointsRect() const +{ + ForceUndirtyMrkPnt(); + if (mbMarkedPointsRectsDirty) ImpSetPointsRects(); + return maMarkedGluePointsRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoashp.cxx b/svx/source/svdraw/svdoashp.cxx new file mode 100644 index 0000000000..eeaa557157 --- /dev/null +++ b/svx/source/svdraw/svdoashp.cxx @@ -0,0 +1,3267 @@ +/* -*- 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 <vcl/BitmapShadowFilter.hxx> +#include <svx/svdoashp.hxx> +#include <svx/unoapi.hxx> +#include <com/sun/star/loader/CannotActivateFactoryException.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XCustomShapeEngine.hpp> +#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <tools/helpers.hxx> +#include <svx/svddrag.hxx> +#include <svx/svddrgmt.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svditer.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdtrans.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <editeng/eeitem.hxx> +#include <editeng/editstat.hxx> +#include <editeng/adjustitem.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/outlobj.hxx> +#include <svx/sdtfchim.hxx> +#include <svx/EnhancedCustomShapeGeometry.hxx> +#include <svx/EnhancedCustomShapeTypeNames.hxx> +#include <svx/EnhancedCustomShape2d.hxx> +#include <com/sun/star/beans/PropertyValues.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> +#include <editeng/writingmodeitem.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnclit.hxx> +#include <sdr/properties/customshapeproperties.hxx> +#include <sdr/contact/viewcontactofsdrobjcustomshape.hxx> +#include <svx/xlntrit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xfltrit.hxx> +#include <svx/xflclit.hxx> +#include <svx/xflgrit.hxx> +#include <svx/xflhtit.hxx> +#include <svx/xbtmpit.hxx> +#include <vcl/virdev.hxx> +#include <svx/svdview.hxx> +#include <svx/sdmetitm.hxx> +#include <svx/sdprcitm.hxx> +#include <svx/sdshitm.hxx> +#include <svx/sdsxyitm.hxx> +#include <svx/sdtmfitm.hxx> +#include <svx/sdasitm.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <svdobjplusdata.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include "presetooxhandleadjustmentrelations.hxx" +#include <editeng/frmdiritem.hxx> + +using namespace ::com::sun::star; + +static void lcl_ShapeSegmentFromBinary( drawing::EnhancedCustomShapeSegment& rSegInfo, sal_uInt16 nSDat ) +{ + switch( nSDat >> 8 ) + { + case 0x00 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO; + rSegInfo.Count = nSDat & 0xff; + if ( !rSegInfo.Count ) + rSegInfo.Count = 1; + break; + case 0x20 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO; + rSegInfo.Count = nSDat & 0xff; + if ( !rSegInfo.Count ) + rSegInfo.Count = 1; + break; + case 0x40 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO; + rSegInfo.Count = nSDat & 0xff; + if ( !rSegInfo.Count ) + rSegInfo.Count = 1; + break; + case 0x60 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH; + rSegInfo.Count = 0; + break; + case 0x80 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH; + rSegInfo.Count = 0; + break; + case 0xa1 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO; + rSegInfo.Count = ( nSDat & 0xff ) / 3; + break; + case 0xa2 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE; + rSegInfo.Count = ( nSDat & 0xff ) / 3; + break; + case 0xa3 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO; + rSegInfo.Count = ( nSDat & 0xff ) >> 2; + break; + case 0xa4 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARC; + rSegInfo.Count = ( nSDat & 0xff ) >> 2; + break; + case 0xa5 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO; + rSegInfo.Count = ( nSDat & 0xff ) >> 2; + break; + case 0xa6 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC; + rSegInfo.Count = ( nSDat & 0xff ) >> 2; + break; + case 0xa7 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX; + rSegInfo.Count = nSDat & 0xff; + break; + case 0xa8 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY; + rSegInfo.Count = nSDat & 0xff; + break; + case 0xaa : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOFILL; + rSegInfo.Count = 0; + break; + case 0xab : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOSTROKE; + rSegInfo.Count = 0; + break; + default: + case 0xf8 : + rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::UNKNOWN; + rSegInfo.Count = nSDat; + break; + } +} + +static MSO_SPT ImpGetCustomShapeType( const SdrObjCustomShape& rCustoShape ) +{ + MSO_SPT eRetValue = mso_sptNil; + + OUString aEngine( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue() ); + if ( aEngine.isEmpty() || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine" ) + { + OUString sShapeType; + const SdrCustomShapeGeometryItem& rGeometryItem( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "Type" ); + if ( pAny && ( *pAny >>= sShapeType ) ) + eRetValue = EnhancedCustomShapeTypeNames::Get( sShapeType ); + } + return eRetValue; +}; + +static bool ImpVerticalSwitch( const SdrObjCustomShape& rCustoShape ) +{ + bool bRet = false; + MSO_SPT eShapeType( ImpGetCustomShapeType( rCustoShape ) ); + switch( eShapeType ) + { + case mso_sptAccentBorderCallout90 : // 2 ortho + case mso_sptBorderCallout1 : // 2 diag + case mso_sptBorderCallout2 : // 3 + { + bRet = true; + } + break; + default: break; + } + return bRet; +} + +// #i37011# create a clone with all attributes changed to shadow attributes +// and translation executed, too. +static rtl::Reference<SdrObject> ImpCreateShadowObjectClone(const SdrObject& rOriginal, const SfxItemSet& rOriginalSet) +{ + rtl::Reference<SdrObject> pRetval; + const bool bShadow(rOriginalSet.Get(SDRATTR_SHADOW).GetValue()); + + if(bShadow) + { + // create a shadow representing object + const sal_Int32 nXDist(rOriginalSet.Get(SDRATTR_SHADOWXDIST).GetValue()); + const sal_Int32 nYDist(rOriginalSet.Get(SDRATTR_SHADOWYDIST).GetValue()); + const ::Color aShadowColor(rOriginalSet.Get(SDRATTR_SHADOWCOLOR).GetColorValue()); + const sal_uInt16 nShadowTransparence(rOriginalSet.Get(SDRATTR_SHADOWTRANSPARENCE).GetValue()); + pRetval = rOriginal.CloneSdrObject(rOriginal.getSdrModelFromSdrObject()); + DBG_ASSERT(pRetval, "ImpCreateShadowObjectClone: Could not clone object (!)"); + + // look for used stuff + SdrObjListIter aIterator(rOriginal); + bool bLineUsed(false); + bool bAllFillUsed(false); + bool bSolidFillUsed(false); + bool bGradientFillUsed(false); + bool bHatchFillUsed(false); + bool bBitmapFillUsed(false); + + while(aIterator.IsMore()) + { + SdrObject* pObj = aIterator.Next(); + drawing::FillStyle eFillStyle = pObj->GetMergedItem(XATTR_FILLSTYLE).GetValue(); + + if(!bLineUsed) + { + drawing::LineStyle eLineStyle = pObj->GetMergedItem(XATTR_LINESTYLE).GetValue(); + + if(drawing::LineStyle_NONE != eLineStyle) + { + bLineUsed = true; + } + } + + if(!bAllFillUsed) + { + if(!bSolidFillUsed && drawing::FillStyle_SOLID == eFillStyle) + { + bSolidFillUsed = true; + bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed); + } + if(!bGradientFillUsed && drawing::FillStyle_GRADIENT == eFillStyle) + { + bGradientFillUsed = true; + bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed); + } + if(!bHatchFillUsed && drawing::FillStyle_HATCH == eFillStyle) + { + bHatchFillUsed = true; + bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed); + } + if(!bBitmapFillUsed && drawing::FillStyle_BITMAP == eFillStyle) + { + bBitmapFillUsed = true; + bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed); + } + } + } + + // translate to shadow coordinates + pRetval->NbcMove(Size(nXDist, nYDist)); + + // set items as needed + SfxItemSet aTempSet(rOriginalSet); + + // if a SvxWritingModeItem (Top->Bottom) is set the text object + // is creating a paraobject, but paraobjects can not be created without model. So + // we are preventing the crash by setting the writing mode always left to right, + // this is not bad since our shadow geometry does not contain text. + aTempSet.Put(SvxWritingModeItem(text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION)); + + // no shadow + aTempSet.Put(makeSdrShadowItem(false)); + aTempSet.Put(makeSdrShadowXDistItem(0)); + aTempSet.Put(makeSdrShadowYDistItem(0)); + + // line color and transparency like shadow + if(bLineUsed) + { + aTempSet.Put(XLineColorItem(OUString(), aShadowColor)); + aTempSet.Put(XLineTransparenceItem(nShadowTransparence)); + } + + // fill color and transparency like shadow + if(bSolidFillUsed) + { + aTempSet.Put(XFillColorItem(OUString(), aShadowColor)); + aTempSet.Put(XFillTransparenceItem(nShadowTransparence)); + } + + // gradient and transparency like shadow + if(bGradientFillUsed) + { + basegfx::BGradient aGradient(rOriginalSet.Get(XATTR_FILLGRADIENT).GetGradientValue()); + sal_uInt8 nStartLuminance(Color(aGradient.GetColorStops().front().getStopColor()).GetLuminance()); + sal_uInt8 nEndLuminance(Color(aGradient.GetColorStops().back().getStopColor()).GetLuminance()); + + if(aGradient.GetStartIntens() != 100) + { + nStartLuminance = static_cast<sal_uInt8>(nStartLuminance * (static_cast<double>(aGradient.GetStartIntens()) / 100.0)); + } + + if(aGradient.GetEndIntens() != 100) + { + nEndLuminance = static_cast<sal_uInt8>(nEndLuminance * (static_cast<double>(aGradient.GetEndIntens()) / 100.0)); + } + + ::Color aStartColor( + static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetRed()) / 256), + static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetGreen()) / 256), + static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetBlue()) / 256)); + + ::Color aEndColor( + static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetRed()) / 256), + static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetGreen()) / 256), + static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetBlue()) / 256)); + + aGradient.SetColorStops( + basegfx::BColorStops( + aStartColor.getBColor(), + aEndColor.getBColor())); + aTempSet.Put(XFillGradientItem(aGradient)); + aTempSet.Put(XFillTransparenceItem(nShadowTransparence)); + } + + // hatch and transparency like shadow + if(bHatchFillUsed) + { + XHatch aHatch(rOriginalSet.Get(XATTR_FILLHATCH).GetHatchValue()); + aHatch.SetColor(aShadowColor); + aTempSet.Put(XFillHatchItem(aHatch)); + aTempSet.Put(XFillTransparenceItem(nShadowTransparence)); + } + + // bitmap and transparency like shadow + if(bBitmapFillUsed) + { + GraphicObject aGraphicObject(rOriginalSet.Get(XATTR_FILLBITMAP).GetGraphicObject()); + BitmapEx aBitmapEx(aGraphicObject.GetGraphic().GetBitmapEx()); + + if(!aBitmapEx.IsEmpty()) + { + ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create()); + pVirDev->SetOutputSizePixel(aBitmapEx.GetSizePixel()); + BitmapFilter::Filter(aBitmapEx, BitmapShadowFilter(aShadowColor)); + pVirDev->DrawBitmapEx(Point(), aBitmapEx); + aGraphicObject.SetGraphic(Graphic(pVirDev->GetBitmapEx(Point(0,0), aBitmapEx.GetSizePixel()))); + } + + aTempSet.Put(XFillBitmapItem(aGraphicObject)); + aTempSet.Put(XFillTransparenceItem(nShadowTransparence)); + } + + // set attributes and paint shadow object + pRetval->SetMergedItemSet( aTempSet ); + } + return pRetval; +} + + +uno::Reference<drawing::XCustomShapeEngine> const & SdrObjCustomShape::GetCustomShapeEngine() const +{ + if (mxCustomShapeEngine.is()) + return mxCustomShapeEngine; + + uno::Reference<drawing::XShape> aXShape = GetXShapeForSdrObject(const_cast<SdrObjCustomShape*>(this)); + if ( !aXShape ) + return mxCustomShapeEngine; + + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + + OUString aEngine(GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue()); + static constexpr OUStringLiteral sEnhancedCustomShapeEngine = u"com.sun.star.drawing.EnhancedCustomShapeEngine"; + if ( aEngine.isEmpty() ) + aEngine = sEnhancedCustomShapeEngine; + + { + static constexpr OUString sCustomShape = u"CustomShape"_ustr; + uno::Sequence<beans::PropertyValue> aPropValues{ comphelper::makePropertyValue(sCustomShape, + aXShape) }; + uno::Sequence<uno::Any> aArgument{ uno::Any(aPropValues) }; + try + { + uno::Reference<uno::XInterface> xInterface(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aEngine, aArgument, xContext)); + if (xInterface.is()) + mxCustomShapeEngine.set(xInterface, uno::UNO_QUERY); + } + catch (const loader::CannotActivateFactoryException&) + { + } + } + + return mxCustomShapeEngine; +} + +const SdrObject* SdrObjCustomShape::GetSdrObjectFromCustomShape() const +{ + if ( !mXRenderedCustomShape.is() ) + { + uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() ); + if ( xCustomShapeEngine.is() ) + const_cast<SdrObjCustomShape*>(this)->mXRenderedCustomShape = xCustomShapeEngine->render(); + } + SdrObject* pRenderedCustomShape = mXRenderedCustomShape.is() + ? SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape ) + : nullptr; + return pRenderedCustomShape; +} + +// #i37011# Shadow geometry creation +const SdrObject* SdrObjCustomShape::GetSdrObjectShadowFromCustomShape() const +{ + if(!mpLastShadowGeometry) + { + const SdrObject* pSdrObject = GetSdrObjectFromCustomShape(); + if(pSdrObject) + { + const SfxItemSet& rOriginalSet = GetObjectItemSet(); + const bool bShadow(rOriginalSet.Get( SDRATTR_SHADOW ).GetValue()); + + if(bShadow) + { + // create a clone with all attributes changed to shadow attributes + // and translation executed, too. + const_cast<SdrObjCustomShape*>(this)->mpLastShadowGeometry = + ImpCreateShadowObjectClone(*pSdrObject, rOriginalSet); + } + } + } + + return mpLastShadowGeometry.get(); +} + +bool SdrObjCustomShape::IsTextPath() const +{ + static constexpr OUString sTextPath( u"TextPath"_ustr ); + bool bTextPathOn = false; + const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sTextPath, sTextPath ); + if ( pAny ) + *pAny >>= bTextPathOn; + return bTextPathOn; +} + +bool SdrObjCustomShape::UseNoFillStyle() const +{ + bool bRet = false; + OUString sShapeType; + static constexpr OUString sType( u"Type"_ustr ); + const SdrCustomShapeGeometryItem& rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sType ); + if ( pAny ) + *pAny >>= sShapeType; + bRet = !IsCustomShapeFilledByDefault( EnhancedCustomShapeTypeNames::Get( sType ) ); + + return bRet; +} + +bool SdrObjCustomShape::IsMirroredX() const +{ + bool bMirroredX = false; + const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "MirroredX" ); + if ( pAny ) + *pAny >>= bMirroredX; + return bMirroredX; +} +bool SdrObjCustomShape::IsMirroredY() const +{ + bool bMirroredY = false; + const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "MirroredY" ); + if ( pAny ) + *pAny >>= bMirroredY; + return bMirroredY; +} +void SdrObjCustomShape::SetMirroredX( const bool bMirrorX ) +{ + SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + beans::PropertyValue aPropVal; + aPropVal.Name = "MirroredX"; + aPropVal.Value <<= bMirrorX; + aGeometryItem.SetPropertyValue( aPropVal ); + SetMergedItem( aGeometryItem ); +} +void SdrObjCustomShape::SetMirroredY( const bool bMirrorY ) +{ + SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + beans::PropertyValue aPropVal; + aPropVal.Name = "MirroredY"; + aPropVal.Value <<= bMirrorY; + aGeometryItem.SetPropertyValue( aPropVal ); + SetMergedItem( aGeometryItem ); +} + +double SdrObjCustomShape::GetExtraTextRotation( const bool bPreRotation ) const +{ + double fExtraTextRotateAngle = 0.0; + if (bPreRotation) + { + // textPreRotateAngle might be set by macro or diagram (SmartArt) import + const uno::Any* pAny; + const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ); + pAny = rGeometryItem.GetPropertyValueByName(u"TextPreRotateAngle"_ustr); + if ( pAny ) + *pAny >>= fExtraTextRotateAngle; + + // As long as the edit engine is not able to render these text directions we + // emulate them by setting a suitable text pre-rotation. + const SvxFrameDirectionItem& rDirectionItem = GetMergedItem(SDRATTR_WRITINGMODE2); + if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_RL_TB90) + fExtraTextRotateAngle -= 90; + else if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_LR_BT) + fExtraTextRotateAngle -=270; + } + else + { + const uno::Any* pAny; + const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ); + pAny = rGeometryItem.GetPropertyValueByName(u"TextRotateAngle"_ustr); + if ( pAny ) + *pAny >>= fExtraTextRotateAngle; + } + return fExtraTextRotateAngle; +} + +bool SdrObjCustomShape::GetTextBounds( tools::Rectangle& rTextBound ) const +{ + bool bRet = false; + + uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() ); + if ( xCustomShapeEngine.is() ) + { + awt::Rectangle aR( xCustomShapeEngine->getTextBounds() ); + if ( aR.Width > 1 && aR.Height > 1 ) + { + rTextBound = tools::Rectangle( Point( aR.X, aR.Y ), Size( aR.Width, aR.Height ) ); + bRet = true; + } + } + return bRet; +} +basegfx::B2DPolyPolygon SdrObjCustomShape::GetLineGeometry( const bool bBezierAllowed ) const +{ + basegfx::B2DPolyPolygon aRetval; + uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() ); + if ( xCustomShapeEngine.is() ) + { + drawing::PolyPolygonBezierCoords aBezierCoords = xCustomShapeEngine->getLineGeometry(); + try + { + aRetval = basegfx::utils::UnoPolyPolygonBezierCoordsToB2DPolyPolygon( aBezierCoords ); + if ( !bBezierAllowed && aRetval.areControlPointsUsed()) + { + aRetval = basegfx::utils::adaptiveSubdivideByAngle(aRetval); + } + } + catch ( const lang::IllegalArgumentException & ) + { + } + } + return aRetval; +} + +std::vector< SdrCustomShapeInteraction > SdrObjCustomShape::GetInteractionHandles() const +{ + std::vector< SdrCustomShapeInteraction > aRet; + try + { + uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() ); + if ( xCustomShapeEngine.is() ) + { + int i; + uno::Sequence<uno::Reference<drawing::XCustomShapeHandle>> xInteractionHandles( xCustomShapeEngine->getInteraction() ); + for ( i = 0; i < xInteractionHandles.getLength(); i++ ) + { + if ( xInteractionHandles[ i ].is() ) + { + SdrCustomShapeInteraction aSdrCustomShapeInteraction; + aSdrCustomShapeInteraction.xInteraction = xInteractionHandles[ i ]; + aSdrCustomShapeInteraction.aPosition = xInteractionHandles[ i ]->getPosition(); + + CustomShapeHandleModes nMode = CustomShapeHandleModes::NONE; + switch( ImpGetCustomShapeType( *this ) ) + { + case mso_sptAccentBorderCallout90 : // 2 ortho + { + if (i == 0) + nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED; + else if (i == 1) + nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE | CustomShapeHandleModes::ORTHO4; + } + break; + + case mso_sptChevron : + case mso_sptHomePlate : + nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX; + break; + + case mso_sptWedgeRectCallout : + case mso_sptWedgeRRectCallout : + case mso_sptCloudCallout : + case mso_sptWedgeEllipseCallout : + { + if (i == 0) + nMode |= CustomShapeHandleModes::RESIZE_FIXED; + } + break; + + case mso_sptBorderCallout1 : // 2 diag + { + if (i == 0) + nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED; + else if (i == 1) + nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE; + } + break; + case mso_sptBorderCallout2 : // 3 + { + if (i == 0) + nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED; + else if (i == 2) + nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE; + } + break; + case mso_sptCallout90 : + case mso_sptAccentCallout90 : + case mso_sptBorderCallout90 : + case mso_sptCallout1 : + case mso_sptCallout2 : + case mso_sptCallout3 : + case mso_sptAccentCallout1 : + case mso_sptAccentCallout2 : + case mso_sptAccentCallout3 : + case mso_sptBorderCallout3 : + case mso_sptAccentBorderCallout1 : + case mso_sptAccentBorderCallout2 : + case mso_sptAccentBorderCallout3 : + { + if (i == 0) + nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED; + } + break; + default: break; + } + aSdrCustomShapeInteraction.nMode = nMode; + aRet.push_back( aSdrCustomShapeInteraction ); + } + } + } + } + catch( const uno::RuntimeException& ) + { + } + return aRet; +} + + +// BaseProperties section +#define DEFAULT_MINIMUM_SIGNED_COMPARE (sal_Int32(0x80000000)) +#define DEFAULT_MAXIMUM_SIGNED_COMPARE (sal_Int32(0x7fffffff)) + +static sal_Int32 GetNumberOfProperties ( const SvxMSDffHandle* pData ) +{ + sal_Int32 nPropertiesNeeded=1; // position is always needed + SvxMSDffHandleFlags nFlags = pData->nFlags; + + if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X ) + nPropertiesNeeded++; + if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y ) + nPropertiesNeeded++; + if ( nFlags & SvxMSDffHandleFlags::SWITCHED ) + nPropertiesNeeded++; + if ( nFlags & SvxMSDffHandleFlags::POLAR ) + { + nPropertiesNeeded++; + if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE ) + { + if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + } + } + else if ( nFlags & SvxMSDffHandleFlags::RANGE ) + { + if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + nPropertiesNeeded++; + } + + return nPropertiesNeeded; +} + +static void lcl_ShapePropertiesFromDFF( const SvxMSDffHandle* pData, beans::PropertyValues& rPropValues ) +{ + SvxMSDffHandleFlags nFlags = pData->nFlags; + sal_Int32 n=0; + auto pPropValues = rPropValues.getArray(); + + // POSITION + { + drawing::EnhancedCustomShapeParameterPair aPosition; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.First, pData->nPositionX, true, true ); + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.Second, pData->nPositionY, true, false ); + pPropValues[ n ].Name = "Position"; + pPropValues[ n++ ].Value <<= aPosition; + } + if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X ) + { + pPropValues[ n ].Name = "MirroredX"; + pPropValues[ n++ ].Value <<= true; + } + if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y ) + { + pPropValues[ n ].Name = "MirroredY"; + pPropValues[ n++ ].Value <<= true; + } + if ( nFlags & SvxMSDffHandleFlags::SWITCHED ) + { + pPropValues[ n ].Name = "Switched"; + pPropValues[ n++ ].Value <<= true; + } + if ( nFlags & SvxMSDffHandleFlags::POLAR ) + { + drawing::EnhancedCustomShapeParameterPair aCenter; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.First, pData->nCenterX, + bool( nFlags & SvxMSDffHandleFlags::CENTER_X_IS_SPECIAL ), true ); + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.Second, pData->nCenterY, + bool( nFlags & SvxMSDffHandleFlags::CENTER_Y_IS_SPECIAL ), false ); + pPropValues[ n ].Name = "Polar"; + pPropValues[ n++ ].Value <<= aCenter; + if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE ) + { + if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRadiusRangeMinimum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMinimum, pData->nRangeXMin, + bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true ); + pPropValues[ n ].Name = "RadiusRangeMinimum"; + pPropValues[ n++ ].Value <<= aRadiusRangeMinimum; + } + if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRadiusRangeMaximum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMaximum, pData->nRangeXMax, + bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false ); + pPropValues[ n ].Name = "RadiusRangeMaximum"; + pPropValues[ n++ ].Value <<= aRadiusRangeMaximum; + } + } + } + else if ( nFlags & SvxMSDffHandleFlags::RANGE ) + { + if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRangeXMinimum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMinimum, pData->nRangeXMin, + bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true ); + pPropValues[ n ].Name = "RangeXMinimum"; + pPropValues[ n++ ].Value <<= aRangeXMinimum; + } + if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRangeXMaximum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMaximum, pData->nRangeXMax, + bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false ); + pPropValues[ n ].Name = "RangeXMaximum"; + pPropValues[ n++ ].Value <<= aRangeXMaximum; + } + if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRangeYMinimum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMinimum, pData->nRangeYMin, + bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MIN_IS_SPECIAL ), true ); + pPropValues[ n ].Name = "RangeYMinimum"; + pPropValues[ n++ ].Value <<= aRangeYMinimum; + } + if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE ) + { + drawing::EnhancedCustomShapeParameter aRangeYMaximum; + EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMaximum, pData->nRangeYMax, + bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MAX_IS_SPECIAL ), false ); + pPropValues[ n ].Name = "RangeYMaximum"; + pPropValues[ n++ ].Value <<= aRangeYMaximum; + } + } +} + +std::unique_ptr<sdr::properties::BaseProperties> SdrObjCustomShape::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::CustomShapeProperties>(*this); +} + +SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel) +: SdrTextObj(rSdrModel) + , m_fObjectRotation(0.0) + , mbAdjustingTextFrameWidthAndHeight(false) +{ + m_bClosedObj = true; // custom shapes may be filled + mbTextFrame = true; +} + +SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel, SdrObjCustomShape const & rSource) +: SdrTextObj(rSdrModel, rSource) + , m_fObjectRotation(0.0) + , mbAdjustingTextFrameWidthAndHeight(false) +{ + m_bClosedObj = true; // custom shapes may be filled + mbTextFrame = true; + + m_fObjectRotation = rSource.m_fObjectRotation; + mbAdjustingTextFrameWidthAndHeight = rSource.mbAdjustingTextFrameWidthAndHeight; + assert(!mbAdjustingTextFrameWidthAndHeight); + InvalidateRenderGeometry(); +} + +SdrObjCustomShape::~SdrObjCustomShape() +{ + // delete buffered display geometry + InvalidateRenderGeometry(); +} + +void SdrObjCustomShape::MergeDefaultAttributes( const OUString* pType ) +{ + beans::PropertyValue aPropVal; + OUString sShapeType; + static constexpr OUString sType( u"Type"_ustr ); + SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + if ( pType && !pType->isEmpty() ) + { + sal_Int32 nType = pType->toInt32(); + if ( nType ) + sShapeType = EnhancedCustomShapeTypeNames::Get( static_cast< MSO_SPT >( nType ) ); + else + sShapeType = *pType; + + aPropVal.Name = sType; + aPropVal.Value <<= sShapeType; + aGeometryItem.SetPropertyValue( aPropVal ); + } + else + { + uno::Any *pAny = aGeometryItem.GetPropertyValueByName( sType ); + if ( pAny ) + *pAny >>= sShapeType; + } + MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType ); + + const sal_Int32* pDefData = nullptr; + const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType ); + if ( pDefCustomShape ) + pDefData = pDefCustomShape->pDefData; + + uno::Sequence<drawing::EnhancedCustomShapeAdjustmentValue> seqAdjustmentValues; + + + // AdjustmentValues + + static constexpr OUString sAdjustmentValues( u"AdjustmentValues"_ustr ); + const uno::Any* pAny = aGeometryItem.GetPropertyValueByName( sAdjustmentValues ); + if ( pAny ) + *pAny >>= seqAdjustmentValues; + if ( pDefCustomShape && pDefData ) // now check if we have to default some adjustment values + { + // first check if there are adjustment values are to be appended + sal_Int32 i, nAdjustmentValues = seqAdjustmentValues.getLength(); + sal_Int32 nAdjustmentDefaults = *pDefData++; + if ( nAdjustmentDefaults > nAdjustmentValues ) + seqAdjustmentValues.realloc( nAdjustmentDefaults ); + auto pseqAdjustmentValues = seqAdjustmentValues.getArray(); + for ( i = nAdjustmentValues; i < nAdjustmentDefaults; i++ ) + { + pseqAdjustmentValues[ i ].Value <<= pDefData[ i ]; + pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE; + } + // check if there are defaulted adjustment values that should be filled the hard coded defaults (pDefValue) + sal_Int32 nCount = std::min(nAdjustmentValues, nAdjustmentDefaults); + for ( i = 0; i < nCount; i++ ) + { + if ( seqAdjustmentValues[ i ].State != beans::PropertyState_DIRECT_VALUE ) + { + pseqAdjustmentValues[ i ].Value <<= pDefData[ i ]; + pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE; + } + } + } + aPropVal.Name = sAdjustmentValues; + aPropVal.Value <<= seqAdjustmentValues; + aGeometryItem.SetPropertyValue( aPropVal ); + + + // Coordsize + + static constexpr OUString sViewBox( u"ViewBox"_ustr ); + const uno::Any* pViewBox = aGeometryItem.GetPropertyValueByName( sViewBox ); + awt::Rectangle aViewBox; + if ( !pViewBox || !(*pViewBox >>= aViewBox ) ) + { + if ( pDefCustomShape ) + { + aViewBox.X = 0; + aViewBox.Y = 0; + aViewBox.Width = pDefCustomShape->nCoordWidth; + aViewBox.Height= pDefCustomShape->nCoordHeight; + aPropVal.Name = sViewBox; + aPropVal.Value <<= aViewBox; + aGeometryItem.SetPropertyValue( aPropVal ); + } + } + + static constexpr OUString sPath( u"Path"_ustr ); + + + // Path/Coordinates + + static constexpr OUString sCoordinates( u"Coordinates"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sCoordinates ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nVertices && pDefCustomShape->pVertices ) + { + sal_Int32 i, nCount = pDefCustomShape->nVertices; + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates( nCount ); + auto pseqCoordinates = seqCoordinates.getArray(); + for ( i = 0; i < nCount; i++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].First, pDefCustomShape->pVertices[ i ].nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].Second, pDefCustomShape->pVertices[ i ].nValB ); + } + aPropVal.Name = sCoordinates; + aPropVal.Value <<= seqCoordinates; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + + // Path/GluePoints + static constexpr OUString sGluePoints( u"GluePoints"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sGluePoints ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nGluePoints && pDefCustomShape->pGluePoints ) + { + sal_Int32 i, nCount = pDefCustomShape->nGluePoints; + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints( nCount ); + auto pseqGluePoints = seqGluePoints.getArray(); + for ( i = 0; i < nCount; i++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB ); + } + aPropVal.Name = sGluePoints; + aPropVal.Value <<= seqGluePoints; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + + // Path/Segments + static constexpr OUString sSegments( u"Segments"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sSegments ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements ) + { + sal_Int32 i, nCount = pDefCustomShape->nElements; + uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments( nCount ); + auto pseqSegments = seqSegments.getArray(); + for ( i = 0; i < nCount; i++ ) + { + drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments[ i ]; + sal_uInt16 nSDat = pDefCustomShape->pElements[ i ]; + lcl_ShapeSegmentFromBinary( rSegInfo, nSDat ); + } + aPropVal.Name = sSegments; + aPropVal.Value <<= seqSegments; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + + // Path/StretchX + static constexpr OUString sStretchX( u"StretchX"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchX ); + if ( !pAny && pDefCustomShape ) + { + sal_Int32 nXRef = pDefCustomShape->nXRef; + if ( nXRef != DEFAULT_MINIMUM_SIGNED_COMPARE ) + { + aPropVal.Name = sStretchX; + aPropVal.Value <<= nXRef; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + } + + // Path/StretchY + static constexpr OUString sStretchY( u"StretchY"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchY ); + if ( !pAny && pDefCustomShape ) + { + sal_Int32 nYRef = pDefCustomShape->nYRef; + if ( nYRef != DEFAULT_MINIMUM_SIGNED_COMPARE ) + { + aPropVal.Name = sStretchY; + aPropVal.Value <<= nYRef; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + } + + // Path/TextFrames + static constexpr OUString sTextFrames( u"TextFrames"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sPath, sTextFrames ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nTextRect && pDefCustomShape->pTextRect ) + { + sal_Int32 i, nCount = pDefCustomShape->nTextRect; + uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames( nCount ); + auto pseqTextFrames = seqTextFrames.getArray(); + const SvxMSDffTextRectangles* pRectangles = pDefCustomShape->pTextRect; + for ( i = 0; i < nCount; i++, pRectangles++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.First, pRectangles->nPairA.nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.Second, pRectangles->nPairA.nValB ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.First, pRectangles->nPairB.nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.Second, pRectangles->nPairB.nValB ); + } + aPropVal.Name = sTextFrames; + aPropVal.Value <<= seqTextFrames; + aGeometryItem.SetPropertyValue( sPath, aPropVal ); + } + + // Equations + static constexpr OUString sEquations( u"Equations"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sEquations ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nCalculation && pDefCustomShape->pCalculation ) + { + sal_Int32 i, nCount = pDefCustomShape->nCalculation; + uno::Sequence< OUString > seqEquations( nCount ); + auto pseqEquations = seqEquations.getArray(); + const SvxMSDffCalculationData* pData = pDefCustomShape->pCalculation; + for ( i = 0; i < nCount; i++, pData++ ) + pseqEquations[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] ); + aPropVal.Name = sEquations; + aPropVal.Value <<= seqEquations; + aGeometryItem.SetPropertyValue( aPropVal ); + } + + // Handles + static constexpr OUString sHandles( u"Handles"_ustr ); + pAny = aGeometryItem.GetPropertyValueByName( sHandles ); + if ( !pAny && pDefCustomShape && pDefCustomShape->nHandles && pDefCustomShape->pHandles ) + { + sal_Int32 i, nCount = pDefCustomShape->nHandles; + const SvxMSDffHandle* pData = pDefCustomShape->pHandles; + uno::Sequence<beans::PropertyValues> seqHandles( nCount ); + auto pseqHandles = seqHandles.getArray(); + for ( i = 0; i < nCount; i++, pData++ ) + { + sal_Int32 nPropertiesNeeded; + beans::PropertyValues& rPropValues = pseqHandles[ i ]; + nPropertiesNeeded = GetNumberOfProperties( pData ); + rPropValues.realloc( nPropertiesNeeded ); + lcl_ShapePropertiesFromDFF( pData, rPropValues ); + } + aPropVal.Name = sHandles; + aPropVal.Value <<= seqHandles; + aGeometryItem.SetPropertyValue( aPropVal ); + } + else if (pAny && sShapeType.startsWith("ooxml-") && sShapeType != "ooxml-non-primitive") + { + // ODF is not able to store the ooxml way of connecting handle to an adjustment + // value by name, e.g. attribute RefX="adj". So the information is lost, when exporting + // a pptx to odp, for example. This part reconstructs this information for the + // ooxml preset shapes from their definition. + uno::Sequence<beans::PropertyValues> seqHandles; + *pAny >>= seqHandles; + auto seqHandlesRange = asNonConstRange(seqHandles); + bool bChanged(false); + for (sal_Int32 i = 0; i < seqHandles.getLength(); i++) + { + comphelper::SequenceAsHashMap aHandleProps(seqHandles[i]); + OUString sFirstRefType; + sal_Int32 nFirstAdjRef; + OUString sSecondRefType; + sal_Int32 nSecondAdjRef; + PresetOOXHandleAdj::GetOOXHandleAdjRelation(sShapeType, i, sFirstRefType, nFirstAdjRef, + sSecondRefType, nSecondAdjRef); + if (sFirstRefType != "na" && 0 <= nFirstAdjRef + && nFirstAdjRef < seqAdjustmentValues.getLength()) + { + bChanged |= aHandleProps.createItemIfMissing(sFirstRefType, nFirstAdjRef); + } + if (sSecondRefType != "na" && 0 <= nSecondAdjRef + && nSecondAdjRef < seqAdjustmentValues.getLength()) + { + bChanged |= aHandleProps.createItemIfMissing(sSecondRefType, nSecondAdjRef); + } + aHandleProps >> seqHandlesRange[i]; + } + if (bChanged) + { + aPropVal.Name = sHandles; + aPropVal.Value <<= seqHandles; + aGeometryItem.SetPropertyValue(aPropVal); + } + } + + SetMergedItem( aGeometryItem ); +} + +bool SdrObjCustomShape::IsDefaultGeometry( const DefaultType eDefaultType ) const +{ + bool bIsDefaultGeometry = false; + + OUString sShapeType; + const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + + const uno::Any *pAny = rGeometryItem.GetPropertyValueByName( "Type" ); + if ( pAny ) + *pAny >>= sShapeType; + + MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType ); + + const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType ); + static constexpr OUString sPath( u"Path"_ustr ); + switch( eDefaultType ) + { + case DefaultType::Viewbox : + { + const uno::Any* pViewBox = rGeometryItem.GetPropertyValueByName( "ViewBox" ); + awt::Rectangle aViewBox; + if (pViewBox && (*pViewBox >>= aViewBox) && pDefCustomShape) + { + if ( ( aViewBox.Width == pDefCustomShape->nCoordWidth ) + && ( aViewBox.Height == pDefCustomShape->nCoordHeight ) ) + bIsDefaultGeometry = true; + } + } + break; + + case DefaultType::Path : + { + pAny = rGeometryItem.GetPropertyValueByName( sPath, "Coordinates" ); + if ( pAny && pDefCustomShape && pDefCustomShape->nVertices && pDefCustomShape->pVertices ) + { + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates1; + if ( *pAny >>= seqCoordinates1 ) + { + sal_Int32 i, nCount = pDefCustomShape->nVertices; + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates2( nCount ); + auto pseqCoordinates2 = seqCoordinates2.getArray(); + for ( i = 0; i < nCount; i++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].First, pDefCustomShape->pVertices[ i ].nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].Second, pDefCustomShape->pVertices[ i ].nValB ); + } + if ( seqCoordinates1 == seqCoordinates2 ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( ( pDefCustomShape->nVertices == 0 ) || ( pDefCustomShape->pVertices == nullptr ) ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::Gluepoints : + { + pAny = rGeometryItem.GetPropertyValueByName( sPath, "GluePoints" ); + if ( pAny && pDefCustomShape && pDefCustomShape->nGluePoints && pDefCustomShape->pGluePoints ) + { + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints1; + if ( *pAny >>= seqGluePoints1 ) + { + sal_Int32 i, nCount = pDefCustomShape->nGluePoints; + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints2( nCount ); + auto pseqGluePoints2 = seqGluePoints2.getArray(); + for ( i = 0; i < nCount; i++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB ); + } + if ( seqGluePoints1 == seqGluePoints2 ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( pDefCustomShape->nGluePoints == 0 ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::Segments : + { + // Path/Segments + pAny = rGeometryItem.GetPropertyValueByName( sPath, "Segments" ); + if ( pAny ) + { + uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments1; + if ( *pAny >>= seqSegments1 ) + { + if ( pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements ) + { + sal_Int32 i, nCount = pDefCustomShape->nElements; + if ( nCount ) + { + uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments2( nCount ); + auto pseqSegments2 = seqSegments2.getArray(); + for ( i = 0; i < nCount; i++ ) + { + drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments2[ i ]; + sal_uInt16 nSDat = pDefCustomShape->pElements[ i ]; + lcl_ShapeSegmentFromBinary( rSegInfo, nSDat ); + } + if ( seqSegments1 == seqSegments2 ) + bIsDefaultGeometry = true; + } + } + else + { + // check if it's the default segment description ( M L Z N ) + if ( seqSegments1.getLength() == 4 ) + { + if ( ( seqSegments1[ 0 ].Command == drawing::EnhancedCustomShapeSegmentCommand::MOVETO ) + && ( seqSegments1[ 1 ].Command == drawing::EnhancedCustomShapeSegmentCommand::LINETO ) + && ( seqSegments1[ 2 ].Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH ) + && ( seqSegments1[ 3 ].Command == drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH ) ) + bIsDefaultGeometry = true; + } + } + } + } + else if ( pDefCustomShape && ( ( pDefCustomShape->nElements == 0 ) || ( pDefCustomShape->pElements == nullptr ) ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::StretchX : + { + pAny = rGeometryItem.GetPropertyValueByName( sPath, "StretchX" ); + if ( pAny && pDefCustomShape ) + { + sal_Int32 nStretchX = 0; + if ( *pAny >>= nStretchX ) + { + if ( pDefCustomShape->nXRef == nStretchX ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( pDefCustomShape->nXRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::StretchY : + { + pAny = rGeometryItem.GetPropertyValueByName( sPath, "StretchY" ); + if ( pAny && pDefCustomShape ) + { + sal_Int32 nStretchY = 0; + if ( *pAny >>= nStretchY ) + { + if ( pDefCustomShape->nYRef == nStretchY ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( pDefCustomShape->nYRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::Equations : + { + pAny = rGeometryItem.GetPropertyValueByName( "Equations" ); + if ( pAny && pDefCustomShape && pDefCustomShape->nCalculation && pDefCustomShape->pCalculation ) + { + uno::Sequence<OUString> seqEquations1; + if ( *pAny >>= seqEquations1 ) + { + sal_Int32 i, nCount = pDefCustomShape->nCalculation; + uno::Sequence<OUString> seqEquations2( nCount ); + auto pseqEquations2 = seqEquations2.getArray(); + + const SvxMSDffCalculationData* pData = pDefCustomShape->pCalculation; + for ( i = 0; i < nCount; i++, pData++ ) + pseqEquations2[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] ); + + if ( seqEquations1 == seqEquations2 ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( ( pDefCustomShape->nCalculation == 0 ) || ( pDefCustomShape->pCalculation == nullptr ) ) ) + bIsDefaultGeometry = true; + } + break; + + case DefaultType::TextFrames : + { + pAny = rGeometryItem.GetPropertyValueByName( sPath, "TextFrames" ); + if ( pAny && pDefCustomShape && pDefCustomShape->nTextRect && pDefCustomShape->pTextRect ) + { + uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames1; + if ( *pAny >>= seqTextFrames1 ) + { + sal_Int32 i, nCount = pDefCustomShape->nTextRect; + uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames2( nCount ); + auto pseqTextFrames2 = seqTextFrames2.getArray(); + const SvxMSDffTextRectangles* pRectangles = pDefCustomShape->pTextRect; + for ( i = 0; i < nCount; i++, pRectangles++ ) + { + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.First, pRectangles->nPairA.nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.Second, pRectangles->nPairA.nValB ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.First, pRectangles->nPairB.nValA ); + EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.Second, pRectangles->nPairB.nValB ); + } + if ( seqTextFrames1 == seqTextFrames2 ) + bIsDefaultGeometry = true; + } + } + else if ( pDefCustomShape && ( ( pDefCustomShape->nTextRect == 0 ) || ( pDefCustomShape->pTextRect == nullptr ) ) ) + bIsDefaultGeometry = true; + } + break; + } + return bIsDefaultGeometry; +} + +void SdrObjCustomShape::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bResizeFreeAllowed=m_fObjectRotation == 0.0; + rInfo.bResizePropAllowed=true; + rInfo.bRotateFreeAllowed=true; + rInfo.bRotate90Allowed =true; + rInfo.bMirrorFreeAllowed=true; + rInfo.bMirror45Allowed =true; + rInfo.bMirror90Allowed =true; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =true; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bNoContortion =true; + + // #i37011# + if ( !mXRenderedCustomShape.is() ) + return; + + const SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape ); + if ( !pRenderedCustomShape ) + return; + + // #i37262# + // Iterate self over the contained objects, since there are combinations of + // polygon and curve objects. In that case, aInfo.bCanConvToPath and + // aInfo.bCanConvToPoly would be false. What is needed here is an or, not an and. + SdrObjListIter aIterator(*pRenderedCustomShape); + while(aIterator.IsMore()) + { + SdrObject* pCandidate = aIterator.Next(); + SdrObjTransformInfoRec aInfo; + pCandidate->TakeObjInfo(aInfo); + + // set path and poly conversion if one is possible since + // this object will first be broken + const bool bCanConvToPathOrPoly(aInfo.bCanConvToPath || aInfo.bCanConvToPoly); + if(rInfo.bCanConvToPath != bCanConvToPathOrPoly) + { + rInfo.bCanConvToPath = bCanConvToPathOrPoly; + } + + if(rInfo.bCanConvToPoly != bCanConvToPathOrPoly) + { + rInfo.bCanConvToPoly = bCanConvToPathOrPoly; + } + + if(rInfo.bCanConvToContour != aInfo.bCanConvToContour) + { + rInfo.bCanConvToContour = aInfo.bCanConvToContour; + } + + if(rInfo.bShearAllowed != aInfo.bShearAllowed) + { + rInfo.bShearAllowed = aInfo.bShearAllowed; + } + } +} + +SdrObjKind SdrObjCustomShape::GetObjIdentifier() const +{ + return SdrObjKind::CustomShape; +} + +// #115391# This implementation is based on the TextFrame size of the CustomShape and the +// state of the ResizeShapeToFitText flag to correctly set TextMinFrameWidth/Height +void SdrObjCustomShape::AdaptTextMinSize() +{ + if (getSdrModelFromSdrObject().IsCreatingDataObj() || getSdrModelFromSdrObject().IsPasteResize()) + return; + + // check if we need to change anything before creating an SfxItemSet, because that is expensive + const bool bResizeShapeToFitText(GetObjectItem(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue()); + tools::Rectangle aTextBound(getRectangle()); + bool bChanged(false); + if(bResizeShapeToFitText) + bChanged = true; + else if(GetTextBounds(aTextBound)) + bChanged = true; + if (!bChanged) + return; + + SfxItemSetFixed<SDRATTR_TEXT_MINFRAMEHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT, + SDRATTR_TEXT_MINFRAMEWIDTH, SDRATTR_TEXT_AUTOGROWWIDTH> // contains SDRATTR_TEXT_MAXFRAMEWIDTH + aSet(*GetObjectItemSet().GetPool()); + + if(bResizeShapeToFitText) + { + // always reset MinWidthHeight to zero to only rely on text size and frame size + // to allow resizing being completely dependent on text size only + aSet.Put(makeSdrTextMinFrameWidthItem(0)); + aSet.Put(makeSdrTextMinFrameHeightItem(0)); + } + else + { + // recreate from CustomShape-specific TextBounds + const tools::Long nHDist(GetTextLeftDistance() + GetTextRightDistance()); + const tools::Long nVDist(GetTextUpperDistance() + GetTextLowerDistance()); + const tools::Long nTWdt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetWidth() - 1 - nHDist))); + const tools::Long nTHgt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetHeight() - 1 - nVDist))); + + aSet.Put(makeSdrTextMinFrameWidthItem(nTWdt)); + aSet.Put(makeSdrTextMinFrameHeightItem(nTHgt)); + } + + SetObjectItemSet(aSet); +} + +void SdrObjCustomShape::NbcSetSnapRect(const tools::Rectangle& rRectangle) +{ + tools::Rectangle aRectangle(rRectangle); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + InvalidateRenderGeometry(); + + AdaptTextMinSize(); + + ImpCheckShear(); + SetBoundAndSnapRectsDirty(); + SetChanged(); +} + +void SdrObjCustomShape::SetSnapRect( const tools::Rectangle& rRect ) +{ + tools::Rectangle aBoundRect0; + if ( m_pUserCall ) + aBoundRect0 = GetLastBoundRect(); + NbcSetSnapRect( rRect ); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObjCustomShape::NbcSetLogicRect(const tools::Rectangle& rRectangle) +{ + tools::Rectangle aRectangle(rRectangle); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + InvalidateRenderGeometry(); + + AdaptTextMinSize(); + + SetBoundAndSnapRectsDirty(); + SetChanged(); +} + +void SdrObjCustomShape::SetLogicRect( const tools::Rectangle& rRect ) +{ + tools::Rectangle aBoundRect0; + if ( m_pUserCall ) + aBoundRect0 = GetLastBoundRect(); + NbcSetLogicRect(rRect); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObjCustomShape::Move( const Size& rSiz ) +{ + if ( rSiz.Width() || rSiz.Height() ) + { + tools::Rectangle aBoundRect0; + if ( m_pUserCall ) + aBoundRect0 = GetLastBoundRect(); + NbcMove(rSiz); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} +void SdrObjCustomShape::NbcMove( const Size& rSiz ) +{ + SdrTextObj::NbcMove( rSiz ); + if ( mXRenderedCustomShape.is() ) + { + SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape(mXRenderedCustomShape); + if ( pRenderedCustomShape ) + { + // #i97149# the visualisation shape needs to be informed + // about change, too + pRenderedCustomShape->ActionChanged(); + pRenderedCustomShape->NbcMove( rSiz ); + } + } + + // #i37011# adapt geometry shadow + if(mpLastShadowGeometry) + { + mpLastShadowGeometry->NbcMove( rSiz ); + } +} + +void SdrObjCustomShape::NbcResize( const Point& rRef, const Fraction& rxFact, const Fraction& ryFact ) +{ + // taking care of handles that should not been changed + tools::Rectangle aOld(getRectangle()); + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + + SdrTextObj::NbcResize( rRef, rxFact, ryFact ); + + if ( ( rxFact.GetNumerator() != rxFact.GetDenominator() ) + || ( ryFact.GetNumerator()!= ryFact.GetDenominator() ) ) + { + if ( ( ( rxFact.GetNumerator() < 0 ) && ( rxFact.GetDenominator() > 0 ) ) || + ( ( rxFact.GetNumerator() > 0 ) && ( rxFact.GetDenominator() < 0 ) ) ) + { + SetMirroredX( !IsMirroredX() ); + } + if ( ( ( ryFact.GetNumerator() < 0 ) && ( ryFact.GetDenominator() > 0 ) ) || + ( ( ryFact.GetNumerator() > 0 ) && ( ryFact.GetDenominator() < 0 ) ) ) + { + SetMirroredY( !IsMirroredY() ); + } + } + + for (const auto& rInteraction : aInteractionHandles) + { + try + { + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED ) + rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition ); + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_X ) + { + sal_Int32 nX = ( rInteraction.aPosition.X - aOld.Left() ) + getRectangle().Left(); + rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y)); + } + else if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX ) + { + sal_Int32 nX = getRectangle().Right() - (aOld.Right() - rInteraction.aPosition.X); + rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y)); + } + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_Y ) + { + sal_Int32 nY = ( rInteraction.aPosition.Y - aOld.Top() ) + getRectangle().Top(); + rInteraction.xInteraction->setControllerPosition(awt::Point(rInteraction.xInteraction->getPosition().X, nY)); + } + } + catch ( const uno::RuntimeException& ) + { + } + } + + // updating fObjectRotation + Degree100 nTextObjRotation = maGeo.m_nRotationAngle; + double fAngle = toDegrees(nTextObjRotation); + if (IsMirroredX()) + { + if (IsMirroredY()) + m_fObjectRotation = fAngle - 180.0; + else + m_fObjectRotation = -fAngle; + } + else + { + if (IsMirroredY()) + m_fObjectRotation = 180.0 - fAngle; + else + m_fObjectRotation = fAngle; + } + while (m_fObjectRotation < 0) + m_fObjectRotation += 360.0; + while (m_fObjectRotation >= 360.0) + m_fObjectRotation -= 360.0; + + InvalidateRenderGeometry(); +} + +void SdrObjCustomShape::NbcRotate( const Point& rRef, Degree100 nAngle, double sn, double cs ) +{ + bool bMirroredX = IsMirroredX(); + bool bMirroredY = IsMirroredY(); + + m_fObjectRotation = fmod( m_fObjectRotation, 360.0 ); + if ( m_fObjectRotation < 0 ) + m_fObjectRotation = 360 + m_fObjectRotation; + + // the rotation angle for ashapes is stored in fObjectRotation, this rotation + // has to be applied to the text object (which is internally using maGeo.nAngle). + SdrTextObj::NbcRotate( getRectangle().TopLeft(), -maGeo.m_nRotationAngle, // retrieving the unrotated text object + -maGeo.mfSinRotationAngle, + maGeo.mfCosRotationAngle ); + maGeo.m_nRotationAngle = 0_deg100; // resetting aGeo data + maGeo.RecalcSinCos(); + + Degree100 nW(static_cast<sal_Int32>( m_fObjectRotation * 100 )); // applying our object rotation + if ( bMirroredX ) + nW = 36000_deg100 - nW; + if ( bMirroredY ) + nW = 18000_deg100 - nW; + nW = nW % 36000_deg100; + if ( nW < 0_deg100 ) + nW = 36000_deg100 + nW; + SdrTextObj::NbcRotate( getRectangle().TopLeft(), nW, // applying text rotation + sin( toRadians(nW) ), + cos( toRadians(nW) ) ); + + int nSwap = 0; + if ( bMirroredX ) + nSwap ^= 1; + if ( bMirroredY ) + nSwap ^= 1; + + double fAngle = toDegrees(nAngle); // updating to our new object rotation + m_fObjectRotation = fmod( nSwap ? m_fObjectRotation - fAngle : m_fObjectRotation + fAngle, 360.0 ); + if ( m_fObjectRotation < 0 ) + m_fObjectRotation = 360 + m_fObjectRotation; + + SdrTextObj::NbcRotate( rRef, nAngle, sn, cs ); // applying text rotation + InvalidateRenderGeometry(); +} + +void SdrObjCustomShape::NbcMirror( const Point& rRef1, const Point& rRef2 ) +{ + // TTTT: Fix for old mirroring, can be removed again in aw080 + // storing horizontal and vertical flipping without modifying the rotate angle + // decompose other flipping to rotation and MirrorX. + tools::Long ndx = rRef2.X()-rRef1.X(); + tools::Long ndy = rRef2.Y()-rRef1.Y(); + + if(!ndx) // MirroredX + { + SetMirroredX(!IsMirroredX()); + SdrTextObj::NbcMirror( rRef1, rRef2 ); + } + else + { + if(!ndy) // MirroredY + { + SetMirroredY(!IsMirroredY()); + SdrTextObj::NbcMirror( rRef1, rRef2 ); + } + else // neither horizontal nor vertical + { + SetMirroredX(!IsMirroredX()); + + // call parent + SdrTextObj::NbcMirror( rRef1, rRef2 ); + + // update fObjectRotation + Degree100 nTextObjRotation = maGeo.m_nRotationAngle; + double fAngle = toDegrees(nTextObjRotation); + + bool bSingleFlip = (IsMirroredX()!= IsMirroredY()); + + m_fObjectRotation = fmod( bSingleFlip ? -fAngle : fAngle, 360.0 ); + + if ( m_fObjectRotation < 0 ) + { + m_fObjectRotation = 360.0 + m_fObjectRotation; + } + } + } + + InvalidateRenderGeometry(); +} + +void SdrObjCustomShape::Shear( const Point& rRef, Degree100 nAngle, double tn, bool bVShear ) +{ + SdrTextObj::Shear( rRef, nAngle, tn, bVShear ); + InvalidateRenderGeometry(); +} +void SdrObjCustomShape::NbcShear( const Point& rRef, Degree100 nAngle, double tn, bool bVShear ) +{ + // TTTT: Fix for old mirroring, can be removed again in aw080 + SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear); + + // updating fObjectRotation + Degree100 nTextObjRotation = maGeo.m_nRotationAngle; + double fAngle = toDegrees(nTextObjRotation); + if (IsMirroredX()) + { + if (IsMirroredY()) + m_fObjectRotation = fAngle - 180.0; + else + m_fObjectRotation = -fAngle; + } + else + { + if (IsMirroredY()) + m_fObjectRotation = 180.0 - fAngle; + else + m_fObjectRotation = fAngle; + } + while (m_fObjectRotation < 0) + m_fObjectRotation += 360.0; + while (m_fObjectRotation >= 360.0) + m_fObjectRotation -= 360.0; + + InvalidateRenderGeometry(); +} + +SdrGluePoint SdrObjCustomShape::GetVertexGluePoint(sal_uInt16 nPosNum) const +{ + sal_Int32 nWdt = ImpGetLineWdt(); // #i25616# + + // #i25616# + if(!LineIsOutsideGeometry()) + { + nWdt++; + nWdt /= 2; + } + + Point aPt; + tools::Rectangle aRectangle = getRectangle(); + switch (nPosNum) + { + case 0: aPt = aRectangle.TopCenter(); aPt.AdjustY( -nWdt ); break; + case 1: aPt = aRectangle.RightCenter(); aPt.AdjustX(nWdt ); break; + case 2: aPt = aRectangle.BottomCenter(); aPt.AdjustY(nWdt ); break; + case 3: aPt = aRectangle.LeftCenter(); aPt.AdjustX( -nWdt ); break; + } + if (maGeo.m_nShearAngle != 0_deg100) + ShearPoint(aPt, aRectangle.TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle != 0_deg100) + RotatePoint(aPt, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aPt-=GetSnapRect().Center(); + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + return aGP; +} + + +// #i38892# +void SdrObjCustomShape::ImpCheckCustomGluePointsAreAdded() +{ + const SdrObject* pSdrObject = GetSdrObjectFromCustomShape(); + + if(!pSdrObject) + return; + + const SdrGluePointList* pSource = pSdrObject->GetGluePointList(); + + if(!(pSource && pSource->GetCount())) + return; + + if(!SdrTextObj::GetGluePointList()) + { + SdrTextObj::ForceGluePointList(); + } + + const SdrGluePointList* pList = SdrTextObj::GetGluePointList(); + + if(!pList) + return; + + SdrGluePointList aNewList; + sal_uInt16 a; + + for(a = 0; a < pSource->GetCount(); a++) + { + SdrGluePoint aCopy((*pSource)[a]); + aCopy.SetUserDefined(false); + aNewList.Insert(aCopy); + } + + bool bMirroredX = IsMirroredX(); + bool bMirroredY = IsMirroredY(); + + Degree100 nShearAngle = maGeo.m_nShearAngle; + double fTan = maGeo.mfTanShearAngle; + + if (maGeo.m_nRotationAngle || nShearAngle || bMirroredX || bMirroredY) + { + tools::Polygon aPoly(getRectangle()); + if( nShearAngle ) + { + sal_uInt16 nPointCount=aPoly.GetSize(); + for (sal_uInt16 i=0; i<nPointCount; i++) + ShearPoint(aPoly[i], getRectangle().Center(), fTan ); + } + if (maGeo.m_nRotationAngle) + aPoly.Rotate( getRectangle().Center(), to<Degree10>(maGeo.m_nRotationAngle) ); + + tools::Rectangle aBoundRect( aPoly.GetBoundRect() ); + sal_Int32 nXDiff = aBoundRect.Left() - getRectangle().Left(); + sal_Int32 nYDiff = aBoundRect.Top() - getRectangle().Top(); + + if (nShearAngle && bMirroredX != bMirroredY) + { + nShearAngle = -nShearAngle; + fTan = -fTan; + } + + Point aRef( getRectangle().GetWidth() / 2, getRectangle().GetHeight() / 2 ); + for ( a = 0; a < aNewList.GetCount(); a++ ) + { + SdrGluePoint& rPoint = aNewList[ a ]; + Point aGlue( rPoint.GetPos() ); + if ( nShearAngle ) + ShearPoint( aGlue, aRef, fTan ); + + RotatePoint(aGlue, aRef, sin(basegfx::deg2rad(m_fObjectRotation)), + cos(basegfx::deg2rad(m_fObjectRotation))); + if ( bMirroredX ) + aGlue.setX( getRectangle().GetWidth() - aGlue.X() ); + if ( bMirroredY ) + aGlue.setY( getRectangle().GetHeight() - aGlue.Y() ); + aGlue.AdjustX( -nXDiff ); + aGlue.AdjustY( -nYDiff ); + rPoint.SetPos( aGlue ); + } + } + + for(a = 0; a < pList->GetCount(); a++) + { + const SdrGluePoint& rCandidate = (*pList)[a]; + + if(rCandidate.IsUserDefined()) + { + aNewList.Insert(rCandidate); + } + } + + // copy new list to local. This is NOT very convenient behavior, the local + // GluePointList should not be set, but we delivered by using GetGluePointList(), + // maybe on demand. Since the local object is changed here, this is assumed to + // be a result of GetGluePointList and thus the list is copied + if(m_pPlusData) + { + m_pPlusData->SetGluePoints(aNewList); + } +} + +// #i38892# +const SdrGluePointList* SdrObjCustomShape::GetGluePointList() const +{ + const_cast<SdrObjCustomShape*>(this)->ImpCheckCustomGluePointsAreAdded(); + return SdrTextObj::GetGluePointList(); +} + +// #i38892# +SdrGluePointList* SdrObjCustomShape::ForceGluePointList() +{ + if(SdrTextObj::ForceGluePointList()) + { + ImpCheckCustomGluePointsAreAdded(); + return SdrTextObj::ForceGluePointList(); + } + else + { + return nullptr; + } +} + + +sal_uInt32 SdrObjCustomShape::GetHdlCount() const +{ + const sal_uInt32 nBasicHdlCount(SdrTextObj::GetHdlCount()); + return ( GetInteractionHandles().size() + nBasicHdlCount ); +} + +void SdrObjCustomShape::AddToHdlList(SdrHdlList& rHdlList) const +{ + SdrTextObj::AddToHdlList(rHdlList); + + int nCustomShapeHdlNum = 0; + for (SdrCustomShapeInteraction const & rInteraction : GetInteractionHandles()) + { + if ( rInteraction.xInteraction.is() ) + { + try + { + awt::Point aPosition( rInteraction.xInteraction->getPosition() ); + std::unique_ptr<SdrHdl> pH(new SdrHdl( Point( aPosition.X, aPosition.Y ), SdrHdlKind::CustomShape1 )); + pH->SetPointNum( nCustomShapeHdlNum ); + pH->SetObj( const_cast<SdrObjCustomShape*>(this) ); + rHdlList.AddHdl(std::move(pH)); + } + catch ( const uno::RuntimeException& ) + { + } + } + ++nCustomShapeHdlNum; + } +} + +bool SdrObjCustomShape::hasSpecialDrag() const +{ + return true; +} + +bool SdrObjCustomShape::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + + if(pHdl && SdrHdlKind::CustomShape1 == pHdl->GetKind()) + { + rDrag.SetEndDragChangesAttributes(true); + rDrag.SetNoSnap(); + } + else + { + const SdrHdl* pHdl2 = rDrag.GetHdl(); + const SdrHdlKind eHdl((pHdl2 == nullptr) ? SdrHdlKind::Move : pHdl2->GetKind()); + + switch( eHdl ) + { + case SdrHdlKind::UpperLeft : + case SdrHdlKind::Upper : + case SdrHdlKind::UpperRight : + case SdrHdlKind::Left : + case SdrHdlKind::Right : + case SdrHdlKind::LowerLeft : + case SdrHdlKind::Lower : + case SdrHdlKind::LowerRight : + case SdrHdlKind::Move : + { + break; + } + default: + { + return false; + } + } + } + + return true; +} + +void SdrObjCustomShape::DragResizeCustomShape( const tools::Rectangle& rNewRect ) +{ + tools::Rectangle aOld(getRectangle()); + bool bOldMirroredX( IsMirroredX() ); + bool bOldMirroredY( IsMirroredY() ); + + tools::Rectangle aNewRect( rNewRect ); + aNewRect.Normalize(); + + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + + GeoStat aGeoStat( GetGeoStat() ); + if ( aNewRect.TopLeft() != getRectangle().TopLeft() && + ( maGeo.m_nRotationAngle || maGeo.m_nShearAngle ) ) + { + Point aNewPos( aNewRect.TopLeft() ); + if ( maGeo.m_nShearAngle ) ShearPoint( aNewPos, aOld.TopLeft(), aGeoStat.mfTanShearAngle ); + if ( maGeo.m_nRotationAngle ) RotatePoint(aNewPos, aOld.TopLeft(), aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle ); + aNewRect.SetPos( aNewPos ); + } + if (aNewRect == getRectangle()) + return; + + SetLogicRect( aNewRect ); + InvalidateRenderGeometry(); + + if ( rNewRect.Left() > rNewRect.Right() ) + { + Point aTop( ( GetSnapRect().Left() + GetSnapRect().Right() ) >> 1, GetSnapRect().Top() ); + Point aBottom( aTop.X(), aTop.Y() + 1000 ); + NbcMirror( aTop, aBottom ); + } + if ( rNewRect.Top() > rNewRect.Bottom() ) + { + Point aLeft( GetSnapRect().Left(), ( GetSnapRect().Top() + GetSnapRect().Bottom() ) >> 1 ); + Point aRight( aLeft.X() + 1000, aLeft.Y() ); + NbcMirror( aLeft, aRight ); + } + + for (const auto& rInteraction : aInteractionHandles) + { + try + { + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED ) + rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition ); + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_X || + rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX ) + { + if (rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX) + bOldMirroredX = !bOldMirroredX; + + sal_Int32 nX; + if ( bOldMirroredX ) + { + nX = ( rInteraction.aPosition.X - aOld.Right() ); + if ( rNewRect.Left() > rNewRect.Right() ) + nX = getRectangle().Left() - nX; + else + nX += getRectangle().Right(); + } + else + { + nX = ( rInteraction.aPosition.X - aOld.Left() ); + if ( rNewRect.Left() > rNewRect.Right() ) + nX = getRectangle().Right() - nX; + else + nX += getRectangle().Left(); + } + rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y)); + } + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_Y ) + { + sal_Int32 nY; + if ( bOldMirroredY ) + { + nY = ( rInteraction.aPosition.Y - aOld.Bottom() ); + if ( rNewRect.Top() > rNewRect.Bottom() ) + nY = getRectangle().Top() - nY; + else + nY += getRectangle().Bottom(); + } + else + { + nY = ( rInteraction.aPosition.Y - aOld.Top() ); + if ( rNewRect.Top() > rNewRect.Bottom() ) + nY = getRectangle().Bottom() - nY; + else + nY += getRectangle().Top(); + } + rInteraction.xInteraction->setControllerPosition(awt::Point(rInteraction.xInteraction->getPosition().X, nY)); + } + } + catch ( const uno::RuntimeException& ) + { + } + } +} + +void SdrObjCustomShape::DragMoveCustomShapeHdl( const Point& rDestination, + const sal_uInt16 nCustomShapeHdlNum, bool bMoveCalloutRectangle ) +{ + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + if ( nCustomShapeHdlNum >= aInteractionHandles.size() ) + return; + + SdrCustomShapeInteraction aInteractionHandle( aInteractionHandles[ nCustomShapeHdlNum ] ); + if ( !aInteractionHandle.xInteraction.is() ) + return; + + try + { + awt::Point aPt( rDestination.X(), rDestination.Y() ); + if ( aInteractionHandle.nMode & CustomShapeHandleModes::MOVE_SHAPE && bMoveCalloutRectangle ) + { + sal_Int32 nXDiff = aPt.X - aInteractionHandle.aPosition.X; + sal_Int32 nYDiff = aPt.Y - aInteractionHandle.aPosition.Y; + + moveRectangle(nXDiff, nYDiff); + moveOutRectangle(nXDiff, nYDiff); + maSnapRect.Move( nXDiff, nYDiff ); + SetBoundAndSnapRectsDirty(/*bNotMyself*/true); + InvalidateRenderGeometry(); + + for (const auto& rInteraction : aInteractionHandles) + { + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED ) + { + if ( rInteraction.xInteraction.is() ) + rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition ); + } + } + } + aInteractionHandle.xInteraction->setControllerPosition( aPt ); + } + catch ( const uno::RuntimeException& ) + { + } +} + +bool SdrObjCustomShape::applySpecialDrag(SdrDragStat& rDrag) +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind()); + + switch(eHdl) + { + case SdrHdlKind::CustomShape1 : + { + rDrag.SetEndDragChangesGeoAndAttributes(true); + DragMoveCustomShapeHdl( rDrag.GetNow(), static_cast<sal_uInt16>(pHdl->GetPointNum()), !rDrag.GetDragMethod()->IsShiftPressed() ); + SetBoundAndSnapRectsDirty(); + InvalidateRenderGeometry(); + SetChanged(); + break; + } + + case SdrHdlKind::UpperLeft : + case SdrHdlKind::Upper : + case SdrHdlKind::UpperRight : + case SdrHdlKind::Left : + case SdrHdlKind::Right : + case SdrHdlKind::LowerLeft : + case SdrHdlKind::Lower : + case SdrHdlKind::LowerRight : + { + DragResizeCustomShape( ImpDragCalcRect(rDrag) ); + break; + } + case SdrHdlKind::Move : + { + Move(Size(rDrag.GetDX(), rDrag.GetDY())); + break; + } + default: break; + } + + return true; +} + + +void SdrObjCustomShape::DragCreateObject( SdrDragStat& rStat ) +{ + tools::Rectangle aRect1; + rStat.TakeCreateRect( aRect1 ); + + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + + constexpr sal_uInt32 nDefaultObjectSizeWidth = 3000; // default width from SDOptions ? + constexpr sal_uInt32 nDefaultObjectSizeHeight= 3000; + + if ( ImpVerticalSwitch( *this ) ) + { + SetMirroredX( aRect1.Left() > aRect1.Right() ); + + aRect1 = tools::Rectangle( rStat.GetNow(), Size( nDefaultObjectSizeWidth, nDefaultObjectSizeHeight ) ); + // subtracting the horizontal difference of the latest handle from shape position + if ( !aInteractionHandles.empty() ) + { + sal_Int32 nHandlePos = aInteractionHandles[ aInteractionHandles.size() - 1 ].xInteraction->getPosition().X; + aRect1.Move(getRectangle().Left() - nHandlePos, 0); + } + } + ImpJustifyRect( aRect1 ); + rStat.SetActionRect( aRect1 ); + setRectangle(aRect1); + SetBoundAndSnapRectsDirty(); + + for (const auto& rInteraction : aInteractionHandles) + { + try + { + if ( rInteraction.nMode & CustomShapeHandleModes::CREATE_FIXED ) + rInteraction.xInteraction->setControllerPosition( awt::Point( rStat.GetStart().X(), rStat.GetStart().Y() ) ); + } + catch ( const uno::RuntimeException& ) + { + } + } + + SetBoundRectDirty(); + m_bSnapRectDirty=true; +} + +bool SdrObjCustomShape::MovCreate(SdrDragStat& rStat) +{ + SdrView* pView = rStat.GetView(); // #i37448# + if( pView && pView->IsSolidDragging() ) + { + InvalidateRenderGeometry(); + } + DragCreateObject( rStat ); + SetBoundAndSnapRectsDirty(); + return true; +} + +bool SdrObjCustomShape::EndCreate( SdrDragStat& rStat, SdrCreateCmd eCmd ) +{ + DragCreateObject( rStat ); + + AdaptTextMinSize(); + + SetBoundAndSnapRectsDirty(); + return ( eCmd == SdrCreateCmd::ForceEnd || rStat.GetPointCount() >= 2 ); +} + +basegfx::B2DPolyPolygon SdrObjCustomShape::TakeCreatePoly(const SdrDragStat& /*rDrag*/) const +{ + return GetLineGeometry( false ); +} + + +// in context with the SdrObjCustomShape the SdrTextAutoGrowHeightItem == true -> Resize Shape to fit text, +// the SdrTextAutoGrowWidthItem == true -> Word wrap text in Shape +bool SdrObjCustomShape::IsAutoGrowHeight() const +{ + const SfxItemSet& rSet = GetMergedItemSet(); + bool bIsAutoGrowHeight = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + if ( bIsAutoGrowHeight && IsVerticalWriting() ) + bIsAutoGrowHeight = !rSet.Get(SDRATTR_TEXT_WORDWRAP).GetValue(); + return bIsAutoGrowHeight; +} +bool SdrObjCustomShape::IsAutoGrowWidth() const +{ + const SfxItemSet& rSet = GetMergedItemSet(); + bool bIsAutoGrowWidth = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + if ( bIsAutoGrowWidth && !IsVerticalWriting() ) + bIsAutoGrowWidth = !rSet.Get(SDRATTR_TEXT_WORDWRAP).GetValue(); + return bIsAutoGrowWidth; +} + +/* The following method is identical to the SdrTextObj::SetVerticalWriting method, the only difference + is that the SdrAutoGrowWidthItem and SdrAutoGrowHeightItem are not exchanged if the vertical writing + mode has been changed */ + +void SdrObjCustomShape::SetVerticalWriting( bool bVertical ) +{ + ForceOutlinerParaObject(); + + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + + DBG_ASSERT( pOutlinerParaObject, "SdrTextObj::SetVerticalWriting() without OutlinerParaObject!" ); + + if( !pOutlinerParaObject || + (pOutlinerParaObject->IsEffectivelyVertical() == bVertical) ) + return; + + // get item settings + const SfxItemSet& rSet = GetObjectItemSet(); + + // Also exchange horizontal and vertical adjust items + SdrTextHorzAdjust eHorz = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue(); + SdrTextVertAdjust eVert = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue(); + + // rescue object size, SetSnapRect below expects logic rect, + // not snap rect. + tools::Rectangle aObjectRect = GetLogicRect(); + + // prepare ItemSet to set exchanged width and height items + SfxItemSetFixed<SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT, + // Expanded item ranges to also support horizontal and vertical adjust. + SDRATTR_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST, + SDRATTR_TEXT_AUTOGROWWIDTH, SDRATTR_TEXT_HORZADJUST> aNewSet(*rSet.GetPool()); + + aNewSet.Put(rSet); + + // Exchange horizontal and vertical adjusts + switch(eVert) + { + case SDRTEXTVERTADJUST_TOP: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); break; + case SDRTEXTVERTADJUST_CENTER: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_CENTER)); break; + case SDRTEXTVERTADJUST_BOTTOM: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); break; + case SDRTEXTVERTADJUST_BLOCK: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_BLOCK)); break; + } + switch(eHorz) + { + case SDRTEXTHORZADJUST_LEFT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BOTTOM)); break; + case SDRTEXTHORZADJUST_CENTER: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_CENTER)); break; + case SDRTEXTHORZADJUST_RIGHT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP)); break; + case SDRTEXTHORZADJUST_BLOCK: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BLOCK)); break; + } + + pOutlinerParaObject = GetOutlinerParaObject(); + if ( pOutlinerParaObject ) + pOutlinerParaObject->SetVertical(bVertical); + SetObjectItemSet( aNewSet ); + + // restore object size + SetSnapRect(aObjectRect); +} + +void SdrObjCustomShape::SuggestTextFrameSize(Size aSuggestedTextFrameSize) +{ + m_aSuggestedTextFrameSize = aSuggestedTextFrameSize; +} + +bool SdrObjCustomShape::AdjustTextFrameWidthAndHeight(tools::Rectangle& rR, bool bHgt, bool bWdt) const +{ + // Either we have text or the application has native text and suggested its size to us. + bool bHasText = HasText() || !m_aSuggestedTextFrameSize.IsEmpty(); + if ( bHasText && !rR.IsEmpty() ) + { + bool bWdtGrow=bWdt && IsAutoGrowWidth(); + bool bHgtGrow=bHgt && IsAutoGrowHeight(); + if ( bWdtGrow || bHgtGrow ) + { + tools::Rectangle aR0(rR); + tools::Long nHgt=0,nMinHgt=0,nMaxHgt=0; + tools::Long nWdt=0,nMinWdt=0,nMaxWdt=0; + Size aSiz(rR.GetSize()); aSiz.AdjustWidth( -1 ); aSiz.AdjustHeight( -1 ); + Size aMaxSiz(100000,100000); + Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize()); + if (aTmpSiz.Width()!=0) aMaxSiz.setWidth(aTmpSiz.Width() ); + if (aTmpSiz.Height()!=0) aMaxSiz.setHeight(aTmpSiz.Height() ); + if (bWdtGrow) + { + nMinWdt=GetMinTextFrameWidth(); + nMaxWdt=GetMaxTextFrameWidth(); + if (nMaxWdt==0 || nMaxWdt>aMaxSiz.Width()) nMaxWdt=aMaxSiz.Width(); + if (nMinWdt<=0) nMinWdt=1; + aSiz.setWidth(nMaxWdt ); + } + if (bHgtGrow) + { + nMinHgt=GetMinTextFrameHeight(); + nMaxHgt=GetMaxTextFrameHeight(); + if (nMaxHgt==0 || nMaxHgt>aMaxSiz.Height()) nMaxHgt=aMaxSiz.Height(); + if (nMinHgt<=0) nMinHgt=1; + aSiz.setHeight(nMaxHgt ); + } + tools::Long nHDist=GetTextLeftDistance()+GetTextRightDistance(); + tools::Long nVDist=GetTextUpperDistance()+GetTextLowerDistance(); + aSiz.AdjustWidth( -nHDist ); + aSiz.AdjustHeight( -nVDist ); + if ( aSiz.Width() < 2 ) + aSiz.setWidth( 2 ); // minimum size=2 + if ( aSiz.Height() < 2 ) + aSiz.setHeight( 2 ); // minimum size=2 + + if (HasText()) + { + if(mpEditingOutliner) + { + mpEditingOutliner->SetMaxAutoPaperSize( aSiz ); + if (bWdtGrow) + { + Size aSiz2(mpEditingOutliner->CalcTextSize()); + nWdt=aSiz2.Width()+1; // a little more tolerance + if (bHgtGrow) nHgt=aSiz2.Height()+1; // a little more tolerance + } else + { + nHgt=mpEditingOutliner->GetTextHeight()+1; // a little more tolerance + } + } + else + { + Outliner& rOutliner=ImpGetDrawOutliner(); + rOutliner.SetPaperSize(aSiz); + rOutliner.SetUpdateLayout(true); + // TODO: add the optimization with bPortionInfoChecked again. + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if( pOutlinerParaObject != nullptr ) + { + rOutliner.SetText(*pOutlinerParaObject); + rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + if ( bWdtGrow ) + { + Size aSiz2(rOutliner.CalcTextSize()); + nWdt=aSiz2.Width()+1; // a little more tolerance + if ( bHgtGrow ) + nHgt=aSiz2.Height()+1; // a little more tolerance + } + else + { + nHgt = rOutliner.GetTextHeight()+1; // a little more tolerance + + sal_Int16 nColumns = GetMergedItem(SDRATTR_TEXTCOLUMNS_NUMBER).GetValue(); + if (bHgtGrow && nColumns > 1) + { + // Both 'resize shape to fix text' and multiple columns are enabled. The + // first means a dynamic height, the second expects a fixed height. + // Resolve this conflict by going with the original height. + nHgt = rR.getOpenHeight(); + } + } + rOutliner.Clear(); + } + } + else + { + nHgt = m_aSuggestedTextFrameSize.Height(); + nWdt = m_aSuggestedTextFrameSize.Width(); + } + if ( nWdt < nMinWdt ) + nWdt = nMinWdt; + if ( nWdt > nMaxWdt ) + nWdt = nMaxWdt; + nWdt += nHDist; + if ( nWdt < 1 ) + nWdt = 1; // nHDist may also be negative + if ( nHgt < nMinHgt ) + nHgt = nMinHgt; + if ( nHgt > nMaxHgt ) + nHgt = nMaxHgt; + nHgt+=nVDist; + if ( nHgt < 1 ) + nHgt = 1; // nVDist may also be negative + tools::Long nWdtGrow = nWdt-(rR.Right()-rR.Left()); + tools::Long nHgtGrow = nHgt-(rR.Bottom()-rR.Top()); + if ( nWdtGrow == 0 ) + bWdtGrow = false; + if ( nHgtGrow == 0 ) + bHgtGrow=false; + if ( bWdtGrow || bHgtGrow || !m_aSuggestedTextFrameSize.IsEmpty()) + { + if ( bWdtGrow || m_aSuggestedTextFrameSize.Width() ) + { + SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust(); + if (m_aSuggestedTextFrameSize.Width()) + { + rR.SetRight(rR.Left() + m_aSuggestedTextFrameSize.Width()); + } + else if ( eHAdj == SDRTEXTHORZADJUST_LEFT ) + rR.AdjustRight(nWdtGrow ); + else if ( eHAdj == SDRTEXTHORZADJUST_RIGHT ) + rR.AdjustLeft( -nWdtGrow ); + else + { + tools::Long nWdtGrow2=nWdtGrow/2; + rR.AdjustLeft( -nWdtGrow2 ); + rR.SetRight(rR.Left()+nWdt ); + } + } + if ( bHgtGrow || m_aSuggestedTextFrameSize.Height() ) + { + SdrTextVertAdjust eVAdj=GetTextVerticalAdjust(); + if (m_aSuggestedTextFrameSize.Height()) + { + rR.SetBottom(rR.Top() + m_aSuggestedTextFrameSize.Height()); + } + else if ( eVAdj == SDRTEXTVERTADJUST_TOP ) + rR.AdjustBottom(nHgtGrow ); + else if ( eVAdj == SDRTEXTVERTADJUST_BOTTOM ) + rR.AdjustTop( -nHgtGrow ); + else + { + tools::Long nHgtGrow2=nHgtGrow/2; + rR.AdjustTop( -nHgtGrow2 ); + rR.SetBottom(rR.Top()+nHgt ); + } + } + if ( maGeo.m_nRotationAngle ) + { + Point aD1(rR.TopLeft()); + aD1-=aR0.TopLeft(); + Point aD2(aD1); + RotatePoint(aD2,Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aD2-=aD1; + rR.Move(aD2.X(),aD2.Y()); + } + return true; + } + } + } + return false; +} + +tools::Rectangle SdrObjCustomShape::ImpCalculateTextFrame( const bool bHgt, const bool bWdt ) +{ + tools::Rectangle aReturnValue; + + tools::Rectangle aOldTextRect(getRectangle()); // <- initial text rectangle + + tools::Rectangle aNewTextRect(getRectangle()); // <- new text rectangle returned from the custom shape renderer, + GetTextBounds( aNewTextRect ); // it depends to the current logical shape size + + tools::Rectangle aAdjustedTextRect( aNewTextRect ); // <- new text rectangle is being tested by AdjustTextFrameWidthAndHeight to ensure + if ( AdjustTextFrameWidthAndHeight( aAdjustedTextRect, bHgt, bWdt ) ) // that the new text rectangle is matching the current text size from the outliner + { + if (aAdjustedTextRect != aNewTextRect && aOldTextRect != aAdjustedTextRect && + aNewTextRect.GetWidth() && aNewTextRect.GetHeight()) + { + aReturnValue = getRectangle(); + double fXScale = static_cast<double>(aOldTextRect.GetWidth()) / static_cast<double>(aNewTextRect.GetWidth()); + double fYScale = static_cast<double>(aOldTextRect.GetHeight()) / static_cast<double>(aNewTextRect.GetHeight()); + double fRightDiff = static_cast<double>( aAdjustedTextRect.Right() - aNewTextRect.Right() ) * fXScale; + double fLeftDiff = static_cast<double>( aAdjustedTextRect.Left() - aNewTextRect.Left() ) * fXScale; + double fTopDiff = static_cast<double>( aAdjustedTextRect.Top() - aNewTextRect.Top() ) * fYScale; + double fBottomDiff= static_cast<double>( aAdjustedTextRect.Bottom()- aNewTextRect.Bottom()) * fYScale; + aReturnValue.AdjustLeft(static_cast<sal_Int32>(fLeftDiff) ); + aReturnValue.AdjustRight(static_cast<sal_Int32>(fRightDiff) ); + aReturnValue.AdjustTop(static_cast<sal_Int32>(fTopDiff) ); + aReturnValue.AdjustBottom(static_cast<sal_Int32>(fBottomDiff) ); + } + } + return aReturnValue; +} + +bool SdrObjCustomShape::NbcAdjustTextFrameWidthAndHeight(bool bHgt, bool bWdt) +{ + tools::Rectangle aNewTextRect = ImpCalculateTextFrame(bHgt, bWdt); + const bool bRet = !aNewTextRect.IsEmpty() && aNewTextRect != getRectangle(); + if (bRet && !mbAdjustingTextFrameWidthAndHeight) + { + mbAdjustingTextFrameWidthAndHeight = true; + + // taking care of handles that should not been changed + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + + setRectangle(aNewTextRect); + SetBoundAndSnapRectsDirty(); + SetChanged(); + + for (const auto& rInteraction : aInteractionHandles) + { + try + { + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED ) + rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition ); + } + catch ( const uno::RuntimeException& ) + { + } + } + InvalidateRenderGeometry(); + + mbAdjustingTextFrameWidthAndHeight = false; + } + return bRet; +} + +bool SdrObjCustomShape::AdjustTextFrameWidthAndHeight() +{ + tools::Rectangle aNewTextRect = ImpCalculateTextFrame( true/*bHgt*/, true/*bWdt*/ ); + bool bRet = !aNewTextRect.IsEmpty() && ( aNewTextRect != getRectangle()); + if ( bRet ) + { + tools::Rectangle aBoundRect0; + if ( m_pUserCall ) + aBoundRect0 = GetCurrentBoundRect(); + + // taking care of handles that should not been changed + std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() ); + + setRectangle(aNewTextRect); + SetBoundAndSnapRectsDirty(); + + for (const auto& rInteraction : aInteractionHandles) + { + try + { + if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED ) + rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition ); + } + catch ( const uno::RuntimeException& ) + { + } + } + + InvalidateRenderGeometry(); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } + return bRet; +} +void SdrObjCustomShape::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const +{ + tools::Rectangle aViewInit; + TakeTextAnchorRect( aViewInit ); + if (maGeo.m_nRotationAngle) + { + Point aCenter(aViewInit.Center()); + aCenter-=aViewInit.TopLeft(); + Point aCenter0(aCenter); + RotatePoint(aCenter, Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aCenter-=aCenter0; + aViewInit.Move(aCenter.X(),aCenter.Y()); + } + Size aAnkSiz(aViewInit.GetSize()); + aAnkSiz.AdjustWidth( -1 ); aAnkSiz.AdjustHeight( -1 ); // because GetSize() adds 1 + Size aMaxSiz(1000000,1000000); + { + Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize()); + if (aTmpSiz.Width()!=0) aMaxSiz.setWidth(aTmpSiz.Width() ); + if (aTmpSiz.Height()!=0) aMaxSiz.setHeight(aTmpSiz.Height() ); + } + SdrTextHorzAdjust eHAdj(GetTextHorizontalAdjust()); + SdrTextVertAdjust eVAdj(GetTextVerticalAdjust()); + + tools::Long nMinWdt = GetMinTextFrameWidth(); + tools::Long nMinHgt = GetMinTextFrameHeight(); + tools::Long nMaxWdt = GetMaxTextFrameWidth(); + tools::Long nMaxHgt = GetMaxTextFrameHeight(); + if (nMinWdt<1) nMinWdt=1; + if (nMinHgt<1) nMinHgt=1; + if ( nMaxWdt == 0 || nMaxWdt > aMaxSiz.Width() ) + nMaxWdt = aMaxSiz.Width(); + if ( nMaxHgt == 0 || nMaxHgt > aMaxSiz.Height() ) + nMaxHgt=aMaxSiz.Height(); + + if (GetMergedItem(SDRATTR_TEXT_WORDWRAP).GetValue()) + { + if ( IsVerticalWriting() ) + { + nMaxHgt = aAnkSiz.Height(); + nMinHgt = nMaxHgt; + } + else + { + nMaxWdt = aAnkSiz.Width(); + nMinWdt = nMaxWdt; + } + } + Size aPaperMax(nMaxWdt, nMaxHgt); + Size aPaperMin(nMinWdt, nMinHgt); + + if ( pViewMin ) + { + *pViewMin = aViewInit; + + tools::Long nXFree = aAnkSiz.Width() - aPaperMin.Width(); + if ( eHAdj == SDRTEXTHORZADJUST_LEFT ) + pViewMin->AdjustRight( -nXFree ); + else if ( eHAdj == SDRTEXTHORZADJUST_RIGHT ) + pViewMin->AdjustLeft(nXFree ); + else { pViewMin->AdjustLeft(nXFree / 2 ); pViewMin->SetRight( pViewMin->Left() + aPaperMin.Width() ); } + + tools::Long nYFree = aAnkSiz.Height() - aPaperMin.Height(); + if ( eVAdj == SDRTEXTVERTADJUST_TOP ) + pViewMin->AdjustBottom( -nYFree ); + else if ( eVAdj == SDRTEXTVERTADJUST_BOTTOM ) + pViewMin->AdjustTop(nYFree ); + else { pViewMin->AdjustTop(nYFree / 2 ); pViewMin->SetBottom( pViewMin->Top() + aPaperMin.Height() ); } + } + + if( IsVerticalWriting() ) + aPaperMin.setWidth( 0 ); + else + aPaperMin.setHeight( 0 ); + + if( eHAdj != SDRTEXTHORZADJUST_BLOCK ) + aPaperMin.setWidth(0 ); + + // For complete vertical adjust support, set paper min height to 0, here. + if(SDRTEXTVERTADJUST_BLOCK != eVAdj ) + aPaperMin.setHeight( 0 ); + + if (pPaperMin!=nullptr) *pPaperMin=aPaperMin; + if (pPaperMax!=nullptr) *pPaperMax=aPaperMax; + if (pViewInit!=nullptr) *pViewInit=aViewInit; +} +void SdrObjCustomShape::EndTextEdit( SdrOutliner& rOutl ) +{ + SdrTextObj::EndTextEdit( rOutl ); + InvalidateRenderGeometry(); +} +void SdrObjCustomShape::TakeTextAnchorRect( tools::Rectangle& rAnchorRect ) const +{ + if ( GetTextBounds( rAnchorRect ) ) + { + Point aRotateRef( maSnapRect.Center() ); + AdjustRectToTextDistance(rAnchorRect); + + if ( rAnchorRect.GetWidth() < 2 ) + rAnchorRect.SetRight( rAnchorRect.Left() + 1 ); // minimal width is 2 + if ( rAnchorRect.GetHeight() < 2 ) + rAnchorRect.SetBottom( rAnchorRect.Top() + 1 ); // minimal height is 2 + if (maGeo.m_nRotationAngle) + { + Point aP( rAnchorRect.TopLeft() ); + RotatePoint(aP, aRotateRef, maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + rAnchorRect.SetPos( aP ); + } + } + else + SdrTextObj::TakeTextAnchorRect( rAnchorRect ); +} +void SdrObjCustomShape::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, + tools::Rectangle* pAnchorRect, bool /*bLineWidth*/) const +{ + tools::Rectangle aAnkRect; // Rect in which we anchor + TakeTextAnchorRect(aAnkRect); + SdrTextVertAdjust eVAdj=GetTextVerticalAdjust(); + SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust(); + EEControlBits nStat0=rOutliner.GetControlWord(); + Size aNullSize; + + rOutliner.SetControlWord(nStat0|EEControlBits::AUTOPAGESIZE); + rOutliner.SetMinAutoPaperSize(aNullSize); + sal_Int32 nMaxAutoPaperWidth = 1000000; + sal_Int32 nMaxAutoPaperHeight= 1000000; + + tools::Long nAnkWdt=aAnkRect.GetWidth(); + tools::Long nAnkHgt=aAnkRect.GetHeight(); + + if (GetMergedItem(SDRATTR_TEXT_WORDWRAP).GetValue()) + { + if ( IsVerticalWriting() ) + nMaxAutoPaperHeight = nAnkHgt; + else + nMaxAutoPaperWidth = nAnkWdt; + } + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(nAnkWdt, 0)); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnkHgt)); + } + rOutliner.SetMaxAutoPaperSize( Size( nMaxAutoPaperWidth, nMaxAutoPaperHeight ) ); + rOutliner.SetPaperSize( aNullSize ); + + // put text into the Outliner - if necessary the use the text from the EditOutliner + std::optional<OutlinerParaObject> pPara; + if (GetOutlinerParaObject()) + pPara = *GetOutlinerParaObject(); + if (mpEditingOutliner && !bNoEditText) + pPara=mpEditingOutliner->CreateParaObject(); + + if (pPara) + { + bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner); + const SdrTextObj* pTestObj = rOutliner.GetTextObj(); + + if( !pTestObj || !bHitTest || pTestObj != this || + pTestObj->GetOutlinerParaObject() != GetOutlinerParaObject() ) + { + if( bHitTest ) + rOutliner.SetTextObj( this ); + + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pPara); + } + } + else + { + rOutliner.SetTextObj( nullptr ); + } + + rOutliner.SetUpdateLayout(true); + rOutliner.SetControlWord(nStat0); + + SdrText* pText = getActiveText(); + if( pText ) + pText->CheckPortionInfo( rOutliner ); + + Point aTextPos(aAnkRect.TopLeft()); + Size aTextSiz(rOutliner.GetPaperSize()); // GetPaperSize() has a little added tolerance, no? + + // For draw objects containing text correct horizontal/vertical alignment if text is bigger + // than the object itself. Without that correction, the text would always be + // formatted to the left edge (or top edge when vertical) of the draw object. + + if( !IsTextFrame() ) + { + if(aAnkRect.GetWidth() < aTextSiz.Width() && !IsVerticalWriting()) + { + // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTHORZADJUST_BLOCK == eHAdj) + { + SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust(); + switch (eAdjust) + { + case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break; + case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break; + case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break; + default: break; + } + } + } + + if(aAnkRect.GetHeight() < aTextSiz.Height() && IsVerticalWriting()) + { + // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTVERTADJUST_BLOCK == eVAdj) + { + eVAdj = SDRTEXTVERTADJUST_CENTER; + } + } + } + + if (eHAdj==SDRTEXTHORZADJUST_CENTER || eHAdj==SDRTEXTHORZADJUST_RIGHT) + { + tools::Long nFreeWdt=aAnkRect.GetWidth()-aTextSiz.Width(); + if (eHAdj==SDRTEXTHORZADJUST_CENTER) + aTextPos.AdjustX(nFreeWdt/2 ); + if (eHAdj==SDRTEXTHORZADJUST_RIGHT) + aTextPos.AdjustX(nFreeWdt ); + } + if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM) + { + tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height(); + if (eVAdj==SDRTEXTVERTADJUST_CENTER) + aTextPos.AdjustY(nFreeHgt/2 ); + if (eVAdj==SDRTEXTVERTADJUST_BOTTOM) + aTextPos.AdjustY(nFreeHgt ); + } + if (maGeo.m_nRotationAngle != 0_deg100) + RotatePoint(aTextPos,aAnkRect.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + if (pAnchorRect) + *pAnchorRect=aAnkRect; + + // using rTextRect together with ContourFrame doesn't always work correctly + rTextRect=tools::Rectangle(aTextPos,aTextSiz); +} + +void SdrObjCustomShape::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject) +{ + SdrTextObj::NbcSetOutlinerParaObject( std::move(pTextObject) ); + SetBoundRectDirty(); + SetBoundAndSnapRectsDirty(true); + InvalidateRenderGeometry(); +} + +rtl::Reference<SdrObject> SdrObjCustomShape::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrObjCustomShape(rTargetModel, *this); +} + +OUString SdrObjCustomShape::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulCUSTOMSHAPE)); + OUString aNm(GetName()); + if (!aNm.isEmpty()) + sName += " '" + aNm + "'"; + return sName; +} + +OUString SdrObjCustomShape::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralCUSTOMSHAPE); +} + +basegfx::B2DPolyPolygon SdrObjCustomShape::TakeXorPoly() const +{ + return GetLineGeometry( false ); +} + +basegfx::B2DPolyPolygon SdrObjCustomShape::TakeContour() const +{ + const SdrObject* pSdrObject = GetSdrObjectFromCustomShape(); + if ( pSdrObject ) + return pSdrObject->TakeContour(); + return basegfx::B2DPolyPolygon(); +} + +rtl::Reference<SdrObject> SdrObjCustomShape::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + // #i37011# + rtl::Reference<SdrObject> pRetval; + SdrObject* pRenderedCustomShape = nullptr; + + if ( !mXRenderedCustomShape.is() ) + { + // force CustomShape + GetSdrObjectFromCustomShape(); + } + + if ( mXRenderedCustomShape.is() ) + { + pRenderedCustomShape = SdrObject::getSdrObjectFromXShape(mXRenderedCustomShape); + } + + if ( pRenderedCustomShape ) + { + // Clone to same SdrModel + rtl::Reference<SdrObject> pCandidate(pRenderedCustomShape->CloneSdrObject(pRenderedCustomShape->getSdrModelFromSdrObject())); + DBG_ASSERT(pCandidate, "SdrObjCustomShape::DoConvertToPolyObj: Could not clone SdrObject (!)"); + pRetval = pCandidate->DoConvertToPolyObj(bBezier, bAddText); + pCandidate.clear(); + + if(pRetval) + { + const bool bShadow(GetMergedItem(SDRATTR_SHADOW).GetValue()); + if(bShadow) + { + pRetval->SetMergedItem(makeSdrShadowItem(true)); + } + } + + if(bAddText && HasText() && !IsTextPath()) + { + pRetval = ImpConvertAddText(std::move(pRetval), bBezier); + } + } + + return pRetval; +} + +void SdrObjCustomShape::InternalSetStyleSheet( SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr, bool bBroadcast ) +{ + // #i40944# + InvalidateRenderGeometry(); + SdrObject::InternalSetStyleSheet( pNewStyleSheet, bDontRemoveHardAttr, bBroadcast ); +} + +void SdrObjCustomShape::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + // call parent + SdrTextObj::handlePageChange(pOldPage, pNewPage); + + if(nullptr != pNewPage) + { + // invalidating rectangles by SetRectsDirty is not sufficient, + // AdjustTextFrameWidthAndHeight() also has to be made, both + // actions are done by NbcSetSnapRect + tools::Rectangle aRectangle(getRectangle()); //creating temporary rectangle #i61108# + NbcSetSnapRect(aRectangle); + } +} + +std::unique_ptr<SdrObjGeoData> SdrObjCustomShape::NewGeoData() const +{ + return std::make_unique<SdrAShapeObjGeoData>(); +} + +void SdrObjCustomShape::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrTextObj::SaveGeoData( rGeo ); + SdrAShapeObjGeoData& rAGeo=static_cast<SdrAShapeObjGeoData&>(rGeo); + rAGeo.fObjectRotation = m_fObjectRotation; + rAGeo.bMirroredX = IsMirroredX(); + rAGeo.bMirroredY = IsMirroredY(); + + const uno::Any* pAny = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ).GetPropertyValueByName( "AdjustmentValues" ); + if ( pAny ) + *pAny >>= rAGeo.aAdjustmentSeq; +} + +void SdrObjCustomShape::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrTextObj::RestoreGeoData( rGeo ); + const SdrAShapeObjGeoData& rAGeo=static_cast<const SdrAShapeObjGeoData&>(rGeo); + m_fObjectRotation = rAGeo.fObjectRotation; + SetMirroredX( rAGeo.bMirroredX ); + SetMirroredY( rAGeo.bMirroredY ); + + SdrCustomShapeGeometryItem rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ); + beans::PropertyValue aPropVal; + aPropVal.Name = "AdjustmentValues"; + aPropVal.Value <<= rAGeo.aAdjustmentSeq; + rGeometryItem.SetPropertyValue( aPropVal ); + SetMergedItem( rGeometryItem ); + + InvalidateRenderGeometry(); +} + +void SdrObjCustomShape::AdjustToMaxRect(const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */) +{ + SAL_INFO_IF(bShrinkOnly, "svx", "Case bShrinkOnly == true is not implemented yet."); + + if (rMaxRect.IsEmpty() || rMaxRect == GetSnapRect()) + return; + + // Get a matrix, that would produce the existing shape, when applied to a unit square + basegfx::B2DPolyPolygon aPolyPolygon; //not used, but formal needed + basegfx::B2DHomMatrix aMatrix; + TRGetBaseGeometry(aMatrix, aPolyPolygon); + // Using TRSetBaseGeometry(aMatrix, aPolyPolygon) would regenerate the current shape. But + // applying aMatrix to a unit square will not generate the current shape. Scaling, + // rotation and translation are correct, but shear angle has wrong sign. So break up + // matrix and create a mathematically correct new one. + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + aMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + basegfx::B2DHomMatrix aMathMatrix; + aMathMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + basegfx::fTools::equalZero(fShearX) ? 0.0 : -fShearX, + basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate, + aTranslate); + + // Calculate scaling factors from size of the transformed unit polygon as ersatz for the not + // usable current snap rectangle. + basegfx::B2DPolygon aB2DPolygon(basegfx::utils::createUnitPolygon()); + aB2DPolygon.transform(aMathMatrix); + basegfx::B2DRange aB2DRange(aB2DPolygon.getB2DRange()); + double fPolygonWidth = aB2DRange.getWidth(); + if (fPolygonWidth == 0) + fPolygonWidth = 1; + double fPolygonHeight = aB2DRange.getHeight(); + if (fPolygonHeight == 0) + fPolygonHeight = 1; + const double aFactorX = static_cast<double>(rMaxRect.GetWidth()) / fPolygonWidth; + const double aFactorY = static_cast<double>(rMaxRect.GetHeight()) / fPolygonHeight; + + // Generate matrix, that would produce the desired rMaxRect when applied to unit square + aMathMatrix.scale(aFactorX, aFactorY); + aB2DPolygon = basegfx::utils::createUnitPolygon(); + aB2DPolygon.transform(aMathMatrix); + aB2DRange = aB2DPolygon.getB2DRange(); + const double fPolygonLeft = aB2DRange.getMinX(); + const double fPolygonTop = aB2DRange.getMinY(); + aMathMatrix.translate(rMaxRect.Left() - fPolygonLeft, rMaxRect.Top() - fPolygonTop); + + // Create a Matrix from aMathMatrix, which is usable with TRSetBaseGeometry + aMathMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + aMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + basegfx::fTools::equalZero(fShearX) ? 0.0 : -fShearX, + basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate, + aTranslate); + + // Now use TRSetBaseGeometry to actually perform scale, shear, rotate and translate + // on the shape. That considers gluepoints, interaction handles and text area, and includes + // setting rectangles dirty and broadcast. + TRSetBaseGeometry(aMatrix, aPolyPolygon); +} + +void SdrObjCustomShape::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/) +{ + // The shape might have already flipping in its enhanced geometry. LibreOffice applies + // such after all transformations. We remove it, but remember it to apply them later. + bool bIsMirroredX = IsMirroredX(); + bool bIsMirroredY = IsMirroredY(); + if (bIsMirroredX || bIsMirroredY) + { + Point aCurrentCenter = GetSnapRect().Center(); + if (bIsMirroredX) // mirror on the y-axis + { + Mirror(aCurrentCenter, Point(aCurrentCenter.X(), aCurrentCenter.Y() + 1000)); + } + if (bIsMirroredY) // mirror on the x-axis + { + Mirror(aCurrentCenter, Point(aCurrentCenter.X() + 1000, aCurrentCenter.Y())); + } + } + + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + // reset object shear and rotations + m_fObjectRotation = 0.0; + maGeo.m_nRotationAngle = 0_deg100; + maGeo.RecalcSinCos(); + maGeo.m_nShearAngle = 0_deg100; + maGeo.RecalcTan(); + + // if anchor is used, make position relative to it + if(getSdrModelFromSdrObject().IsWriter()) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // scale + Size aSize(FRound(fabs(aScale.getX())), FRound(fabs(aScale.getY()))); + // fdo#47434 We need a valid rectangle here + if( !aSize.Height() ) aSize.setHeight( 1 ); + if( !aSize.Width() ) aSize.setWidth( 1 ); + tools::Rectangle aBaseRect(Point(), aSize); + SetLogicRect(aBaseRect); + + // Apply flipping from Matrix, which is a transformation relative to origin + if (basegfx::fTools::less(aScale.getX(), 0.0)) + Mirror(Point(0, 0), Point(0, 1000)); // mirror on the y-axis + if (basegfx::fTools::less(aScale.getY(), 0.0)) + Mirror(Point(0, 0), Point(1000, 0)); // mirror on the x-axis + + // shear? + if(!basegfx::fTools::equalZero(fShearX)) + { + GeoStat aGeoStat; + // #i123181# The fix for #121932# here was wrong, the trunk version does not correct the + // mirrored shear values, neither at the object level, nor on the API or XML level. Taking + // back the mirroring of the shear angle + aGeoStat.m_nShearAngle = Degree100(FRound(basegfx::rad2deg<100>(atan(fShearX)))); + aGeoStat.RecalcTan(); + Shear(Point(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false); + } + + // rotation? + if(!basegfx::fTools::equalZero(fRotate)) + { + GeoStat aGeoStat; + + // #i78696# + // fRotate is mathematically correct, but aGeoStat.nRotationAngle is + // mirrored -> mirror value here + aGeoStat.m_nRotationAngle = NormAngle36000(Degree100(FRound(-basegfx::rad2deg<100>(fRotate)))); + aGeoStat.RecalcSinCos(); + Rotate(Point(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle); + } + + // translate? + if(!aTranslate.equalZero()) + { + Move(Size(FRound(aTranslate.getX()), FRound(aTranslate.getY()))); + } + + // Apply flipping from enhanced geometry at center of the shape. + if (!(bIsMirroredX || bIsMirroredY)) + return; + + // create mathematically matrix for the applied transformations + // aScale was in most cases built from a rectangle including edge + // and is therefore mathematically too large by 1 + if (aScale.getX() > 2.0 && aScale.getY() > 2.0) + aScale -= basegfx::B2DTuple(1.0, 1.0); + basegfx::B2DHomMatrix aMathMat = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, -fShearX, basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate, + aTranslate); + // Use matrix to get current center + basegfx::B2DPoint aCenter(0.5,0.5); + aCenter = aMathMat * aCenter; + double fCenterX = aCenter.getX(); + double fCenterY = aCenter.getY(); + if (bIsMirroredX) // vertical axis + Mirror(Point(FRound(fCenterX),FRound(fCenterY)), + Point(FRound(fCenterX), FRound(fCenterY + 1000.0))); + if (bIsMirroredY) // horizontal axis + Mirror(Point(FRound(fCenterX),FRound(fCenterY)), + Point(FRound(fCenterX + 1000.0), FRound(fCenterY))); +} + +// taking fObjectRotation instead of aGeo.nAngle +bool SdrObjCustomShape::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const +{ + // get turn and shear + double fRotate = basegfx::deg2rad(m_fObjectRotation); + double fShearX = toRadians(maGeo.m_nShearAngle); + + // get aRectangle, this is the unrotated snaprect + tools::Rectangle aRectangle(getRectangle()); + + bool bMirroredX = IsMirroredX(); + bool bMirroredY = IsMirroredY(); + if ( bMirroredX || bMirroredY ) + { // we have to retrieve the unmirrored rect + + GeoStat aNewGeo(maGeo); + + if ( bMirroredX ) + { + fShearX = -fShearX; + tools::Polygon aPol = Rect2Poly(getRectangle(), aNewGeo); + tools::Rectangle aBoundRect( aPol.GetBoundRect() ); + + Point aRef1( ( aBoundRect.Left() + aBoundRect.Right() ) >> 1, aBoundRect.Top() ); + Point aRef2( aRef1.X(), aRef1.Y() + 1000 ); + sal_uInt16 i; + sal_uInt16 nPointCount=aPol.GetSize(); + for (i=0; i<nPointCount; i++) + { + MirrorPoint(aPol[i],aRef1,aRef2); + } + // mirror polygon and move it a bit + tools::Polygon aPol0(aPol); + aPol[0]=aPol0[1]; + aPol[1]=aPol0[0]; + aPol[2]=aPol0[3]; + aPol[3]=aPol0[2]; + aPol[4]=aPol0[1]; + aRectangle = svx::polygonToRectangle(aPol, aNewGeo); + } + if ( bMirroredY ) + { + fShearX = -fShearX; + tools::Polygon aPol( Rect2Poly( aRectangle, aNewGeo ) ); + tools::Rectangle aBoundRect( aPol.GetBoundRect() ); + + Point aRef1( aBoundRect.Left(), ( aBoundRect.Top() + aBoundRect.Bottom() ) >> 1 ); + Point aRef2( aRef1.X() + 1000, aRef1.Y() ); + sal_uInt16 i; + sal_uInt16 nPointCount=aPol.GetSize(); + for (i=0; i<nPointCount; i++) + { + MirrorPoint(aPol[i],aRef1,aRef2); + } + // mirror polygon and move it a bit + tools::Polygon aPol0(aPol); + aPol[0]=aPol0[1]; // This was WRONG for vertical (!) + aPol[1]=aPol0[0]; // #i121932# Despite my own comment above + aPol[2]=aPol0[3]; // it was *not* wrong even when the reordering + aPol[3]=aPol0[2]; // *seems* to be specific for X-Mirrorings. Oh + aPol[4]=aPol0[1]; // will I be happy when this old stuff is |gone| with aw080 (!) + aRectangle = svx::polygonToRectangle(aPol, aNewGeo); + } + } + + // fill other values + basegfx::B2DTuple aScale(aRectangle.GetWidth(), aRectangle.GetHeight()); + basegfx::B2DTuple aTranslate(aRectangle.Left(), aRectangle.Top()); + + // position may be relative to anchorpos, convert + if(getSdrModelFromSdrObject().IsWriter()) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build matrix + rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX), + basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate, + aTranslate); + + return false; +} + +std::unique_ptr<sdr::contact::ViewContact> SdrObjCustomShape::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrObjCustomShape>(*this); +} + +// #i33136# +bool SdrObjCustomShape::doConstructOrthogonal(std::u16string_view rName) +{ + bool bRetval(false); + + if(o3tl::equalsIgnoreAsciiCase(rName, u"quadrat")) + { + bRetval = true; + } + else if(o3tl::equalsIgnoreAsciiCase(rName, u"round-quadrat")) + { + bRetval = true; + } + else if(o3tl::equalsIgnoreAsciiCase(rName, u"circle")) + { + bRetval = true; + } + else if(o3tl::equalsIgnoreAsciiCase(rName, u"circle-pie")) + { + bRetval = true; + } + else if(o3tl::equalsIgnoreAsciiCase(rName, u"ring")) + { + bRetval = true; + } + + return bRetval; +} + +// #i37011# centralize throw-away of render geometry +void SdrObjCustomShape::InvalidateRenderGeometry() +{ + mXRenderedCustomShape = nullptr; + mpLastShadowGeometry = nullptr; +} + +void SdrObjCustomShape::setUnoShape(const uno::Reference<drawing::XShape>& rxUnoShape) +{ + SdrTextObj::setUnoShape(rxUnoShape); + + // The shape engine is created with _current_ shape. This means we + // _must_ reset it when the shape changes. + mxCustomShapeEngine.clear(); +} + +OUString SdrObjCustomShape::GetCustomShapeName() const +{ + OUString sShapeName; + OUString aEngine( GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue() ); + if ( aEngine.isEmpty() + || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine" ) + { + OUString sShapeType; + const SdrCustomShapeGeometryItem& rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); + const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "Type" ); + if ( pAny && ( *pAny >>= sShapeType ) ) + sShapeName = EnhancedCustomShapeTypeNames::GetAccName( sShapeType ); + } + return sShapeName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoattr.cxx b/svx/source/svdraw/svdoattr.cxx new file mode 100644 index 0000000000..ae4f59baa5 --- /dev/null +++ b/svx/source/svdraw/svdoattr.cxx @@ -0,0 +1,98 @@ +/* -*- 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/svdoattr.hxx> +#include <svx/svdmodel.hxx> +#include <svl/hint.hxx> +#include <svl/itemset.hxx> +#include <svx/xdef.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xlnwtit.hxx> +#include <sdr/properties/attributeproperties.hxx> + +using namespace com::sun::star; + +SdrAttrObj::SdrAttrObj(SdrModel& rSdrModel) + : SdrObject(rSdrModel) +{ +} + +SdrAttrObj::SdrAttrObj(SdrModel& rSdrModel, SdrAttrObj const& rSource) + : SdrObject(rSdrModel, rSource) +{ +} + +SdrAttrObj::~SdrAttrObj() {} + +const tools::Rectangle& SdrAttrObj::GetSnapRect() const +{ + if (m_bSnapRectDirty) + { + const_cast<SdrAttrObj*>(this)->RecalcSnapRect(); + const_cast<SdrAttrObj*>(this)->m_bSnapRectDirty = false; + } + + return maSnapRect; +} + +// syntactical sugar for ItemSet accesses +void SdrAttrObj::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint) +{ + bool bDataChg(SfxHintId::DataChanged == rHint.GetId()); + + if (bDataChg) + { + tools::Rectangle aBoundRect = GetLastBoundRect(); + SetBoundRectDirty(); + SetBoundAndSnapRectsDirty(/*bNotMyself*/ true); + + // This may have led to object change + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::ChangeAttr, aBoundRect); + } +} + +sal_Int32 SdrAttrObj::ImpGetLineWdt() const +{ + sal_Int32 nRetval(0); + + if (drawing::LineStyle_NONE != GetObjectItem(XATTR_LINESTYLE).GetValue()) + { + nRetval = GetObjectItem(XATTR_LINEWIDTH).GetValue(); + } + + return nRetval; +} + +bool SdrAttrObj::HasFill() const +{ + return m_bClosedObj + && GetProperties().GetObjectItemSet().Get(XATTR_FILLSTYLE).GetValue() + != drawing::FillStyle_NONE; +} + +bool SdrAttrObj::HasLine() const +{ + return GetProperties().GetObjectItemSet().Get(XATTR_LINESTYLE).GetValue() + != drawing::LineStyle_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdobj.cxx b/svx/source/svdraw/svdobj.cxx new file mode 100644 index 0000000000..f4d13219db --- /dev/null +++ b/svx/source/svdraw/svdobj.cxx @@ -0,0 +1,3434 @@ +/* -*- 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/svdobj.hxx> +#include <config_features.h> + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/frame/Desktop.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <drawinglayer/processor2d/contourextractor2d.hxx> +#include <drawinglayer/processor2d/linegeometryextractor2d.hxx> +#include <comphelper/processfactory.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outlobj.hxx> +#include <o3tl/deleter.hxx> +#include <math.h> +#include <svl/grabbagitem.hxx> +#include <tools/bigint.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/helpers.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> +#include <vector> + +#include <svx/svdotable.hxx> + +#include <svx/sdr/contact/displayinfo.hxx> +#include <sdr/contact/objectcontactofobjlistpainter.hxx> +#include <svx/sdr/contact/viewcontactofsdrobj.hxx> +#include <sdr/properties/emptyproperties.hxx> +#include <svx/sdrhittesthelper.hxx> +#include <svx/sdrobjectuser.hxx> +#include <svx/sdrobjectfilter.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svditer.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdomeas.hxx> +#include <svx/svdomedia.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdopage.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdovirt.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpool.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdview.hxx> +#include <sxlayitm.hxx> +#include <sxlogitm.hxx> +#include <sxmovitm.hxx> +#include <sxoneitm.hxx> +#include <sxopitm.hxx> +#include <sxreoitm.hxx> +#include <sxrooitm.hxx> +#include <sxsaitm.hxx> +#include <sxsoitm.hxx> +#include <sxtraitm.hxx> +#include <svx/unopage.hxx> +#include <svx/unoshape.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xfltrit.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlntrit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/svdglue.hxx> +#include <svx/svdsob.hxx> +#include <svdobjplusdata.hxx> +#include <svdobjuserdatalist.hxx> + +#include <unordered_set> + +#include <optional> +#include <libxml/xmlwriter.h> +#include <memory> + +#include <svx/scene3d.hxx> +#include <rtl/character.hxx> +#include <tools/UnitConversion.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::com::sun::star; + + +SdrObjUserCall::~SdrObjUserCall() +{ +} + +void SdrObjUserCall::Changed(const SdrObject& /*rObj*/, SdrUserCallType /*eType*/, const tools::Rectangle& /*rOldBoundRect*/) +{ +} + +void const* SdrObjUserCall::GetPDFAnchorStructureElementKey(SdrObject const&) +{ + return nullptr; +} + +SdrObjMacroHitRec::SdrObjMacroHitRec() : + pVisiLayer(nullptr), + pPageView(nullptr), + nTol(0) {} + + +SdrObjUserData::SdrObjUserData(SdrInventor nInv, sal_uInt16 nId) : + m_nInventor(nInv), + m_nIdentifier(nId) {} + +SdrObjUserData::SdrObjUserData(const SdrObjUserData& rData) : + m_nInventor(rData.m_nInventor), + m_nIdentifier(rData.m_nIdentifier) {} + +SdrObjUserData::~SdrObjUserData() {} + +SdrObjGeoData::SdrObjGeoData(): + bMovProt(false), + bSizProt(false), + bNoPrint(false), + bClosedObj(false), + mbVisible(true), + mnLayerID(0) +{ +} + +SdrObjGeoData::~SdrObjGeoData() +{ +} + +SdrObjTransformInfoRec::SdrObjTransformInfoRec() : + bMoveAllowed(true), + bResizeFreeAllowed(true), + bResizePropAllowed(true), + bRotateFreeAllowed(true), + bRotate90Allowed(true), + bMirrorFreeAllowed(true), + bMirror45Allowed(true), + bMirror90Allowed(true), + bTransparenceAllowed(true), + bShearAllowed(true), + bEdgeRadiusAllowed(true), + bNoOrthoDesired(true), + bNoContortion(true), + bCanConvToPath(true), + bCanConvToPoly(true), + bCanConvToContour(false), + bCanConvToPathLineToArea(true), + bCanConvToPolyLineToArea(true) {} + +struct SdrObject::Impl +{ + sdr::ObjectUserVector maObjectUsers; + std::optional<double> mnRelativeWidth; + std::optional<double> mnRelativeHeight; + sal_Int16 meRelativeWidthRelation; + sal_Int16 meRelativeHeightRelation; + + Impl() : + meRelativeWidthRelation(text::RelOrientation::PAGE_FRAME), + meRelativeHeightRelation(text::RelOrientation::PAGE_FRAME) {} +}; + +const std::shared_ptr< svx::diagram::IDiagramHelper >& SdrObject::getDiagramHelper() const +{ + static std::shared_ptr< svx::diagram::IDiagramHelper > aEmpty; + return aEmpty; +} + +// BaseProperties section + +sdr::properties::BaseProperties& SdrObject::GetProperties() const +{ + if(!mpProperties) + { + // CAUTION(!) Do *not* call this during SdrObject construction, + // that will lead to wrong type-casts (dependent on constructor-level) + // and thus eventually create the wrong sdr::properties (!). Is there + // a way to check if on the stack is a SdrObject-constructor (?) + const_cast< SdrObject* >(this)->mpProperties = + const_cast< SdrObject* >(this)->CreateObjectSpecificProperties(); + } + + return *mpProperties; +} + + +// ObjectUser section + +void SdrObject::AddObjectUser(sdr::ObjectUser& rNewUser) +{ + mpImpl->maObjectUsers.push_back(&rNewUser); +} + +void SdrObject::RemoveObjectUser(sdr::ObjectUser& rOldUser) +{ + const sdr::ObjectUserVector::iterator aFindResult = + std::find(mpImpl->maObjectUsers.begin(), mpImpl->maObjectUsers.end(), &rOldUser); + if (aFindResult != mpImpl->maObjectUsers.end()) + { + mpImpl->maObjectUsers.erase(aFindResult); + } +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrObject::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrObj>(*this); +} + +sdr::contact::ViewContact& SdrObject::GetViewContact() const +{ + if(!mpViewContact) + { + const_cast< SdrObject* >(this)->mpViewContact = + const_cast< SdrObject* >(this)->CreateObjectSpecificViewContact(); + } + + return *mpViewContact; +} + +// DrawContact support: Methods for handling Object changes +void SdrObject::ActionChanged() const +{ + // Do necessary ViewContact actions + GetViewContact().ActionChanged(); +} + +SdrPage* SdrObject::getSdrPageFromSdrObject() const +{ + if (SdrObjList* pParentList = getParentSdrObjListFromSdrObject()) + { + return pParentList->getSdrPageFromSdrObjList(); + } + + return nullptr; +} + +SdrModel& SdrObject::getSdrModelFromSdrObject() const +{ + return mrSdrModelFromSdrObject; +} + +void SdrObject::setParentOfSdrObject(SdrObjList* pNewObjList) +{ + assert(!pNewObjList || mpParentOfSdrObject != pNewObjList); + if(mpParentOfSdrObject == pNewObjList) + return; + // we need to be removed from the old parent before we are attached to the new parent + assert(bool(mpParentOfSdrObject) != bool(pNewObjList) && "may only transition empty->full or full->empty"); + + // remember current page + SdrPage* pOldPage(getSdrPageFromSdrObject()); + + // set new parent + mpParentOfSdrObject = pNewObjList; + + // get new page + SdrPage* pNewPage(getSdrPageFromSdrObject()); + + // broadcast page change over objects if needed + if(pOldPage != pNewPage) + { + handlePageChange(pOldPage, pNewPage); + } +} + +SdrObjList* SdrObject::getParentSdrObjListFromSdrObject() const +{ + return mpParentOfSdrObject; +} + +SdrObjList* SdrObject::getChildrenOfSdrObject() const +{ + // default has no children + return nullptr; +} + +void SdrObject::SetBoundRectDirty() +{ + resetOutRectangle(); +} + +#ifdef DBG_UTIL +// SdrObjectLifetimeWatchDog: +void impAddIncarnatedSdrObjectToSdrModel(SdrObject& rSdrObject, SdrModel& rSdrModel) +{ + rSdrModel.maAllIncarnatedObjects.insert(&rSdrObject); +} +void impRemoveIncarnatedSdrObjectToSdrModel(SdrObject& rSdrObject, SdrModel& rSdrModel) +{ + if(!rSdrModel.maAllIncarnatedObjects.erase(&rSdrObject)) + { + assert(false && "SdrObject::~SdrObject: Destructed incarnation of SdrObject not member of this SdrModel (!)"); + } +} +#endif + +SdrObject::SdrObject(SdrModel& rSdrModel) +: mpFillGeometryDefiningShape(nullptr) + ,mrSdrModelFromSdrObject(rSdrModel) + ,m_pUserCall(nullptr) + ,mpImpl(new Impl) + ,mpParentOfSdrObject(nullptr) + ,m_nOrdNum(0) + ,mnNavigationPosition(SAL_MAX_UINT32) + ,mnLayerID(0) + ,mpSvxShape( nullptr ) + ,mbDoNotInsertIntoPageAutomatically(false) +{ + m_bVirtObj =false; + m_bSnapRectDirty =true; + m_bMovProt =false; + m_bSizProt =false; + m_bNoPrint =false; + m_bEmptyPresObj =false; + m_bNotVisibleAsMaster=false; + m_bClosedObj =false; + mbVisible = true; + + // #i25616# + mbLineIsOutsideGeometry = false; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; + + m_bIsEdge=false; + m_bIs3DObj=false; + m_bMarkProt=false; + m_bIsUnoObj=false; +#ifdef DBG_UTIL + // SdrObjectLifetimeWatchDog: + impAddIncarnatedSdrObjectToSdrModel(*this, getSdrModelFromSdrObject()); +#endif +} + +SdrObject::SdrObject(SdrModel& rSdrModel, SdrObject const & rSource) +: mpFillGeometryDefiningShape(nullptr) + ,mrSdrModelFromSdrObject(rSdrModel) + ,m_pUserCall(nullptr) + ,mpImpl(new Impl) + ,mpParentOfSdrObject(nullptr) + ,m_nOrdNum(0) + ,mnNavigationPosition(SAL_MAX_UINT32) + ,mnLayerID(0) + ,mpSvxShape( nullptr ) + ,mbDoNotInsertIntoPageAutomatically(false) +{ + m_bVirtObj =false; + m_bSnapRectDirty =true; + m_bMovProt =false; + m_bSizProt =false; + m_bNoPrint =false; + m_bEmptyPresObj =false; + m_bNotVisibleAsMaster=false; + m_bClosedObj =false; + mbVisible = true; + + // #i25616# + mbLineIsOutsideGeometry = false; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; + + m_bIsEdge=false; + m_bIs3DObj=false; + m_bMarkProt=false; + m_bIsUnoObj=false; + + mpProperties.reset(); + mpViewContact.reset(); + + // The CloneSdrObject() method uses the local copy constructor from the individual + // sdr::properties::BaseProperties class. Since the target class maybe for another + // draw object, an SdrObject needs to be provided, as in the normal constructor. + mpProperties = rSource.GetProperties().Clone(*this); + + setOutRectangle(rSource.getOutRectangle()); + mnLayerID = rSource.mnLayerID; + m_aAnchor =rSource.m_aAnchor; + m_bVirtObj=rSource.m_bVirtObj; + m_bSizProt=rSource.m_bSizProt; + m_bMovProt=rSource.m_bMovProt; + m_bNoPrint=rSource.m_bNoPrint; + mbVisible=rSource.mbVisible; + m_bMarkProt=rSource.m_bMarkProt; + m_bEmptyPresObj =rSource.m_bEmptyPresObj; + m_bNotVisibleAsMaster=rSource.m_bNotVisibleAsMaster; + m_bSnapRectDirty=true; + m_pPlusData.reset(); + if (rSource.m_pPlusData!=nullptr) { + m_pPlusData.reset(rSource.m_pPlusData->Clone(this)); + } + if (m_pPlusData!=nullptr && m_pPlusData->pBroadcast!=nullptr) { + m_pPlusData->pBroadcast.reset(); // broadcaster isn't copied + } + + m_pGrabBagItem.reset(); + if (rSource.m_pGrabBagItem!=nullptr) + m_pGrabBagItem.reset(rSource.m_pGrabBagItem->Clone()); +#ifdef DBG_UTIL + // SdrObjectLifetimeWatchDog: + impAddIncarnatedSdrObjectToSdrModel(*this, getSdrModelFromSdrObject()); +#endif +} + +SdrObject::~SdrObject() +{ +#ifdef DBG_UTIL + // see logic in SdrObject::release + assert(m_refCount == -1); +#endif + // Tell all the registered ObjectUsers that the page is in destruction. + // And clear the vector. This means that user do not need to call RemoveObjectUser() + // when they get called from ObjectInDestruction(). + sdr::ObjectUserVector aList; + aList.swap(mpImpl->maObjectUsers); + for(sdr::ObjectUser* pObjectUser : aList) + { + DBG_ASSERT(pObjectUser, "SdrObject::~SdrObject: corrupt ObjectUser list (!)"); + pObjectUser->ObjectInDestruction(*this); + } + + // UserCall + SendUserCall(SdrUserCallType::Delete, GetLastBoundRect()); + o3tl::reset_preserve_ptr_during(m_pPlusData); + + m_pGrabBagItem.reset(); + mpProperties.reset(); + mpViewContact.reset(); +#ifdef DBG_UTIL + // SdrObjectLifetimeWatchDog: + impRemoveIncarnatedSdrObjectToSdrModel(*this, getSdrModelFromSdrObject()); +#endif +} + +void SdrObject::acquire() noexcept +{ +#ifdef DBG_UTIL + assert(m_refCount != -1); +#endif + osl_atomic_increment( &m_refCount ); +} + +void SdrObject::release() noexcept +{ + oslInterlockedCount x = osl_atomic_decrement( &m_refCount ); + if ( x == 0 ) + { + disposeWeakConnectionPoint(); +#ifdef DBG_UTIL + // make sure it doesn't accidentally come back to life, see assert in acquire() + osl_atomic_decrement( &m_refCount ); +#endif + delete this; + } +} + +void SdrObject::SetBoundAndSnapRectsDirty(bool bNotMyself, bool bRecursive) +{ + if (!bNotMyself) + { + SetBoundRectDirty(); + m_bSnapRectDirty=true; + } + + if (bRecursive && nullptr != getParentSdrObjListFromSdrObject()) + { + getParentSdrObjListFromSdrObject()->SetSdrObjListRectsDirty(); + } +} + +void SdrObject::handlePageChange(SdrPage*, SdrPage* ) +{ +} + + +// global static ItemPool for not-yet-inserted items +static rtl::Reference<SdrItemPool> mpGlobalItemPool; + +/** If we let the libc runtime clean us up, we trigger a crash */ +namespace +{ +class TerminateListener : public ::cppu::WeakImplHelper< css::frame::XTerminateListener > +{ + void SAL_CALL queryTermination( const lang::EventObject& ) override + {} + void SAL_CALL notifyTermination( const lang::EventObject& ) override + { + mpGlobalItemPool.clear(); + } + virtual void SAL_CALL disposing( const ::css::lang::EventObject& ) override + {} +}; +}; + +// init global static itempool +SdrItemPool& SdrObject::GetGlobalDrawObjectItemPool() +{ + if(!mpGlobalItemPool) + { + mpGlobalItemPool = new SdrItemPool(); + rtl::Reference<SfxItemPool> pGlobalOutlPool = EditEngine::CreatePool(); + mpGlobalItemPool->SetSecondaryPool(pGlobalOutlPool.get()); + mpGlobalItemPool->SetDefaultMetric(SdrEngineDefaults::GetMapUnit()); + mpGlobalItemPool->FreezeIdRanges(); + if (utl::ConfigManager::IsFuzzing()) + mpGlobalItemPool->acquire(); + else + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext()); + uno::Reference< frame::XTerminateListener > xListener( new TerminateListener ); + xDesktop->addTerminateListener( xListener ); + } + } + + return *mpGlobalItemPool; +} + +void SdrObject::SetRelativeWidth( double nValue ) +{ + mpImpl->mnRelativeWidth = nValue; +} + +void SdrObject::SetRelativeWidthRelation( sal_Int16 eValue ) +{ + mpImpl->meRelativeWidthRelation = eValue; +} + +void SdrObject::SetRelativeHeight( double nValue ) +{ + mpImpl->mnRelativeHeight = nValue; +} + +void SdrObject::SetRelativeHeightRelation( sal_Int16 eValue ) +{ + mpImpl->meRelativeHeightRelation = eValue; +} + +const double* SdrObject::GetRelativeWidth( ) const +{ + if (!mpImpl->mnRelativeWidth) + return nullptr; + + return &*mpImpl->mnRelativeWidth; +} + +sal_Int16 SdrObject::GetRelativeWidthRelation() const +{ + return mpImpl->meRelativeWidthRelation; +} + +const double* SdrObject::GetRelativeHeight( ) const +{ + if (!mpImpl->mnRelativeHeight) + return nullptr; + + return &*mpImpl->mnRelativeHeight; +} + +sal_Int16 SdrObject::GetRelativeHeightRelation() const +{ + return mpImpl->meRelativeHeightRelation; +} + +SfxItemPool& SdrObject::GetObjectItemPool() const +{ + return getSdrModelFromSdrObject().GetItemPool(); +} + +SdrInventor SdrObject::GetObjInventor() const +{ + return SdrInventor::Default; +} + +SdrObjKind SdrObject::GetObjIdentifier() const +{ + return SdrObjKind::NONE; +} + +void SdrObject::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bRotateFreeAllowed=false; + rInfo.bMirrorFreeAllowed=false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =false; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bCanConvToPath =false; + rInfo.bCanConvToPoly =false; + rInfo.bCanConvToContour = false; + rInfo.bCanConvToPathLineToArea=false; + rInfo.bCanConvToPolyLineToArea=false; +} + +SdrLayerID SdrObject::GetLayer() const +{ + return mnLayerID; +} + +bool SdrObject::isVisibleOnAnyOfTheseLayers(const SdrLayerIDSet& rSet) const +{ + if (rSet.IsSet(GetLayer())) + return true; + SdrObjList* pOL=GetSubList(); + if (!pOL) + return false; + for (const rtl::Reference<SdrObject>& pObject : *pOL) + if (pObject->isVisibleOnAnyOfTheseLayers(rSet)) + return true; + return false; +} + +void SdrObject::NbcSetLayer(SdrLayerID nLayer) +{ + mnLayerID = nLayer; +} + +void SdrObject::SetLayer(SdrLayerID nLayer) +{ + NbcSetLayer(nLayer); + SetChanged(); + BroadcastObjectChange(); +} + +void SdrObject::AddListener(SfxListener& rListener) +{ + ImpForcePlusData(); + if (m_pPlusData->pBroadcast==nullptr) m_pPlusData->pBroadcast.reset(new SfxBroadcaster); + + // SdrEdgeObj may be connected to same SdrObject on both ends so allow it + // to listen twice + SdrEdgeObj const*const pEdge(dynamic_cast<SdrEdgeObj const*>(&rListener)); + rListener.StartListening(*m_pPlusData->pBroadcast, pEdge ? DuplicateHandling::Allow : DuplicateHandling::Unexpected); +} + +void SdrObject::RemoveListener(SfxListener& rListener) +{ + if (m_pPlusData!=nullptr && m_pPlusData->pBroadcast!=nullptr) { + rListener.EndListening(*m_pPlusData->pBroadcast); + if (!m_pPlusData->pBroadcast->HasListeners()) { + m_pPlusData->pBroadcast.reset(); + } + } +} + +const SfxBroadcaster* SdrObject::GetBroadcaster() const +{ + return m_pPlusData!=nullptr ? m_pPlusData->pBroadcast.get() : nullptr; +} + +void SdrObject::AddReference(SdrVirtObj& rVrtObj) +{ + AddListener(rVrtObj); +} + +void SdrObject::DelReference(SdrVirtObj& rVrtObj) +{ + RemoveListener(rVrtObj); +} + +bool SdrObject::IsGroupObject() const +{ + return GetSubList()!=nullptr; +} + +SdrObjList* SdrObject::GetSubList() const +{ + return nullptr; +} + +SdrObject* SdrObject::getParentSdrObjectFromSdrObject() const +{ + SdrObjList* pParent(getParentSdrObjListFromSdrObject()); + + if(nullptr == pParent) + { + return nullptr; + } + + return pParent->getSdrObjectFromSdrObjList(); +} + +void SdrObject::SetName(const OUString& rStr, const bool bSetChanged) +{ + if (!rStr.isEmpty() && !m_pPlusData) + { + ImpForcePlusData(); + } + + if(!(m_pPlusData && m_pPlusData->aObjName != rStr)) + return; + + // Undo/Redo for setting object's name (#i73249#) + bool bUndo( false ); + if ( getSdrModelFromSdrObject().IsUndoEnabled() ) + { + bUndo = true; + std::unique_ptr<SdrUndoAction> pUndoAction = + SdrUndoFactory::CreateUndoObjectStrAttr( + *this, + SdrUndoObjStrAttr::ObjStrAttrType::Name, + GetName(), + rStr ); + getSdrModelFromSdrObject().BegUndo( pUndoAction->GetComment() ); + getSdrModelFromSdrObject().AddUndo( std::move(pUndoAction) ); + } + m_pPlusData->aObjName = rStr; + // Undo/Redo for setting object's name (#i73249#) + if ( bUndo ) + { + getSdrModelFromSdrObject().EndUndo(); + } + if (bSetChanged) + { + SetChanged(); + BroadcastObjectChange(); + } +} + +const OUString & SdrObject::GetName() const +{ + static const OUString EMPTY = u""_ustr; + + if(m_pPlusData) + { + return m_pPlusData->aObjName; + } + + return EMPTY; +} + +void SdrObject::SetTitle(const OUString& rStr) +{ + if (!rStr.isEmpty() && !m_pPlusData) + { + ImpForcePlusData(); + } + + if(!(m_pPlusData && m_pPlusData->aObjTitle != rStr)) + return; + + // Undo/Redo for setting object's title (#i73249#) + bool bUndo( false ); + if ( getSdrModelFromSdrObject().IsUndoEnabled() ) + { + bUndo = true; + std::unique_ptr<SdrUndoAction> pUndoAction = + SdrUndoFactory::CreateUndoObjectStrAttr( + *this, + SdrUndoObjStrAttr::ObjStrAttrType::Title, + GetTitle(), + rStr ); + getSdrModelFromSdrObject().BegUndo( pUndoAction->GetComment() ); + getSdrModelFromSdrObject().AddUndo( std::move(pUndoAction) ); + } + m_pPlusData->aObjTitle = rStr; + // Undo/Redo for setting object's title (#i73249#) + if ( bUndo ) + { + getSdrModelFromSdrObject().EndUndo(); + } + SetChanged(); + BroadcastObjectChange(); +} + +OUString SdrObject::GetTitle() const +{ + if(m_pPlusData) + { + return m_pPlusData->aObjTitle; + } + + return OUString(); +} + +void SdrObject::SetDescription(const OUString& rStr) +{ + if (!rStr.isEmpty() && !m_pPlusData) + { + ImpForcePlusData(); + } + + if(!(m_pPlusData && m_pPlusData->aObjDescription != rStr)) + return; + + // Undo/Redo for setting object's description (#i73249#) + bool bUndo( false ); + if ( getSdrModelFromSdrObject().IsUndoEnabled() ) + { + bUndo = true; + std::unique_ptr<SdrUndoAction> pUndoAction = + SdrUndoFactory::CreateUndoObjectStrAttr( + *this, + SdrUndoObjStrAttr::ObjStrAttrType::Description, + GetDescription(), + rStr ); + getSdrModelFromSdrObject().BegUndo( pUndoAction->GetComment() ); + getSdrModelFromSdrObject().AddUndo( std::move(pUndoAction) ); + } + m_pPlusData->aObjDescription = rStr; + // Undo/Redo for setting object's description (#i73249#) + if ( bUndo ) + { + getSdrModelFromSdrObject().EndUndo(); + } + SetChanged(); + BroadcastObjectChange(); +} + +OUString SdrObject::GetDescription() const +{ + if(m_pPlusData) + { + return m_pPlusData->aObjDescription; + } + + return OUString(); +} + +void SdrObject::SetDecorative(bool const isDecorative) +{ + ImpForcePlusData(); + + if (m_pPlusData->isDecorative == isDecorative) + { + return; + } + + if (getSdrModelFromSdrObject().IsUndoEnabled()) + { + std::unique_ptr<SdrUndoAction> pUndoAction( + SdrUndoFactory::CreateUndoObjectDecorative( + *this, m_pPlusData->isDecorative)); + getSdrModelFromSdrObject().BegUndo(pUndoAction->GetComment()); + getSdrModelFromSdrObject().AddUndo(std::move(pUndoAction)); + } + + m_pPlusData->isDecorative = isDecorative; + + if (getSdrModelFromSdrObject().IsUndoEnabled()) + { + getSdrModelFromSdrObject().EndUndo(); + } + + SetChanged(); + BroadcastObjectChange(); +} + +bool SdrObject::IsDecorative() const +{ + return m_pPlusData == nullptr ? false : m_pPlusData->isDecorative; +} + +sal_uInt32 SdrObject::GetOrdNum() const +{ + if (SdrObjList* pParentList = getParentSdrObjListFromSdrObject()) + { + if (pParentList->IsObjOrdNumsDirty()) + { + pParentList->RecalcObjOrdNums(); + } + } else const_cast<SdrObject*>(this)->m_nOrdNum=0; + return m_nOrdNum; +} + +void SdrObject::SetOrdNum(sal_uInt32 nNum) +{ + m_nOrdNum = nNum; +} + +void SdrObject::GetGrabBagItem(css::uno::Any& rVal) const +{ + if (m_pGrabBagItem != nullptr) + m_pGrabBagItem->QueryValue(rVal); + else + rVal <<= uno::Sequence<beans::PropertyValue>(); +} + +void SdrObject::SetGrabBagItem(const css::uno::Any& rVal) +{ + if (m_pGrabBagItem == nullptr) + m_pGrabBagItem.reset(new SfxGrabBagItem); + + m_pGrabBagItem->PutValue(rVal, 0); + + SetChanged(); + BroadcastObjectChange(); +} + +sal_uInt32 SdrObject::GetNavigationPosition() const +{ + if (nullptr != getParentSdrObjListFromSdrObject() && getParentSdrObjListFromSdrObject()->RecalcNavigationPositions()) + { + return mnNavigationPosition; + } + else + return GetOrdNum(); +} + + +void SdrObject::SetNavigationPosition (const sal_uInt32 nNewPosition) +{ + mnNavigationPosition = nNewPosition; +} + + +// To make clearer that this method may trigger RecalcBoundRect and thus may be +// expensive and sometimes problematic (inside a bigger object change you will get +// non-useful BoundRects sometimes) I rename that method from GetBoundRect() to +// GetCurrentBoundRect(). +const tools::Rectangle& SdrObject::GetCurrentBoundRect() const +{ + auto const& rRectangle = getOutRectangle(); + if (rRectangle.IsEmpty()) + { + const_cast< SdrObject* >(this)->RecalcBoundRect(); + } + + return rRectangle; +} + +// To have a possibility to get the last calculated BoundRect e.g for producing +// the first rectangle for repaints (old and new need to be used) without forcing +// a RecalcBoundRect (which may be problematical and expensive sometimes) I add here +// a new method for accessing the last BoundRect. +const tools::Rectangle& SdrObject::GetLastBoundRect() const +{ + return getOutRectangle(); +} + +void SdrObject::RecalcBoundRect() +{ + // #i101680# suppress BoundRect calculations on import(s) + if ((getSdrModelFromSdrObject().isLocked()) || utl::ConfigManager::IsFuzzing()) + return; + + auto const& rRectangle = getOutRectangle(); + + // central new method which will calculate the BoundRect using primitive geometry + if (!rRectangle.IsEmpty()) + return; + + // Use view-independent data - we do not want any connections + // to e.g. GridOffset in SdrObject-level + drawinglayer::primitive2d::Primitive2DContainer xPrimitives; + GetViewContact().getViewIndependentPrimitive2DContainer(xPrimitives); + + if (xPrimitives.empty()) + return; + + // use neutral ViewInformation and get the range of the primitives + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D)); + + if (!aRange.isEmpty()) + { + tools::Rectangle aNewRectangle( + tools::Long(floor(aRange.getMinX())), + tools::Long(floor(aRange.getMinY())), + tools::Long(ceil(aRange.getMaxX())), + tools::Long(ceil(aRange.getMaxY()))); + setOutRectangle(aNewRectangle); + return; + } +} + +void SdrObject::BroadcastObjectChange() const +{ + if ((getSdrModelFromSdrObject().isLocked()) || utl::ConfigManager::IsFuzzing()) + return; + + bool bPlusDataBroadcast(m_pPlusData && m_pPlusData->pBroadcast); + bool bObjectChange(IsInserted()); + + if(!(bPlusDataBroadcast || bObjectChange)) + return; + + SdrHint aHint(SdrHintKind::ObjectChange, *this); + + if(bPlusDataBroadcast) + { + m_pPlusData->pBroadcast->Broadcast(aHint); + } + + if(bObjectChange) + { + getSdrModelFromSdrObject().Broadcast(aHint); + } +} + +void SdrObject::SetChanged() +{ + // For testing purposes, use the new ViewContact for change + // notification now. + ActionChanged(); + + // TTTT Need to check meaning/usage of IsInserted in one + // of the next changes. It should not mean to have a SdrModel + // set (this is guaranteed now), but should be connected to + // being added to a SdrPage (?) + // TTTT tdf#120066 Indeed - This triggers e.g. by CustomShape + // geometry-presenting SdrObjects that are in a SdrObjGroup, + // but the SdrObjGroup is *by purpose* not inserted. + // Need to check deeper and maybe identify all ::IsInserted() + // calls by rename and let the compiler work... + if(nullptr != getSdrPageFromSdrObject()) + { + getSdrModelFromSdrObject().SetChanged(); + } +} + +// tooling for painting a single object to an OutputDevice. +void SdrObject::SingleObjectPainter(OutputDevice& rOut) const +{ + sdr::contact::SdrObjectVector aObjectVector; + aObjectVector.push_back(const_cast< SdrObject* >(this)); + + sdr::contact::ObjectContactOfObjListPainter aPainter(rOut, std::move(aObjectVector), getSdrPageFromSdrObject()); + sdr::contact::DisplayInfo aDisplayInfo; + + aPainter.ProcessDisplay(aDisplayInfo); +} + +bool SdrObject::LineGeometryUsageIsNecessary() const +{ + drawing::LineStyle eXLS = GetMergedItem(XATTR_LINESTYLE).GetValue(); + return (eXLS != drawing::LineStyle_NONE); +} + +bool SdrObject::HasLimitedRotation() const +{ + // RotGrfFlyFrame: Default is false, support full rotation + return false; +} + +OUString SdrObject::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulNONE)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + return sName; +} + +OUString SdrObject::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralNONE); +} + +OUString SdrObject::ImpGetDescriptionStr(TranslateId pStrCacheID) const +{ + OUString aStr = SvxResId(pStrCacheID); + sal_Int32 nPos = aStr.indexOf("%1"); + if (nPos >= 0) + { + // Replace '%1' with the object name. + OUString aObjName(TakeObjNameSingul()); + aStr = aStr.replaceAt(nPos, 2, aObjName); + } + + nPos = aStr.indexOf("%2"); + if (nPos >= 0) + // Replace '%2' with the passed value. + aStr = aStr.replaceAt(nPos, 2, u"0"); + return aStr; +} + +void SdrObject::ImpForcePlusData() +{ + if (!m_pPlusData) + m_pPlusData.reset( new SdrObjPlusData ); +} + +OUString SdrObject::GetMetrStr(tools::Long nVal) const +{ + return getSdrModelFromSdrObject().GetMetricString(nVal); +} + +basegfx::B2DPolyPolygon SdrObject::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aRetval; + const tools::Rectangle aR(GetCurrentBoundRect()); + aRetval.append(basegfx::utils::createPolygonFromRect(vcl::unotools::b2DRectangleFromRectangle(aR))); + + return aRetval; +} + +basegfx::B2DPolyPolygon SdrObject::TakeContour() const +{ + basegfx::B2DPolyPolygon aRetval; + + // create cloned object without text, but with drawing::LineStyle_SOLID, + // COL_BLACK as line color and drawing::FillStyle_NONE + rtl::Reference<SdrObject> pClone(CloneSdrObject(getSdrModelFromSdrObject())); + + if(pClone) + { + const SdrTextObj* pTextObj = DynCastSdrTextObj(this); + + if(pTextObj) + { + // no text and no text animation + pClone->SetMergedItem(SdrTextAniKindItem(SdrTextAniKind::NONE)); + pClone->SetOutlinerParaObject(std::nullopt); + } + + const SdrEdgeObj* pEdgeObj = dynamic_cast< const SdrEdgeObj* >(this); + + if(pEdgeObj) + { + // create connections if connector, will be cleaned up when + // deleting the connector again + SdrObject* pLeft = pEdgeObj->GetConnectedNode(true); + SdrObject* pRight = pEdgeObj->GetConnectedNode(false); + + if(pLeft) + { + pClone->ConnectToNode(true, pLeft); + } + + if(pRight) + { + pClone->ConnectToNode(false, pRight); + } + } + + SfxItemSet aNewSet(GetObjectItemPool()); + + // #i101980# ignore LineWidth; that's what the old implementation + // did. With line width, the result may be huge due to fat/thick + // line decompositions + aNewSet.Put(XLineWidthItem(0)); + + // solid black lines and no fill + aNewSet.Put(XLineStyleItem(drawing::LineStyle_SOLID)); + aNewSet.Put(XLineColorItem(OUString(), COL_BLACK)); + aNewSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + pClone->SetMergedItemSet(aNewSet); + + // get sequence from clone + const sdr::contact::ViewContact& rVC(pClone->GetViewContact()); + drawinglayer::primitive2d::Primitive2DContainer xSequence; + rVC.getViewIndependentPrimitive2DContainer(xSequence); + + if(!xSequence.empty()) + { + // use neutral ViewInformation + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + + // create extractor, process and get result (with hairlines as opened polygons) + drawinglayer::processor2d::ContourExtractor2D aExtractor(aViewInformation2D, false); + aExtractor.process(xSequence); + const basegfx::B2DPolyPolygonVector& rResult(aExtractor.getExtractedContour()); + const sal_uInt32 nSize(rResult.size()); + + // when count is one, it is implied that the object has only its normal + // contour anyways and TakeContour() is to return an empty PolyPolygon + // (see old implementation for historical reasons) + if(nSize > 1) + { + // the topology for contour is correctly a vector of PolyPolygons; for + // historical reasons cut it back to a single tools::PolyPolygon here + for(sal_uInt32 a(0); a < nSize; a++) + { + aRetval.append(rResult[a]); + } + } + } + } + + return aRetval; +} + +sal_uInt32 SdrObject::GetHdlCount() const +{ + return 8; +} + +void SdrObject::AddToHdlList(SdrHdlList& rHdlList) const +{ + const tools::Rectangle& rR=GetSnapRect(); + for (sal_uInt32 nHdlNum=0; nHdlNum<8; ++nHdlNum) + { + std::unique_ptr<SdrHdl> pH; + switch (nHdlNum) { + case 0: pH.reset(new SdrHdl(rR.TopLeft(), SdrHdlKind::UpperLeft)); break; + case 1: pH.reset(new SdrHdl(rR.TopCenter(), SdrHdlKind::Upper)); break; + case 2: pH.reset(new SdrHdl(rR.TopRight(), SdrHdlKind::UpperRight)); break; + case 3: pH.reset(new SdrHdl(rR.LeftCenter(), SdrHdlKind::Left )); break; + case 4: pH.reset(new SdrHdl(rR.RightCenter(), SdrHdlKind::Right)); break; + case 5: pH.reset(new SdrHdl(rR.BottomLeft(), SdrHdlKind::LowerLeft)); break; + case 6: pH.reset(new SdrHdl(rR.BottomCenter(),SdrHdlKind::Lower)); break; + case 7: pH.reset(new SdrHdl(rR.BottomRight(), SdrHdlKind::LowerRight)); break; + } + rHdlList.AddHdl(std::move(pH)); + } +} + +void SdrObject::AddToPlusHdlList(SdrHdlList&, SdrHdl&) const +{ +} + +void SdrObject::addCropHandles(SdrHdlList& /*rTarget*/) const +{ + // Default implementation, does nothing. Overloaded in + // SdrGrafObj and SwVirtFlyDrawObj +} + +tools::Rectangle SdrObject::ImpDragCalcRect(const SdrDragStat& rDrag) const +{ + tools::Rectangle aTmpRect(GetSnapRect()); + tools::Rectangle aRect(aTmpRect); + const SdrHdl* pHdl=rDrag.GetHdl(); + SdrHdlKind eHdl=pHdl==nullptr ? SdrHdlKind::Move : pHdl->GetKind(); + bool bEcke=(eHdl==SdrHdlKind::UpperLeft || eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::LowerLeft || eHdl==SdrHdlKind::LowerRight); + bool bOrtho=rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho(); + bool bBigOrtho=bEcke && bOrtho && rDrag.GetView()->IsBigOrtho(); + Point aPos(rDrag.GetNow()); + bool bLft=(eHdl==SdrHdlKind::UpperLeft || eHdl==SdrHdlKind::Left || eHdl==SdrHdlKind::LowerLeft); + bool bRgt=(eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::Right || eHdl==SdrHdlKind::LowerRight); + bool bTop=(eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::Upper || eHdl==SdrHdlKind::UpperLeft); + bool bBtm=(eHdl==SdrHdlKind::LowerRight || eHdl==SdrHdlKind::Lower || eHdl==SdrHdlKind::LowerLeft); + if (bLft) aTmpRect.SetLeft(aPos.X() ); + if (bRgt) aTmpRect.SetRight(aPos.X() ); + if (bTop) aTmpRect.SetTop(aPos.Y() ); + if (bBtm) aTmpRect.SetBottom(aPos.Y() ); + if (bOrtho) { // Ortho + tools::Long nWdt0=aRect.Right() -aRect.Left(); + tools::Long nHgt0=aRect.Bottom()-aRect.Top(); + tools::Long nXMul=aTmpRect.Right() -aTmpRect.Left(); + tools::Long nYMul=aTmpRect.Bottom()-aTmpRect.Top(); + tools::Long nXDiv=nWdt0; + tools::Long nYDiv=nHgt0; + bool bXNeg=(nXMul<0)!=(nXDiv<0); + bool bYNeg=(nYMul<0)!=(nYDiv<0); + nXMul=std::abs(nXMul); + nYMul=std::abs(nYMul); + nXDiv=std::abs(nXDiv); + nYDiv=std::abs(nYDiv); + Fraction aXFact(nXMul,nXDiv); // fractions for canceling + Fraction aYFact(nYMul,nYDiv); // and for comparing + nXMul=aXFact.GetNumerator(); + nYMul=aYFact.GetNumerator(); + nXDiv=aXFact.GetDenominator(); + nYDiv=aYFact.GetDenominator(); + if (bEcke) { // corner point handles + bool bUseX=(aXFact<aYFact) != bBigOrtho; + if (bUseX) { + tools::Long nNeed=tools::Long(BigInt(nHgt0)*BigInt(nXMul)/BigInt(nXDiv)); + if (bYNeg) nNeed=-nNeed; + if (bTop) aTmpRect.SetTop(aTmpRect.Bottom()-nNeed ); + if (bBtm) aTmpRect.SetBottom(aTmpRect.Top()+nNeed ); + } else { + tools::Long nNeed=tools::Long(BigInt(nWdt0)*BigInt(nYMul)/BigInt(nYDiv)); + if (bXNeg) nNeed=-nNeed; + if (bLft) aTmpRect.SetLeft(aTmpRect.Right()-nNeed ); + if (bRgt) aTmpRect.SetRight(aTmpRect.Left()+nNeed ); + } + } else { // apex handles + if ((bLft || bRgt) && nXDiv!=0) { + tools::Long nHgt0b=aRect.Bottom()-aRect.Top(); + tools::Long nNeed=tools::Long(BigInt(nHgt0b)*BigInt(nXMul)/BigInt(nXDiv)); + aTmpRect.AdjustTop( -((nNeed-nHgt0b)/2) ); + aTmpRect.SetBottom(aTmpRect.Top()+nNeed ); + } + if ((bTop || bBtm) && nYDiv!=0) { + tools::Long nWdt0b=aRect.Right()-aRect.Left(); + tools::Long nNeed=tools::Long(BigInt(nWdt0b)*BigInt(nYMul)/BigInt(nYDiv)); + aTmpRect.AdjustLeft( -((nNeed-nWdt0b)/2) ); + aTmpRect.SetRight(aTmpRect.Left()+nNeed ); + } + } + } + aTmpRect.Normalize(); + return aTmpRect; +} + + +bool SdrObject::hasSpecialDrag() const +{ + return false; +} + +bool SdrObject::supportsFullDrag() const +{ + return true; +} + +rtl::Reference<SdrObject> SdrObject::getFullDragClone() const +{ + // default uses simple clone + return CloneSdrObject(getSdrModelFromSdrObject()); +} + +bool SdrObject::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + + SdrHdlKind eHdl = (pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind(); + + return eHdl==SdrHdlKind::UpperLeft || eHdl==SdrHdlKind::Upper || eHdl==SdrHdlKind::UpperRight || + eHdl==SdrHdlKind::Left || eHdl==SdrHdlKind::Right || eHdl==SdrHdlKind::LowerLeft || + eHdl==SdrHdlKind::Lower || eHdl==SdrHdlKind::LowerRight; +} + +bool SdrObject::applySpecialDrag(SdrDragStat& rDrag) +{ + tools::Rectangle aNewRect(ImpDragCalcRect(rDrag)); + + if(aNewRect != GetSnapRect()) + { + NbcSetSnapRect(aNewRect); + } + + return true; +} + +OUString SdrObject::getSpecialDragComment(const SdrDragStat& /*rDrag*/) const +{ + return OUString(); +} + +basegfx::B2DPolyPolygon SdrObject::getSpecialDragPoly(const SdrDragStat& /*rDrag*/) const +{ + // default has nothing to add + return basegfx::B2DPolyPolygon(); +} + + +// Create +bool SdrObject::BegCreate(SdrDragStat& rStat) +{ + rStat.SetOrtho4Possible(); + tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow()); + aRect1.Normalize(); + rStat.SetActionRect(aRect1); + setOutRectangle(aRect1); + return true; +} + +bool SdrObject::MovCreate(SdrDragStat& rStat) +{ + tools::Rectangle aRectangle; + rStat.TakeCreateRect(aRectangle); + rStat.SetActionRect(aRectangle); + aRectangle.Normalize(); + setOutRectangle(aRectangle); + return true; +} + +bool SdrObject::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + tools::Rectangle aRectangle; + rStat.TakeCreateRect(aRectangle); + aRectangle.Normalize(); + setOutRectangle(aRectangle); + + return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2); +} + +void SdrObject::BrkCreate(SdrDragStat& /*rStat*/) +{ +} + +bool SdrObject::BckCreate(SdrDragStat& /*rStat*/) +{ + return false; +} + +basegfx::B2DPolyPolygon SdrObject::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + tools::Rectangle aRect1; + rDrag.TakeCreateRect(aRect1); + aRect1.Normalize(); + + basegfx::B2DPolyPolygon aRetval; + aRetval.append(basegfx::utils::createPolygonFromRect(vcl::unotools::b2DRectangleFromRectangle(aRect1))); + return aRetval; +} + +PointerStyle SdrObject::GetCreatePointer() const +{ + return PointerStyle::Cross; +} + +// transformations +void SdrObject::NbcMove(const Size& rSize) +{ + moveOutRectangle(rSize.Width(), rSize.Height()); + SetBoundAndSnapRectsDirty(); +} + +void SdrObject::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0); + bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0); + if (bXMirr || bYMirr) { + Point aRef1(GetSnapRect().Center()); + if (bXMirr) { + Point aRef2(aRef1); + aRef2.AdjustY( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + if (bYMirr) { + Point aRef2(aRef1); + aRef2.AdjustX( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + } + auto aRectangle = getOutRectangle(); + ResizeRect(aRectangle, rRef, xFact, yFact); + setOutRectangle(aRectangle); + + SetBoundAndSnapRectsDirty(); +} + +void SdrObject::NbcRotate(const Point& rRef, Degree100 nAngle) +{ + if (nAngle) + { + double a = toRadians(nAngle); + NbcRotate( rRef, nAngle, sin( a ), cos( a ) ); + } +} + +namespace +{ +tools::Rectangle lclMirrorRectangle(tools::Rectangle const& rRectangle, Point const& rRef1, Point const& rRef2) +{ + tools::Rectangle aRectangle(rRectangle); + aRectangle.Move(-rRef1.X(),-rRef1.Y()); + tools::Rectangle R(aRectangle); + tools::Long dx=rRef2.X()-rRef1.X(); + tools::Long dy=rRef2.Y()-rRef1.Y(); + if (dx==0) { // vertical axis + aRectangle.SetLeft(-R.Right() ); + aRectangle.SetRight(-R.Left() ); + } else if (dy==0) { // horizontal axis + aRectangle.SetTop(-R.Bottom() ); + aRectangle.SetBottom(-R.Top() ); + } else if (dx==dy) { // 45deg axis + aRectangle.SetLeft(R.Top() ); + aRectangle.SetRight(R.Bottom() ); + aRectangle.SetTop(R.Left() ); + aRectangle.SetBottom(R.Right() ); + } else if (dx==-dy) { // 45deg axis + aRectangle.SetLeft(-R.Bottom() ); + aRectangle.SetRight(-R.Top() ); + aRectangle.SetTop(-R.Right() ); + aRectangle.SetBottom(-R.Left() ); + } + aRectangle.Move(rRef1.X(),rRef1.Y()); + aRectangle.Normalize(); // just in case + return aRectangle; +} + +} // end anonymous namespace + +void SdrObject::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SetGlueReallyAbsolute(true); + + tools::Rectangle aRectangle = getOutRectangle(); + aRectangle = lclMirrorRectangle(aRectangle, rRef1, rRef2); + setOutRectangle(aRectangle); + + SetBoundAndSnapRectsDirty(); + NbcMirrorGluePoints(rRef1,rRef2); + SetGlueReallyAbsolute(false); +} + +void SdrObject::NbcShear(const Point& rRef, Degree100 /*nAngle*/, double tn, bool bVShear) +{ + SetGlueReallyAbsolute(true); + NbcShearGluePoints(rRef,tn,bVShear); + SetGlueReallyAbsolute(false); +} + +void SdrObject::Move(const Size& rSiz) +{ + if (rSiz.Width()!=0 || rSiz.Height()!=0) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcMove(rSiz); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} + +void SdrObject::NbcCrop(const basegfx::B2DPoint& /*aRef*/, double /*fxFact*/, double /*fyFact*/) +{ + // Default: does nothing. Real behaviour in SwVirtFlyDrawObj and SdrDragCrop::EndSdrDrag. + // Where SwVirtFlyDrawObj is the only real user of it to do something local +} + +void SdrObject::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bUnsetRelative) +{ + if (xFact.GetNumerator() == xFact.GetDenominator() && yFact.GetNumerator() == yFact.GetDenominator()) + return; + + if (bUnsetRelative) + { + mpImpl->mnRelativeWidth.reset(); + mpImpl->meRelativeWidthRelation = text::RelOrientation::PAGE_FRAME; + mpImpl->meRelativeHeightRelation = text::RelOrientation::PAGE_FRAME; + mpImpl->mnRelativeHeight.reset(); + } + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcResize(rRef,xFact,yFact); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::Crop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcCrop(rRef, fxFact, fyFact); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::Rotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + if (nAngle) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcRotate(rRef,nAngle,sn,cs); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrObject::Mirror(const Point& rRef1, const Point& rRef2) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcMirror(rRef1,rRef2); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::Shear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + if (nAngle) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcShear(rRef,nAngle,tn,bVShear); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrObject::NbcSetRelativePos(const Point& rPnt) +{ + Point aRelPos0(GetSnapRect().TopLeft()-m_aAnchor); + Size aSiz(rPnt.X()-aRelPos0.X(),rPnt.Y()-aRelPos0.Y()); + NbcMove(aSiz); // This also calls SetRectsDirty() +} + +void SdrObject::SetRelativePos(const Point& rPnt) +{ + if (rPnt!=GetRelativePos()) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetRelativePos(rPnt); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} + +Point SdrObject::GetRelativePos() const +{ + return GetSnapRect().TopLeft()-m_aAnchor; +} + +void SdrObject::ImpSetAnchorPos(const Point& rPnt) +{ + m_aAnchor = rPnt; +} + +void SdrObject::NbcSetAnchorPos(const Point& rPnt) +{ + Size aSiz(rPnt.X()-m_aAnchor.X(),rPnt.Y()-m_aAnchor.Y()); + m_aAnchor=rPnt; + NbcMove(aSiz); // This also calls SetRectsDirty() +} + +void SdrObject::SetAnchorPos(const Point& rPnt) +{ + if (rPnt!=m_aAnchor) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetAnchorPos(rPnt); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} + +const Point& SdrObject::GetAnchorPos() const +{ + return m_aAnchor; +} + +void SdrObject::RecalcSnapRect() +{ +} + +const tools::Rectangle& SdrObject::GetSnapRect() const +{ + return getOutRectangle(); +} + +void SdrObject::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + setOutRectangle(rRect); +} + +const tools::Rectangle& SdrObject::GetLogicRect() const +{ + return GetSnapRect(); +} + +void SdrObject::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + NbcSetSnapRect(rRect); +} + +void SdrObject::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool /* bShrinkOnly = false */ ) +{ + SetLogicRect( rMaxRect ); +} + +void SdrObject::SetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetSnapRect(rRect); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::SetLogicRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetLogicRect(rRect); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +Degree100 SdrObject::GetRotateAngle() const +{ + return 0_deg100; +} + +Degree100 SdrObject::GetShearAngle(bool /*bVertical*/) const +{ + return 0_deg100; +} + +sal_uInt32 SdrObject::GetSnapPointCount() const +{ + return GetPointCount(); +} + +Point SdrObject::GetSnapPoint(sal_uInt32 i) const +{ + return GetPoint(i); +} + +bool SdrObject::IsPolyObj() const +{ + return false; +} + +sal_uInt32 SdrObject::GetPointCount() const +{ + return 0; +} + +Point SdrObject::GetPoint(sal_uInt32 /*i*/) const +{ + return Point(); +} + +void SdrObject::SetPoint(const Point& rPnt, sal_uInt32 i) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetPoint(rPnt, i); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::NbcSetPoint(const Point& /*rPnt*/, sal_uInt32 /*i*/) +{ +} + +bool SdrObject::HasTextEdit() const +{ + return false; +} + +bool SdrObject::Equals(const SdrObject& rOtherObj) const +{ + return (m_aAnchor.X() == rOtherObj.m_aAnchor.X() && m_aAnchor.Y() == rOtherObj.m_aAnchor.Y() && + m_nOrdNum == rOtherObj.m_nOrdNum && mnNavigationPosition == rOtherObj.mnNavigationPosition && + mbSupportTextIndentingOnLineWidthChange == rOtherObj.mbSupportTextIndentingOnLineWidthChange && + mbLineIsOutsideGeometry == rOtherObj.mbLineIsOutsideGeometry && m_bMarkProt == rOtherObj.m_bMarkProt && + m_bIs3DObj == rOtherObj.m_bIs3DObj && m_bIsEdge == rOtherObj.m_bIsEdge && m_bClosedObj == rOtherObj.m_bClosedObj && + m_bNotVisibleAsMaster == rOtherObj.m_bNotVisibleAsMaster && m_bEmptyPresObj == rOtherObj.m_bEmptyPresObj && + mbVisible == rOtherObj.mbVisible && m_bNoPrint == rOtherObj.m_bNoPrint && m_bSizProt == rOtherObj.m_bSizProt && + m_bMovProt == rOtherObj.m_bMovProt && m_bVirtObj == rOtherObj.m_bVirtObj && + mnLayerID == rOtherObj.mnLayerID && GetMergedItemSet().Equals(rOtherObj.GetMergedItemSet(), false) ); +} + +void SdrObject::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrObject")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*this).name())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("name"), "%s", BAD_CAST(GetName().toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("title"), "%s", BAD_CAST(GetTitle().toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("description"), "%s", BAD_CAST(GetDescription().toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("nOrdNum"), "%" SAL_PRIuUINT32, GetOrdNumDirect()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aOutRect"), BAD_CAST(getOutRectangle().toString().getStr())); + + if (m_pGrabBagItem) + { + m_pGrabBagItem->dumpAsXml(pWriter); + } + + if (mpProperties) + { + mpProperties->dumpAsXml(pWriter); + } + + if (const OutlinerParaObject* pOutliner = GetOutlinerParaObject()) + pOutliner->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +void SdrObject::SetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetOutlinerParaObject(std::move(pTextObject)); + SetChanged(); + BroadcastObjectChange(); + if (GetCurrentBoundRect()!=aBoundRect0) { + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } + + if (!getSdrModelFromSdrObject().IsUndoEnabled()) + return; + + // Don't do this during import. + SdrObject* pTopGroupObj = nullptr; + if (getParentSdrObjectFromSdrObject()) + { + pTopGroupObj = getParentSdrObjectFromSdrObject(); + while (pTopGroupObj->getParentSdrObjectFromSdrObject()) + { + pTopGroupObj = pTopGroupObj->getParentSdrObjectFromSdrObject(); + } + } + if (pTopGroupObj) + { + // A shape was modified, which is in a group shape. Empty the group shape's grab-bag, + // which potentially contains the old text of the shapes in case of diagrams. + pTopGroupObj->SetGrabBagItem(uno::Any(uno::Sequence<beans::PropertyValue>())); + } +} + +void SdrObject::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> /*pTextObject*/) +{ +} + +OutlinerParaObject* SdrObject::GetOutlinerParaObject() const +{ + return nullptr; +} + +void SdrObject::NbcReformatText() +{ +} + +void SdrObject::BurnInStyleSheetAttributes() +{ + GetProperties().ForceStyleToHardAttributes(); +} + +bool SdrObject::HasMacro() const +{ + return false; +} + +SdrObject* SdrObject::CheckMacroHit(const SdrObjMacroHitRec& rRec) const +{ + if(rRec.pPageView) + { + return SdrObjectPrimitiveHit(*this, rRec.aPos, {static_cast<double>(rRec.nTol), static_cast<double>(rRec.nTol)}, *rRec.pPageView, rRec.pVisiLayer, false); + } + + return nullptr; +} + +PointerStyle SdrObject::GetMacroPointer(const SdrObjMacroHitRec&) const +{ + return PointerStyle::RefHand; +} + +void SdrObject::PaintMacro(OutputDevice& rOut, const tools::Rectangle& , const SdrObjMacroHitRec& ) const +{ + const RasterOp eRop(rOut.GetRasterOp()); + const basegfx::B2DPolyPolygon aPolyPolygon(TakeXorPoly()); + + rOut.SetLineColor(COL_BLACK); + rOut.SetFillColor(); + rOut.SetRasterOp(RasterOp::Invert); + + for(auto const& rPolygon : aPolyPolygon) + { + rOut.DrawPolyLine(rPolygon); + } + + rOut.SetRasterOp(eRop); +} + +bool SdrObject::DoMacro(const SdrObjMacroHitRec&) +{ + return false; +} + +bool SdrObject::IsMacroHit(const SdrObjMacroHitRec& rRec) const +{ + return CheckMacroHit(rRec) != nullptr; +} + + +std::unique_ptr<SdrObjGeoData> SdrObject::NewGeoData() const +{ + return std::make_unique<SdrObjGeoData>(); +} + +void SdrObject::SaveGeoData(SdrObjGeoData& rGeo) const +{ + rGeo.aBoundRect =GetCurrentBoundRect(); + rGeo.aAnchor =m_aAnchor ; + rGeo.bMovProt =m_bMovProt ; + rGeo.bSizProt =m_bSizProt ; + rGeo.bNoPrint =m_bNoPrint ; + rGeo.mbVisible =mbVisible ; + rGeo.bClosedObj =m_bClosedObj ; + rGeo.mnLayerID = mnLayerID; + + // user-defined gluepoints + if (m_pPlusData!=nullptr && m_pPlusData->pGluePoints!=nullptr) { + if (rGeo.pGPL!=nullptr) { + *rGeo.pGPL=*m_pPlusData->pGluePoints; + } else { + rGeo.pGPL.reset( new SdrGluePointList(*m_pPlusData->pGluePoints) ); + } + } else { + rGeo.pGPL.reset(); + } +} + +void SdrObject::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SetBoundAndSnapRectsDirty(); + setOutRectangle(rGeo.aBoundRect); + m_aAnchor =rGeo.aAnchor ; + m_bMovProt =rGeo.bMovProt ; + m_bSizProt =rGeo.bSizProt ; + m_bNoPrint =rGeo.bNoPrint ; + mbVisible =rGeo.mbVisible ; + m_bClosedObj =rGeo.bClosedObj ; + mnLayerID = rGeo.mnLayerID; + + // user-defined gluepoints + if (rGeo.pGPL!=nullptr) { + ImpForcePlusData(); + if (m_pPlusData->pGluePoints!=nullptr) { + *m_pPlusData->pGluePoints=*rGeo.pGPL; + } else { + m_pPlusData->pGluePoints.reset(new SdrGluePointList(*rGeo.pGPL)); + } + } else { + if (m_pPlusData!=nullptr && m_pPlusData->pGluePoints!=nullptr) { + m_pPlusData->pGluePoints.reset(); + } + } +} + +std::unique_ptr<SdrObjGeoData> SdrObject::GetGeoData() const +{ + std::unique_ptr<SdrObjGeoData> pGeo = NewGeoData(); + SaveGeoData(*pGeo); + return pGeo; +} + +void SdrObject::SetGeoData(const SdrObjGeoData& rGeo) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + RestoreGeoData(rGeo); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +// ItemSet access + +const SfxItemSet& SdrObject::GetObjectItemSet() const +{ + return GetProperties().GetObjectItemSet(); +} + +const SfxItemSet& SdrObject::GetMergedItemSet() const +{ + return GetProperties().GetMergedItemSet(); +} + +void SdrObject::SetObjectItem(const SfxPoolItem& rItem) +{ + GetProperties().SetObjectItem(rItem); +} + +void SdrObject::SetMergedItem(const SfxPoolItem& rItem) +{ + GetProperties().SetMergedItem(rItem); +} + +void SdrObject::ClearMergedItem(const sal_uInt16 nWhich) +{ + GetProperties().ClearMergedItem(nWhich); +} + +void SdrObject::SetObjectItemSet(const SfxItemSet& rSet) +{ + GetProperties().SetObjectItemSet(rSet); +} + +void SdrObject::SetMergedItemSet(const SfxItemSet& rSet, bool bClearAllItems) +{ + GetProperties().SetMergedItemSet(rSet, bClearAllItems); +} + +const SfxPoolItem& SdrObject::GetObjectItem(const sal_uInt16 nWhich) const +{ + return GetObjectItemSet().Get(nWhich); +} + +const SfxPoolItem& SdrObject::GetMergedItem(const sal_uInt16 nWhich) const +{ + return GetMergedItemSet().Get(nWhich); +} + +void SdrObject::SetMergedItemSetAndBroadcast(const SfxItemSet& rSet, bool bClearAllItems) +{ + GetProperties().SetMergedItemSetAndBroadcast(rSet, bClearAllItems); +} + +void SdrObject::ApplyNotPersistAttr(const SfxItemSet& rAttr) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcApplyNotPersistAttr(rAttr); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrObject::NbcApplyNotPersistAttr(const SfxItemSet& rAttr) +{ + const tools::Rectangle& rSnap=GetSnapRect(); + const tools::Rectangle& rLogic=GetLogicRect(); + Point aRef1(rSnap.Center()); + + if (const SdrTransformRef1XItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1X)) + { + aRef1.setX(pPoolItem->GetValue() ); + } + if (const SdrTransformRef1YItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_TRANSFORMREF1Y)) + { + aRef1.setY(pPoolItem->GetValue() ); + } + + tools::Rectangle aNewSnap(rSnap); + if (const SdrMoveXItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_MOVEX)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.Move(n,0); + } + if (const SdrMoveYItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_MOVEY)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.Move(0,n); + } + if (const SdrOnePositionXItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ONEPOSITIONX)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.Move(n-aNewSnap.Left(),0); + } + if (const SdrOnePositionYItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ONEPOSITIONY)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.Move(0,n-aNewSnap.Top()); + } + if (const SdrOneSizeWidthItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ONESIZEWIDTH)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.SetRight(aNewSnap.Left()+n ); + } + if (const SdrOneSizeHeightItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ONESIZEHEIGHT)) + { + tools::Long n = pPoolItem->GetValue(); + aNewSnap.SetBottom(aNewSnap.Top()+n ); + } + if (aNewSnap!=rSnap) { + if (aNewSnap.GetSize()==rSnap.GetSize()) { + NbcMove(Size(aNewSnap.Left()-rSnap.Left(),aNewSnap.Top()-rSnap.Top())); + } else { + NbcSetSnapRect(aNewSnap); + } + } + + if (const SdrShearAngleItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_SHEARANGLE)) + { + Degree100 n = pPoolItem->GetValue(); + n-=GetShearAngle(); + if (n) { + double nTan = tan(toRadians(n)); + NbcShear(aRef1,n,nTan,false); + } + } + if (const SdrAngleItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ROTATEANGLE)) + { + Degree100 n = pPoolItem->GetValue(); + n-=GetRotateAngle(); + if (n) { + NbcRotate(aRef1,n); + } + } + if (const SdrRotateOneItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_ROTATEONE)) + { + Degree100 n = pPoolItem->GetValue(); + NbcRotate(aRef1,n); + } + if (const SdrHorzShearOneItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_HORZSHEARONE)) + { + Degree100 n = pPoolItem->GetValue(); + double nTan = tan(toRadians(n)); + NbcShear(aRef1,n,nTan,false); + } + if (const SdrVertShearOneItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_VERTSHEARONE)) + { + Degree100 n = pPoolItem->GetValue(); + double nTan = tan(toRadians(n)); + NbcShear(aRef1,n,nTan,true); + } + + if (const SdrYesNoItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_OBJMOVEPROTECT)) + { + bool b = pPoolItem->GetValue(); + SetMoveProtect(b); + } + if (const SdrYesNoItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_OBJSIZEPROTECT)) + { + bool b = pPoolItem->GetValue(); + SetResizeProtect(b); + } + + /* move protect always sets size protect */ + if( IsMoveProtect() ) + SetResizeProtect( true ); + + if (const SdrObjPrintableItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_OBJPRINTABLE)) + { + bool b = pPoolItem->GetValue(); + SetPrintable(b); + } + + if (const SdrObjVisibleItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_OBJVISIBLE)) + { + bool b = pPoolItem->GetValue(); + SetVisible(b); + } + + SdrLayerID nLayer=SDRLAYER_NOTFOUND; + if (const SdrLayerIdItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LAYERID)) + { + nLayer = pPoolItem->GetValue(); + } + if (const SdrLayerNameItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LAYERNAME)) + { + OUString aLayerName = pPoolItem->GetValue(); + const SdrLayerAdmin& rLayAd(nullptr != getSdrPageFromSdrObject() + ? getSdrPageFromSdrObject()->GetLayerAdmin() + : getSdrModelFromSdrObject().GetLayerAdmin()); + const SdrLayer* pLayer = rLayAd.GetLayer(aLayerName); + + if(nullptr != pLayer) + { + nLayer=pLayer->GetID(); + } + } + if (nLayer!=SDRLAYER_NOTFOUND) { + NbcSetLayer(nLayer); + } + + if (const SfxStringItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_OBJECTNAME)) + { + OUString aName = pPoolItem->GetValue(); + SetName(aName); + } + tools::Rectangle aNewLogic(rLogic); + if (const SdrLogicSizeWidthItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LOGICSIZEWIDTH)) + { + tools::Long n = pPoolItem->GetValue(); + aNewLogic.SetRight(aNewLogic.Left()+n ); + } + if (const SdrLogicSizeHeightItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LOGICSIZEHEIGHT)) + { + tools::Long n = pPoolItem->GetValue(); + aNewLogic.SetBottom(aNewLogic.Top()+n ); + } + if (aNewLogic!=rLogic) { + NbcSetLogicRect(aNewLogic); + } + Fraction aResizeX(1,1); + Fraction aResizeY(1,1); + if (const SdrResizeXOneItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEXONE)) + { + aResizeX *= pPoolItem->GetValue(); + } + if (const SdrResizeYOneItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_RESIZEYONE)) + { + aResizeY *= pPoolItem->GetValue(); + } + if (aResizeX!=Fraction(1,1) || aResizeY!=Fraction(1,1)) { + NbcResize(aRef1,aResizeX,aResizeY); + } +} + +void SdrObject::TakeNotPersistAttr(SfxItemSet& rAttr) const +{ + const tools::Rectangle& rSnap=GetSnapRect(); + const tools::Rectangle& rLogic=GetLogicRect(); + rAttr.Put(SdrYesNoItem(SDRATTR_OBJMOVEPROTECT, IsMoveProtect())); + rAttr.Put(SdrYesNoItem(SDRATTR_OBJSIZEPROTECT, IsResizeProtect())); + rAttr.Put(SdrObjPrintableItem(IsPrintable())); + rAttr.Put(SdrObjVisibleItem(IsVisible())); + rAttr.Put(SdrAngleItem(SDRATTR_ROTATEANGLE, GetRotateAngle())); + rAttr.Put(SdrShearAngleItem(GetShearAngle())); + rAttr.Put(SdrOneSizeWidthItem(rSnap.GetWidth()-1)); + rAttr.Put(SdrOneSizeHeightItem(rSnap.GetHeight()-1)); + rAttr.Put(SdrOnePositionXItem(rSnap.Left())); + rAttr.Put(SdrOnePositionYItem(rSnap.Top())); + if (rLogic.GetWidth()!=rSnap.GetWidth()) { + rAttr.Put(SdrLogicSizeWidthItem(rLogic.GetWidth()-1)); + } + if (rLogic.GetHeight()!=rSnap.GetHeight()) { + rAttr.Put(SdrLogicSizeHeightItem(rLogic.GetHeight()-1)); + } + OUString aName(GetName()); + + if (!aName.isEmpty()) + { + rAttr.Put(SfxStringItem(SDRATTR_OBJECTNAME, aName)); + } + + rAttr.Put(SdrLayerIdItem(GetLayer())); + const SdrLayerAdmin& rLayAd(nullptr != getSdrPageFromSdrObject() + ? getSdrPageFromSdrObject()->GetLayerAdmin() + : getSdrModelFromSdrObject().GetLayerAdmin()); + const SdrLayer* pLayer = rLayAd.GetLayerPerID(GetLayer()); + if(nullptr != pLayer) + { + rAttr.Put(SdrLayerNameItem(pLayer->GetName())); + } + Point aRef1(rSnap.Center()); + Point aRef2(aRef1); aRef2.AdjustY( 1 ); + rAttr.Put(SdrTransformRef1XItem(aRef1.X())); + rAttr.Put(SdrTransformRef1YItem(aRef1.Y())); + rAttr.Put(SdrTransformRef2XItem(aRef2.X())); + rAttr.Put(SdrTransformRef2YItem(aRef2.Y())); +} + +SfxStyleSheet* SdrObject::GetStyleSheet() const +{ + return GetProperties().GetStyleSheet(); +} + +void SdrObject::SetStyleSheet(SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr) +{ + tools::Rectangle aBoundRect0; + + if(m_pUserCall) + aBoundRect0 = GetLastBoundRect(); + + InternalSetStyleSheet(pNewStyleSheet, bDontRemoveHardAttr, true); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::ChangeAttr, aBoundRect0); +} + +void SdrObject::NbcSetStyleSheet(SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr) +{ + InternalSetStyleSheet(pNewStyleSheet, bDontRemoveHardAttr, false); +} + +void SdrObject::InternalSetStyleSheet(SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr, bool bBroadcast) +{ + GetProperties().SetStyleSheet(pNewStyleSheet, bDontRemoveHardAttr, bBroadcast); +} + +// Broadcasting while setting attributes is managed by the AttrObj. + + +SdrGluePoint SdrObject::GetVertexGluePoint(sal_uInt16 nPosNum) const +{ + // #i41936# Use SnapRect for default GluePoints + const tools::Rectangle aR(GetSnapRect()); + Point aPt; + + switch(nPosNum) + { + case 0 : aPt = aR.TopCenter(); break; + case 1 : aPt = aR.RightCenter(); break; + case 2 : aPt = aR.BottomCenter(); break; + case 3 : aPt = aR.LeftCenter(); break; + } + + aPt -= aR.Center(); + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + + return aGP; +} + +SdrGluePoint SdrObject::GetCornerGluePoint(sal_uInt16 nPosNum) const +{ + tools::Rectangle aR(GetCurrentBoundRect()); + Point aPt; + switch (nPosNum) { + case 0 : aPt=aR.TopLeft(); break; + case 1 : aPt=aR.TopRight(); break; + case 2 : aPt=aR.BottomRight(); break; + case 3 : aPt=aR.BottomLeft(); break; + } + aPt-=GetSnapRect().Center(); + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + return aGP; +} + +const SdrGluePointList* SdrObject::GetGluePointList() const +{ + if (m_pPlusData!=nullptr) return m_pPlusData->pGluePoints.get(); + return nullptr; +} + + +SdrGluePointList* SdrObject::ForceGluePointList() +{ + ImpForcePlusData(); + if (m_pPlusData->pGluePoints==nullptr) { + m_pPlusData->pGluePoints.reset(new SdrGluePointList); + } + return m_pPlusData->pGluePoints.get(); +} + +void SdrObject::SetGlueReallyAbsolute(bool bOn) +{ + // First a const call to see whether there are any gluepoints. + // Force const call! + if (GetGluePointList()!=nullptr) { + SdrGluePointList* pGPL=ForceGluePointList(); + pGPL->SetReallyAbsolute(bOn,*this); + } +} + +void SdrObject::NbcRotateGluePoints(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + // First a const call to see whether there are any gluepoints. + // Force const call! + if (GetGluePointList()!=nullptr) { + SdrGluePointList* pGPL=ForceGluePointList(); + pGPL->Rotate(rRef,nAngle,sn,cs,this); + } +} + +void SdrObject::NbcMirrorGluePoints(const Point& rRef1, const Point& rRef2) +{ + // First a const call to see whether there are any gluepoints. + // Force const call! + if (GetGluePointList()!=nullptr) { + SdrGluePointList* pGPL=ForceGluePointList(); + pGPL->Mirror(rRef1,rRef2,this); + } +} + +void SdrObject::NbcShearGluePoints(const Point& rRef, double tn, bool bVShear) +{ + // First a const call to see whether there are any gluepoints. + // Force const call! + if (GetGluePointList()!=nullptr) { + SdrGluePointList* pGPL=ForceGluePointList(); + pGPL->Shear(rRef,tn,bVShear,this); + } +} + +void SdrObject::ConnectToNode(bool /*bTail1*/, SdrObject* /*pObj*/) +{ +} + +void SdrObject::DisconnectFromNode(bool /*bTail1*/) +{ +} + +SdrObject* SdrObject::GetConnectedNode(bool /*bTail1*/) const +{ + return nullptr; +} + + +static void extractLineContourFromPrimitive2DSequence( + const drawinglayer::primitive2d::Primitive2DContainer& rxSequence, + basegfx::B2DPolygonVector& rExtractedHairlines, + basegfx::B2DPolyPolygonVector& rExtractedLineFills) +{ + rExtractedHairlines.clear(); + rExtractedLineFills.clear(); + + if(rxSequence.empty()) + return; + + // use neutral ViewInformation + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + + // create extractor, process and get result + drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D); + aExtractor.process(rxSequence); + + // copy line results + rExtractedHairlines = aExtractor.getExtractedHairlines(); + + // copy fill rsults + rExtractedLineFills = aExtractor.getExtractedLineFills(); +} + + +rtl::Reference<SdrObject> SdrObject::ImpConvertToContourObj(bool bForceLineDash) +{ + rtl::Reference<SdrObject> pRetval; + + if(LineGeometryUsageIsNecessary()) + { + basegfx::B2DPolyPolygon aMergedLineFillPolyPolygon; + basegfx::B2DPolyPolygon aMergedHairlinePolyPolygon; + drawinglayer::primitive2d::Primitive2DContainer xSequence; + GetViewContact().getViewIndependentPrimitive2DContainer(xSequence); + + if(!xSequence.empty()) + { + basegfx::B2DPolygonVector aExtractedHairlines; + basegfx::B2DPolyPolygonVector aExtractedLineFills; + + extractLineContourFromPrimitive2DSequence(xSequence, aExtractedHairlines, aExtractedLineFills); + + // for SdrObject creation, just copy all to a single Hairline-PolyPolygon + for(const basegfx::B2DPolygon & rExtractedHairline : aExtractedHairlines) + { + aMergedHairlinePolyPolygon.append(rExtractedHairline); + } + + // check for fill rsults + if (!aExtractedLineFills.empty() && !utl::ConfigManager::IsFuzzing()) + { + // merge to a single tools::PolyPolygon (OR) + aMergedLineFillPolyPolygon = basegfx::utils::mergeToSinglePolyPolygon(std::move(aExtractedLineFills)); + } + } + + if(aMergedLineFillPolyPolygon.count() || (bForceLineDash && aMergedHairlinePolyPolygon.count())) + { + SfxItemSet aSet(GetMergedItemSet()); + drawing::FillStyle eOldFillStyle = aSet.Get(XATTR_FILLSTYLE).GetValue(); + rtl::Reference<SdrPathObj> aLinePolygonPart; + rtl::Reference<SdrPathObj> aLineHairlinePart; + bool bBuildGroup(false); + + if(aMergedLineFillPolyPolygon.count()) + { + // create SdrObject for filled line geometry + aLinePolygonPart = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathFill, + std::move(aMergedLineFillPolyPolygon)); + + // correct item properties + aSet.Put(XLineWidthItem(0)); + aSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); + Color aColorLine = aSet.Get(XATTR_LINECOLOR).GetColorValue(); + sal_uInt16 nTransLine = aSet.Get(XATTR_LINETRANSPARENCE).GetValue(); + aSet.Put(XFillColorItem(OUString(), aColorLine)); + aSet.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + aSet.Put(XFillTransparenceItem(nTransLine)); + + aLinePolygonPart->SetMergedItemSet(aSet); + } + + if(aMergedHairlinePolyPolygon.count()) + { + // create SdrObject for hairline geometry + // OBJ_PATHLINE is necessary here, not OBJ_PATHFILL. This is intended + // to get a non-filled object. If the poly is closed, the PathObj takes care for + // the correct closed state. + aLineHairlinePart = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + std::move(aMergedHairlinePolyPolygon)); + + aSet.Put(XLineWidthItem(0)); + aSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + aSet.Put(XLineStyleItem(drawing::LineStyle_SOLID)); + + // it is also necessary to switch off line start and ends here + aSet.Put(XLineStartWidthItem(0)); + aSet.Put(XLineEndWidthItem(0)); + + aLineHairlinePart->SetMergedItemSet(aSet); + + if(aLinePolygonPart) + { + bBuildGroup = true; + } + } + + // check if original geometry should be added (e.g. filled and closed) + bool bAddOriginalGeometry(false); + SdrPathObj* pPath = dynamic_cast<SdrPathObj*>(this); + + if(pPath && pPath->IsClosed()) + { + if(eOldFillStyle != drawing::FillStyle_NONE) + { + bAddOriginalGeometry = true; + } + } + + // do we need a group? + if(bBuildGroup || bAddOriginalGeometry) + { + rtl::Reference<SdrObject> pGroup = new SdrObjGroup(getSdrModelFromSdrObject()); + + if(bAddOriginalGeometry) + { + // Add a clone of the original geometry. + aSet.ClearItem(); + aSet.Put(GetMergedItemSet()); + aSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); + aSet.Put(XLineWidthItem(0)); + + rtl::Reference<SdrObject> pClone(CloneSdrObject(getSdrModelFromSdrObject())); + pClone->SetMergedItemSet(aSet); + + pGroup->GetSubList()->NbcInsertObject(pClone.get()); + } + + if(aLinePolygonPart) + { + pGroup->GetSubList()->NbcInsertObject(aLinePolygonPart.get()); + } + + if(aLineHairlinePart) + { + pGroup->GetSubList()->NbcInsertObject(aLineHairlinePart.get()); + } + + pRetval = pGroup; + } + else + { + if(aLinePolygonPart) + { + pRetval = aLinePolygonPart; + } + else if(aLineHairlinePart) + { + pRetval = aLineHairlinePart; + } + } + } + } + + if(!pRetval) + { + // due to current method usage, create and return a clone when nothing has changed + pRetval = CloneSdrObject(getSdrModelFromSdrObject()); + } + + return pRetval; +} + + +void SdrObject::SetMarkProtect(bool bProt) +{ + m_bMarkProt = bProt; +} + + +void SdrObject::SetEmptyPresObj(bool bEpt) +{ + m_bEmptyPresObj = bEpt; +} + + +void SdrObject::SetNotVisibleAsMaster(bool bFlg) +{ + m_bNotVisibleAsMaster=bFlg; +} + + +// convert this path object to contour object, even when it is a group +rtl::Reference<SdrObject> SdrObject::ConvertToContourObj(SdrObject* pRet1, bool bForceLineDash) const +{ + rtl::Reference<SdrObject> pRet = pRet1; + if(dynamic_cast<const SdrObjGroup*>( pRet.get()) != nullptr) + { + SdrObjList* pObjList2 = pRet->GetSubList(); + rtl::Reference<SdrObject> pGroup = new SdrObjGroup(getSdrModelFromSdrObject()); + + for (const rtl::Reference<SdrObject>& pIterObj : *pObjList2) + pGroup->GetSubList()->NbcInsertObject(ConvertToContourObj(pIterObj.get(), bForceLineDash).get()); + + pRet = pGroup; + } + else + { + if (SdrPathObj *pPathObj = dynamic_cast<SdrPathObj*>(pRet.get())) + { + // bezier geometry got created, even for straight edges since the given + // object is a result of DoConvertToPolyObj. For conversion to contour + // this is not really needed and can be reduced again AFAP + pPathObj->SetPathPoly(basegfx::utils::simplifyCurveSegments(pPathObj->GetPathPoly())); + } + + pRet = pRet->ImpConvertToContourObj(bForceLineDash); + } + + // #i73441# preserve LayerID + if(pRet && pRet->GetLayer() != GetLayer()) + { + pRet->SetLayer(GetLayer()); + } + + return pRet; +} + + +rtl::Reference<SdrObject> SdrObject::ConvertToPolyObj(bool bBezier, bool bLineToArea) const +{ + rtl::Reference<SdrObject> pRet = DoConvertToPolyObj(bBezier, true); + + if(pRet && bLineToArea) + { + pRet = ConvertToContourObj(pRet.get()); + } + + // #i73441# preserve LayerID + if(pRet && pRet->GetLayer() != GetLayer()) + { + pRet->SetLayer(GetLayer()); + } + + return pRet; +} + + +rtl::Reference<SdrObject> SdrObject::DoConvertToPolyObj(bool /*bBezier*/, bool /*bAddText*/) const +{ + return nullptr; +} + + +void SdrObject::InsertedStateChange() +{ + const bool bIsInserted(nullptr != getParentSdrObjListFromSdrObject()); + const tools::Rectangle aBoundRect0(GetLastBoundRect()); + + if(bIsInserted) + { + SendUserCall(SdrUserCallType::Inserted, aBoundRect0); + } + else + { + SendUserCall(SdrUserCallType::Removed, aBoundRect0); + } + + if(nullptr != m_pPlusData && nullptr != m_pPlusData->pBroadcast) + { + SdrHint aHint(bIsInserted ? SdrHintKind::ObjectInserted : SdrHintKind::ObjectRemoved, *this); + m_pPlusData->pBroadcast->Broadcast(aHint); + } +} + +void SdrObject::SetMoveProtect(bool bProt) +{ + if(IsMoveProtect() != bProt) + { + // #i77187# secured and simplified + m_bMovProt = bProt; + SetChanged(); + BroadcastObjectChange(); + } +} + +void SdrObject::SetResizeProtect(bool bProt) +{ + if(IsResizeProtect() != bProt) + { + // #i77187# secured and simplified + m_bSizProt = bProt; + SetChanged(); + BroadcastObjectChange(); + } +} + +void SdrObject::SetPrintable(bool bPrn) +{ + if( bPrn == m_bNoPrint ) + { + m_bNoPrint=!bPrn; + SetChanged(); + if (IsInserted()) + { + SdrHint aHint(SdrHintKind::ObjectChange, *this); + getSdrModelFromSdrObject().Broadcast(aHint); + } + } +} + +void SdrObject::SetVisible(bool bVisible) +{ + if( bVisible != mbVisible ) + { + mbVisible = bVisible; + SetChanged(); + if (IsInserted()) + { + SdrHint aHint(SdrHintKind::ObjectChange, *this); + getSdrModelFromSdrObject().Broadcast(aHint); + } + } +} + + +sal_uInt16 SdrObject::GetUserDataCount() const +{ + if (m_pPlusData==nullptr || m_pPlusData->pUserDataList==nullptr) return 0; + return m_pPlusData->pUserDataList->GetUserDataCount(); +} + +SdrObjUserData* SdrObject::GetUserData(sal_uInt16 nNum) const +{ + if (m_pPlusData==nullptr || m_pPlusData->pUserDataList==nullptr) return nullptr; + return &m_pPlusData->pUserDataList->GetUserData(nNum); +} + +void SdrObject::AppendUserData(std::unique_ptr<SdrObjUserData> pData) +{ + if (!pData) + { + OSL_FAIL("SdrObject::AppendUserData(): pData is NULL pointer."); + return; + } + + ImpForcePlusData(); + if (!m_pPlusData->pUserDataList) + m_pPlusData->pUserDataList.reset( new SdrObjUserDataList ); + + m_pPlusData->pUserDataList->AppendUserData(std::move(pData)); +} + +void SdrObject::DeleteUserData(sal_uInt16 nNum) +{ + sal_uInt16 nCount=GetUserDataCount(); + if (nNum<nCount) { + m_pPlusData->pUserDataList->DeleteUserData(nNum); + if (nCount==1) { + m_pPlusData->pUserDataList.reset(); + } + } else { + OSL_FAIL("SdrObject::DeleteUserData(): Invalid Index."); + } +} + +void SdrObject::SetUserCall(SdrObjUserCall* pUser) +{ + m_pUserCall = pUser; +} + + +void SdrObject::SendUserCall(SdrUserCallType eUserCall, const tools::Rectangle& rBoundRect) const +{ + SdrObject* pGroup(getParentSdrObjectFromSdrObject()); + + if ( m_pUserCall ) + { + m_pUserCall->Changed( *this, eUserCall, rBoundRect ); + } + + if(nullptr != pGroup && pGroup->GetUserCall()) + { + // broadcast to group + SdrUserCallType eChildUserType = SdrUserCallType::ChildChangeAttr; + + switch( eUserCall ) + { + case SdrUserCallType::MoveOnly: + eChildUserType = SdrUserCallType::ChildMoveOnly; + break; + + case SdrUserCallType::Resize: + eChildUserType = SdrUserCallType::ChildResize; + break; + + case SdrUserCallType::ChangeAttr: + eChildUserType = SdrUserCallType::ChildChangeAttr; + break; + + case SdrUserCallType::Delete: + eChildUserType = SdrUserCallType::ChildDelete; + break; + + case SdrUserCallType::Inserted: + eChildUserType = SdrUserCallType::ChildInserted; + break; + + case SdrUserCallType::Removed: + eChildUserType = SdrUserCallType::ChildRemoved; + break; + + default: break; + } + + pGroup->GetUserCall()->Changed( *this, eChildUserType, rBoundRect ); + } + + // notify our UNO shape listeners + switch ( eUserCall ) + { + case SdrUserCallType::Resize: + notifyShapePropertyChange( "Size" ); + [[fallthrough]]; // RESIZE might also imply a change of the position + case SdrUserCallType::MoveOnly: + notifyShapePropertyChange( "Position" ); + break; + default: + // not interested in + break; + } +} + +void SdrObject::setUnoShape( const uno::Reference< drawing::XShape >& _rxUnoShape ) +{ + const uno::Reference< uno::XInterface>& xOldUnoShape( maWeakUnoShape ); + // the UNO shape would be gutted by the following code; return early + if ( _rxUnoShape == xOldUnoShape ) + { + if ( !xOldUnoShape.is() ) + { + // make sure there is no stale impl. pointer if the UNO + // shape was destroyed meanwhile (remember we only hold weak + // reference to it!) + mpSvxShape = nullptr; + } + return; + } + + if ( xOldUnoShape.is() ) + { + // Remove yourself from the current UNO shape. Its destructor + // will reset our UNO shape otherwise. + mpSvxShape->InvalidateSdrObject(); + } + + maWeakUnoShape = _rxUnoShape; + mpSvxShape = comphelper::getFromUnoTunnel<SvxShape>( _rxUnoShape ); +} + +/** only for internal use! */ +SvxShape* SdrObject::getSvxShape() +{ + DBG_TESTSOLARMUTEX(); + // retrieving the impl pointer and subsequently using it is not thread-safe, of course, so it needs to be + // guarded by the SolarMutex + + uno::Reference< uno::XInterface > xShape( maWeakUnoShape ); + //#113608#, make sure mpSvxShape is always synchronized with maWeakUnoShape + if ( mpSvxShape && !xShape ) + mpSvxShape = nullptr; + + return mpSvxShape; +} + +css::uno::Reference< css::drawing::XShape > SdrObject::getUnoShape() +{ + // try weak reference first + uno::Reference< css::drawing::XShape > xShape = maWeakUnoShape; + if (xShape) + return xShape; + + // try to access SdrPage from this SdrObject. This will only exist if the SdrObject is + // inserted in a SdrObjList (page/group/3dScene) + SdrPage* pPageCandidate(getSdrPageFromSdrObject()); + + // tdf#12152, tdf#120728 + // + // With the paradigm change to only get a SdrPage for a SdrObject when the SdrObject + // is *inserted*, the functionality for creating 1:1 associated UNO API implementation + // SvxShapes was partially broken: The used ::CreateShape relies on the SvxPage being + // derived and the CreateShape method overloaded, implementing additional SdrInventor + // types as needed. + // + // The fallback to use SvxDrawPage::CreateShapeByTypeAndInventor is a trap: It's only + // a static fallback that handles the SdrInventor types SdrInventor::E3d and + // SdrInventor::Default. Due to that, e.g. the ReportDesigner broke in various conditions. + // + // That again has to do with the ReportDesigner being implemented using the UNO API + // aspects of SdrObjects early during their construction, not just after these are + // inserted to a SdrPage - but that is not illegal or wrong, the SdrObject exists already. + // + // As a current solution, use the (now always available) SdrModel and any of the + // existing SdrPages. The only important thing is to get a SdrPage where ::CreateShape is + // overloaded and implemented as needed. + // + // Note for the future: + // In a more ideal world there would be only one factory method for creating SdrObjects (not + // ::CreateShape and ::CreateShapeByTypeAndInventor). This also would not be placed at + // SdrPage/SvxPage at all, but at the Model where it belongs - where else would you expect + // objects for the current Model to be constructed? To have this at the Page only would make + // sense if different shapes would need to be constructed for different Pages in the same Model + // - this is never the case. + // At that Model extended functionality for that factory (or overloads and implementations) + // should be placed. But to be realistic, migrating the factories to Model now is too much + // work - maybe over time when melting SdrObject/SvxObject one day... + // + // More Note (added by noel grandin) + // Except that sd/ is being naughty and doing all kinds of magic during CreateShape that + // requires knowing which page the object is being created for. Fixing that would require + // moving a bunch of nasty logic from object creation time, to the point in time when + // it is actually added to a page. + if(nullptr == pPageCandidate) + { + // If not inserted, alternatively access a SdrPage using the SdrModel. There is + // no reason not to create and return a UNO API XShape when the SdrObject is not + // inserted - it may be in construction. Main paradigm is that it exists. + if(0 != getSdrModelFromSdrObject().GetPageCount()) + { + // Take 1st SdrPage. That may be e.g. a special page (in SD), but the + // to-be-used method ::CreateShape will be correctly overloaded in + // all cases + pPageCandidate = getSdrModelFromSdrObject().GetPage(0); + } + } + + if(nullptr != pPageCandidate) + { + uno::Reference< uno::XInterface > xPage(pPageCandidate->getUnoPage()); + if( xPage.is() ) + { + SvxDrawPage* pDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>(xPage); + if( pDrawPage ) + { + // create one + xShape = pDrawPage->CreateShape( this ); + assert(xShape); + setUnoShape( xShape ); + } + } + } + else + { + // Fallback to static base functionality. CAUTION: This will only support + // the most basic stuff like SdrInventor::E3d and SdrInventor::Default. All + // the other SdrInventor enum entries are from overloads and are *not accessible* + // using this fallback (!) - what a bad trap + rtl::Reference<SvxShape> xNewShape = SvxDrawPage::CreateShapeByTypeAndInventor( GetObjIdentifier(), GetObjInventor(), this ); + mpSvxShape = xNewShape.get(); + maWeakUnoShape = xShape = mpSvxShape; + } + + return xShape; +} + +void SdrObject::notifyShapePropertyChange( const OUString& rPropName ) const +{ + DBG_TESTSOLARMUTEX(); + + SvxShape* pSvxShape = const_cast< SdrObject* >( this )->getSvxShape(); + if ( pSvxShape ) + return pSvxShape->notifyPropertyChange( rPropName ); +} + +// transformation interface for StarOfficeAPI. This implements support for +// homogeneous 3x3 matrices containing the transformation of the SdrObject. At the +// moment it contains a shearX, rotation and translation, but for setting all linear +// transforms like Scale, ShearX, ShearY, Rotate and Translate are supported. + + +// gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon +// with the base geometry and returns TRUE. Otherwise it returns FALSE. +bool SdrObject::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const +{ + // any kind of SdrObject, just use SnapRect + tools::Rectangle aRectangle(GetSnapRect()); + + // convert to transformation values + basegfx::B2DTuple aScale(aRectangle.GetWidth(), aRectangle.GetHeight()); + basegfx::B2DTuple aTranslate(aRectangle.Left(), aRectangle.Top()); + + // position maybe relative to anchorpos, convert + if(getSdrModelFromSdrObject().IsWriter()) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build matrix + rMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(aScale, aTranslate); + + return false; +} + +// sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix. +// If it's an SdrPathObj it will use the provided geometry information. The Polygon has +// to use (0,0) as upper left and will be scaled to the given size in the matrix. +void SdrObject::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/) +{ + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings + // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly + if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) + { + aScale.setX(fabs(aScale.getX())); + aScale.setY(fabs(aScale.getY())); + } + + // if anchor is used, make position relative to it + if(getSdrModelFromSdrObject().IsWriter()) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build BaseRect + Point aPoint(FRound(aTranslate.getX()), FRound(aTranslate.getY())); + tools::Rectangle aBaseRect(aPoint, Size(FRound(aScale.getX()), FRound(aScale.getY()))); + + // set BaseRect + SetSnapRect(aBaseRect); +} + +// Give info if object is in destruction +bool SdrObject::IsInDestruction() const +{ + return getSdrModelFromSdrObject().IsInDestruction(); +} + +// return if fill is != drawing::FillStyle_NONE +bool SdrObject::HasFillStyle() const +{ + return GetObjectItem(XATTR_FILLSTYLE).GetValue() != drawing::FillStyle_NONE; +} + +bool SdrObject::HasLineStyle() const +{ + return GetObjectItem(XATTR_LINESTYLE).GetValue() != drawing::LineStyle_NONE; +} + + +// #i52224# +// on import of OLE object from MS documents the BLIP size might be retrieved, +// the following four methods are used to control it; +// usually this data makes no sense after the import is finished, since the object +// might be resized + + +void SdrObject::SetBLIPSizeRectangle( const tools::Rectangle& aRect ) +{ + maBLIPSizeRectangle = aRect; +} + +void SdrObject::SetContextWritingMode( const sal_Int16 /*_nContextWritingMode*/ ) +{ + // this base class does not support different writing modes, so ignore the call +} + +void SdrObject::SetDoNotInsertIntoPageAutomatically(const bool bSet) +{ + mbDoNotInsertIntoPageAutomatically = bSet; +} + + +// #i121917# +bool SdrObject::HasText() const +{ + return false; +} + +bool SdrObject::IsTextBox() const +{ + return false; +} + +void SdrObject::MakeNameUnique() +{ + if (GetName().isEmpty()) + { + OUString aName; + if (const E3dScene* pE3dObj = DynCastE3dScene(this)) + { + SdrObjList* pObjList = pE3dObj->GetSubList(); + if (pObjList) + { + SdrObject* pObj0 = pObjList->GetObj(0); + if (pObj0) + aName = pObj0->TakeObjNameSingul(); + } + } + else + aName = TakeObjNameSingul(); + SetName(aName + " 1"); + } + + std::unordered_set<OUString> aNameSet; + MakeNameUnique(aNameSet); +} + +void SdrObject::MakeNameUnique(std::unordered_set<OUString>& rNameSet) +{ + if (GetName().isEmpty()) + return; + + if (rNameSet.empty()) + { + SdrPage* pPage; + SdrObject* pObj; + for (sal_uInt16 nPage(0); nPage < mrSdrModelFromSdrObject.GetPageCount(); ++nPage) + { + pPage = mrSdrModelFromSdrObject.GetPage(nPage); + SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups); + while (aIter.IsMore()) + { + pObj = aIter.Next(); + if (pObj != this) + rNameSet.insert(pObj->GetName()); + } + } + } + + OUString sName(GetName().trim()); + OUString sRootName(sName); + + if (!sName.isEmpty() && rtl::isAsciiDigit(sName[sName.getLength() - 1])) + { + sal_Int32 nPos(sName.getLength() - 1); + while (nPos > 0 && rtl::isAsciiDigit(sName[--nPos])); + sRootName = o3tl::trim(sName.subView(0, nPos + 1)); + } + + for (sal_uInt32 n = 1; rNameSet.find(sName) != rNameSet.end(); n++) + sName = sRootName + " " + OUString::number(n); + rNameSet.insert(sName); + + SetName(sName); +} + +void SdrObject::ForceMetricToItemPoolMetric(basegfx::B2DPolyPolygon& rPolyPolygon) const noexcept +{ + MapUnit eMapUnit(getSdrModelFromSdrObject().GetItemPool().GetMetric(0)); + if(eMapUnit == MapUnit::Map100thMM) + return; + + if (const auto eTo = MapToO3tlLength(eMapUnit); eTo != o3tl::Length::invalid) + { + const double fConvert(o3tl::convert(1.0, o3tl::Length::mm100, eTo)); + rPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(fConvert, fConvert)); + } + else + { + OSL_FAIL("Missing unit translation to PoolMetric!"); + } +} + +const tools::Rectangle& SdrObject::getOutRectangle() const +{ + return m_aOutRect; +} + +void SdrObject::setOutRectangleConst(tools::Rectangle const& rRectangle) const +{ + m_aOutRect = rRectangle; +} + +void SdrObject::setOutRectangle(tools::Rectangle const& rRectangle) +{ + m_aOutRect = rRectangle; +} + +void SdrObject::resetOutRectangle() +{ + m_aOutRect = tools::Rectangle(); +} + +void SdrObject::moveOutRectangle(sal_Int32 nXDelta, sal_Int32 nYDelta) +{ + m_aOutRect.Move(nXDelta, nYDelta); +} + +E3dScene* DynCastE3dScene(SdrObject* pObj) +{ + if( pObj && pObj->GetObjInventor() == SdrInventor::E3d && pObj->GetObjIdentifier() == SdrObjKind::E3D_Scene ) + return static_cast<E3dScene*>(pObj); + return nullptr; +} + +E3dObject* DynCastE3dObject(SdrObject* pObj) +{ + if( pObj && pObj->GetObjInventor() == SdrInventor::E3d ) + return static_cast<E3dObject*>(pObj); + return nullptr; +} + +SdrTextObj* DynCastSdrTextObj(SdrObject* pObj) +{ + // SdrTextObj has a lot of subclasses, with lots of SdrObjKind identifiers, so use a virtual method + // to be safer. + if( pObj && pObj->IsSdrTextObj() ) + return static_cast<SdrTextObj*>(pObj); + return nullptr; +} + +rtl::Reference<SdrObject> SdrObjFactory::CreateObjectFromFactory(SdrModel& rSdrModel, SdrInventor nInventor, SdrObjKind nObjIdentifier) +{ + SdrObjCreatorParams aParams { nInventor, nObjIdentifier, rSdrModel }; + for (const auto & i : ImpGetUserMakeObjHdl()) { + rtl::Reference<SdrObject> pObj = i.Call(aParams); + if (pObj) { + return pObj; + } + } + return nullptr; +} + +namespace +{ + +// SdrObject subclass, which represents an empty object of a +// certain type (kind). +template <SdrObjKind OBJECT_KIND, SdrInventor OBJECT_INVENTOR> +class EmptyObject final : public SdrObject +{ +private: + virtual ~EmptyObject() override + {} + +public: + EmptyObject(SdrModel& rSdrModel) + : SdrObject(rSdrModel) + { + } + + EmptyObject(SdrModel& rSdrModel, EmptyObject const& rSource) + : SdrObject(rSdrModel, rSource) + { + } + + rtl::Reference<SdrObject> CloneSdrObject(SdrModel& rTargetModel) const override + { + return new EmptyObject(rTargetModel, *this); + } + + virtual std::unique_ptr<sdr::properties::BaseProperties> CreateObjectSpecificProperties() override + { + return std::make_unique<sdr::properties::EmptyProperties>(*this); + } + + SdrInventor GetObjInventor() const override + { + return OBJECT_INVENTOR; + } + + SdrObjKind GetObjIdentifier() const override + { + return OBJECT_KIND; + } + + void NbcRotate(const Point& /*rRef*/, Degree100 /*nAngle*/, double /*sinAngle*/, double /*cosAngle*/) override + { + assert(false); // should not be called for this kind of objects + } +}; + +} // end anonymous namespace + +rtl::Reference<SdrObject> SdrObjFactory::MakeNewObject( + SdrModel& rSdrModel, + SdrInventor nInventor, + SdrObjKind nIdentifier, + const tools::Rectangle* pSnapRect) +{ + rtl::Reference<SdrObject> pObj; + bool bSetSnapRect(nullptr != pSnapRect); + + if (nInventor == SdrInventor::Default) + { + switch (nIdentifier) + { + case SdrObjKind::Measure: + { + if(nullptr != pSnapRect) + { + pObj = new SdrMeasureObj( + rSdrModel, + pSnapRect->TopLeft(), + pSnapRect->BottomRight()); + } + else + { + pObj = new SdrMeasureObj(rSdrModel); + } + } + break; + case SdrObjKind::Line: + { + if(nullptr != pSnapRect) + { + basegfx::B2DPolygon aPoly; + + aPoly.append( + basegfx::B2DPoint( + pSnapRect->Left(), + pSnapRect->Top())); + aPoly.append( + basegfx::B2DPoint( + pSnapRect->Right(), + pSnapRect->Bottom())); + pObj = new SdrPathObj( + rSdrModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aPoly)); + } + else + { + pObj = new SdrPathObj( + rSdrModel, + SdrObjKind::Line); + } + } + break; + case SdrObjKind::Text: + case SdrObjKind::TitleText: + case SdrObjKind::OutlineText: + { + if(nullptr != pSnapRect) + { + pObj = new SdrRectObj( + rSdrModel, + nIdentifier, + *pSnapRect); + bSetSnapRect = false; + } + else + { + pObj = new SdrRectObj( + rSdrModel, + nIdentifier); + } + } + break; + case SdrObjKind::CircleOrEllipse: + case SdrObjKind::CircleSection: + case SdrObjKind::CircleArc: + case SdrObjKind::CircleCut: + { + SdrCircKind eCircKind = ToSdrCircKind(nIdentifier); + if(nullptr != pSnapRect) + { + pObj = new SdrCircObj(rSdrModel, eCircKind, *pSnapRect); + bSetSnapRect = false; + } + else + { + pObj = new SdrCircObj(rSdrModel, eCircKind); + } + } + break; + case SdrObjKind::NONE: pObj = nullptr; break; + case SdrObjKind::Group : pObj=new SdrObjGroup(rSdrModel); break; + case SdrObjKind::Polygon : pObj=new SdrPathObj(rSdrModel, SdrObjKind::Polygon ); break; + case SdrObjKind::PolyLine : pObj=new SdrPathObj(rSdrModel, SdrObjKind::PolyLine ); break; + case SdrObjKind::PathLine : pObj=new SdrPathObj(rSdrModel, SdrObjKind::PathLine ); break; + case SdrObjKind::PathFill : pObj=new SdrPathObj(rSdrModel, SdrObjKind::PathFill ); break; + case SdrObjKind::FreehandLine : pObj=new SdrPathObj(rSdrModel, SdrObjKind::FreehandLine ); break; + case SdrObjKind::FreehandFill : pObj=new SdrPathObj(rSdrModel, SdrObjKind::FreehandFill ); break; + case SdrObjKind::PathPoly : pObj=new SdrPathObj(rSdrModel, SdrObjKind::Polygon ); break; + case SdrObjKind::PathPolyLine : pObj=new SdrPathObj(rSdrModel, SdrObjKind::PolyLine ); break; + case SdrObjKind::Edge : pObj=new SdrEdgeObj(rSdrModel); break; + case SdrObjKind::Rectangle : pObj=new SdrRectObj(rSdrModel); break; + case SdrObjKind::Graphic : pObj=new SdrGrafObj(rSdrModel); break; + case SdrObjKind::OLE2 : pObj=new SdrOle2Obj(rSdrModel); break; + case SdrObjKind::OLEPluginFrame : pObj=new SdrOle2Obj(rSdrModel, true); break; + case SdrObjKind::Caption : pObj=new SdrCaptionObj(rSdrModel); break; + case SdrObjKind::Page : pObj=new SdrPageObj(rSdrModel); break; + case SdrObjKind::UNO : pObj=new SdrUnoObj(rSdrModel, OUString()); break; + case SdrObjKind::CustomShape: pObj=new SdrObjCustomShape(rSdrModel); break; +#if HAVE_FEATURE_AVMEDIA + case SdrObjKind::Media : pObj=new SdrMediaObj(rSdrModel); break; +#endif + case SdrObjKind::Table : pObj=new sdr::table::SdrTableObj(rSdrModel); break; + case SdrObjKind::NewFrame: // used for frame creation in writer + pObj = new EmptyObject<SdrObjKind::NewFrame, SdrInventor::Default>(rSdrModel); + break; + default: + break; + } + } + + if (!pObj) + { + pObj = CreateObjectFromFactory(rSdrModel, nInventor, nIdentifier); + } + + if (!pObj) + { + // Well, if no one wants it... + return nullptr; + } + + if(bSetSnapRect && nullptr != pSnapRect) + { + pObj->NbcSetSnapRect(*pSnapRect); + } + + return pObj; +} + +void SdrObjFactory::InsertMakeObjectHdl(Link<SdrObjCreatorParams, rtl::Reference<SdrObject>> const & rLink) +{ + std::vector<Link<SdrObjCreatorParams, rtl::Reference<SdrObject>>>& rLL=ImpGetUserMakeObjHdl(); + auto it = std::find(rLL.begin(), rLL.end(), rLink); + if (it != rLL.end()) { + OSL_FAIL("SdrObjFactory::InsertMakeObjectHdl(): Link already in place."); + } else { + rLL.push_back(rLink); + } +} + +void SdrObjFactory::RemoveMakeObjectHdl(Link<SdrObjCreatorParams, rtl::Reference<SdrObject>> const & rLink) +{ + std::vector<Link<SdrObjCreatorParams, rtl::Reference<SdrObject>>>& rLL=ImpGetUserMakeObjHdl(); + auto it = std::find(rLL.begin(), rLL.end(), rLink); + if (it != rLL.end()) + rLL.erase(it); +} + +namespace svx +{ + ISdrObjectFilter::~ISdrObjectFilter() + { + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdobjplusdata.cxx b/svx/source/svdraw/svdobjplusdata.cxx new file mode 100644 index 0000000000..8318b3df96 --- /dev/null +++ b/svx/source/svdraw/svdobjplusdata.cxx @@ -0,0 +1,63 @@ +/* -*- 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/. +*/ + +#include <svdobjplusdata.hxx> +#include <svdobjuserdatalist.hxx> +#include <o3tl/deleter.hxx> +#include <svx/svdglue.hxx> +#include <svl/SfxBroadcaster.hxx> +#include <osl/diagnose.h> + +SdrObjPlusData::SdrObjPlusData() +{ +} + +SdrObjPlusData::~SdrObjPlusData() +{ + o3tl::reset_preserve_ptr_during(pBroadcast); + pUserDataList.reset(); + pGluePoints.reset(); +} + +SdrObjPlusData* SdrObjPlusData::Clone(SdrObject* pObj1) const +{ + SdrObjPlusData* pNewPlusData=new SdrObjPlusData; + if (pUserDataList!=nullptr) { + sal_uInt16 nCount=pUserDataList->GetUserDataCount(); + if (nCount!=0) { + pNewPlusData->pUserDataList.reset(new SdrObjUserDataList); + for (sal_uInt16 i=0; i<nCount; i++) { + std::unique_ptr<SdrObjUserData> pNewUserData=pUserDataList->GetUserData(i).Clone(pObj1); + if (pNewUserData!=nullptr) { + pNewPlusData->pUserDataList->AppendUserData(std::move(pNewUserData)); + } else { + OSL_FAIL("SdrObjPlusData::Clone(): UserData.Clone() returns NULL."); + } + } + } + } + if (pGluePoints!=nullptr) pNewPlusData->pGluePoints.reset(new SdrGluePointList(*pGluePoints)); + // MtfAnimator isn't copied either + + // #i68101# + // copy object name, title and description + pNewPlusData->aObjName = aObjName; + pNewPlusData->aObjTitle = aObjTitle; + pNewPlusData->aObjDescription = aObjDescription; + pNewPlusData->isDecorative = isDecorative; + + return pNewPlusData; +} + +void SdrObjPlusData::SetGluePoints(const SdrGluePointList& rPts) +{ + *pGluePoints = rPts; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdobjuserdatalist.cxx b/svx/source/svdraw/svdobjuserdatalist.cxx new file mode 100644 index 0000000000..cae5e8db68 --- /dev/null +++ b/svx/source/svdraw/svdobjuserdatalist.cxx @@ -0,0 +1,36 @@ +/* -*- 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/. +*/ + +#include <memory> +#include <svdobjuserdatalist.hxx> + +SdrObjUserDataList::SdrObjUserDataList() {} +SdrObjUserDataList::~SdrObjUserDataList() {} + +size_t SdrObjUserDataList::GetUserDataCount() const +{ + return maList.size(); +} + +SdrObjUserData& SdrObjUserDataList::GetUserData(size_t nNum) +{ + return *maList.at(nNum); +} + +void SdrObjUserDataList::AppendUserData(std::unique_ptr<SdrObjUserData> pData) +{ + maList.push_back(std::move(pData)); +} + +void SdrObjUserDataList::DeleteUserData(size_t nNum) +{ + maList.erase(maList.begin()+nNum); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdocapt.cxx b/svx/source/svdraw/svdocapt.cxx new file mode 100644 index 0000000000..98c86664c2 --- /dev/null +++ b/svx/source/svdraw/svdocapt.cxx @@ -0,0 +1,757 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <tools/bigint.hxx> +#include <tools/helpers.hxx> + +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <sdr/contact/viewcontactofsdrcaptionobj.hxx> +#include <sdr/properties/captionproperties.hxx> +#include <svx/sdrhittesthelper.hxx> +#include <svx/sdooitm.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdview.hxx> +#include <svx/sxcecitm.hxx> +#include <svx/sxcgitm.hxx> +#include <svx/sxcllitm.hxx> +#include <svx/sxctitm.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> + +namespace { + +enum EscDir {LKS,RTS,OBN,UNT}; + +} + +class ImpCaptParams +{ +public: + SdrCaptionType eType; + tools::Long nGap; + tools::Long nEscRel; + tools::Long nEscAbs; + tools::Long nLineLen; + SdrCaptionEscDir eEscDir; + bool bFitLineLen; + bool bEscRel; + bool bFixedAngle; + +public: + ImpCaptParams() + : eType(SdrCaptionType::Type3), + nGap(0), nEscRel(5000), nEscAbs(0), + nLineLen(0), eEscDir(SdrCaptionEscDir::Horizontal), + bFitLineLen(true), bEscRel(true), bFixedAngle(false) + { + } + void CalcEscPos(const Point& rTail, const tools::Rectangle& rRect, Point& rPt, EscDir& rDir) const; +}; + +void ImpCaptParams::CalcEscPos(const Point& rTailPt, const tools::Rectangle& rRect, Point& rPt, EscDir& rDir) const +{ + Point aTl(rTailPt); // copy locally for performance reasons + tools::Long nX,nY; + if (bEscRel) { + nX=rRect.Right()-rRect.Left(); + nX=BigMulDiv(nX,nEscRel,10000); + nY=rRect.Bottom()-rRect.Top(); + nY=BigMulDiv(nY,nEscRel,10000); + } else { + nX=nEscAbs; + nY=nEscAbs; + } + nX+=rRect.Left(); + nY+=rRect.Top(); + Point aBestPt; + EscDir eBestDir=LKS; + bool bTryH=eEscDir==SdrCaptionEscDir::BestFit; + if (!bTryH) { + if (eType!=SdrCaptionType::Type1) { + bTryH=eEscDir==SdrCaptionEscDir::Horizontal; + } else { + bTryH=eEscDir==SdrCaptionEscDir::Vertical; + } + } + bool bTryV=eEscDir==SdrCaptionEscDir::BestFit; + if (!bTryV) { + if (eType!=SdrCaptionType::Type1) { + bTryV=eEscDir==SdrCaptionEscDir::Vertical; + } else { + bTryV=eEscDir==SdrCaptionEscDir::Horizontal; + } + } + + if (bTryH) { + Point aLft(rRect.Left()-nGap,nY); + Point aRgt(rRect.Right()+nGap,nY); + bool bLft=(aTl.X()-aLft.X()<aRgt.X()-aTl.X()); + if (bLft) { + eBestDir=LKS; + aBestPt=aLft; + } else { + eBestDir=RTS; + aBestPt=aRgt; + } + } + if (bTryV) { + Point aTop(nX,rRect.Top()-nGap); + Point aBtm(nX,rRect.Bottom()+nGap); + bool bTop=(aTl.Y()-aTop.Y()<aBtm.Y()-aTl.Y()); + Point aBest2; + EscDir eBest2; + if (bTop) { + eBest2=OBN; + aBest2=aTop; + } else { + eBest2=UNT; + aBest2=aBtm; + } + bool bTakeIt=eEscDir!=SdrCaptionEscDir::BestFit; + if (!bTakeIt) { + BigInt aHorX(aBestPt.X()-aTl.X()); aHorX*=aHorX; + BigInt aHorY(aBestPt.Y()-aTl.Y()); aHorY*=aHorY; + BigInt aVerX(aBest2.X()-aTl.X()); aVerX*=aVerX; + BigInt aVerY(aBest2.Y()-aTl.Y()); aVerY*=aVerY; + if (eType!=SdrCaptionType::Type1) { + bTakeIt=aVerX+aVerY<aHorX+aHorY; + } else { + bTakeIt=aVerX+aVerY>=aHorX+aHorY; + } + } + if (bTakeIt) { + aBestPt=aBest2; + eBestDir=eBest2; + } + } + rPt=aBestPt; + rDir=eBestDir; +} + + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrCaptionObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::CaptionProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrCaptionObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrCaptionObj>(*this); +} + + +SdrCaptionObj::SdrCaptionObj(SdrModel& rSdrModel) +: SdrRectObj(rSdrModel, SdrObjKind::Text), + aTailPoly(3), // default size: 3 points = 2 lines + mbSpecialTextBoxShadow(false), + mbFixedTail(false), + mbSuppressGetBitmap(false) +{ +} + +SdrCaptionObj::SdrCaptionObj(SdrModel& rSdrModel, SdrCaptionObj const & rSource) +: SdrRectObj(rSdrModel, rSource), + mbSuppressGetBitmap(false) +{ + aTailPoly = rSource.aTailPoly; + mbSpecialTextBoxShadow = rSource.mbSpecialTextBoxShadow; + mbFixedTail = rSource.mbFixedTail; + maFixedTailPos = rSource.maFixedTailPos; +} + +SdrCaptionObj::SdrCaptionObj( + SdrModel& rSdrModel, + const tools::Rectangle& rRect, + const Point& rTail) +: SdrRectObj(rSdrModel, SdrObjKind::Text,rRect), + aTailPoly(3), // default size: 3 points = 2 lines + mbSpecialTextBoxShadow(false), + mbFixedTail(false), + mbSuppressGetBitmap(false) +{ + aTailPoly[0]=maFixedTailPos=rTail; +} + +SdrCaptionObj::~SdrCaptionObj() +{ +} + +void SdrCaptionObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bRotateFreeAllowed=false; + rInfo.bRotate90Allowed =false; + rInfo.bMirrorFreeAllowed=false; + rInfo.bMirror45Allowed =false; + rInfo.bMirror90Allowed =false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =false; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bCanConvToPath =true; + rInfo.bCanConvToPoly =true; + rInfo.bCanConvToPathLineToArea=false; + rInfo.bCanConvToPolyLineToArea=false; + rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrCaptionObj::GetObjIdentifier() const +{ + return SdrObjKind::Caption; +} + +rtl::Reference<SdrObject> SdrCaptionObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrCaptionObj(rTargetModel, *this); +} + +OUString SdrCaptionObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulCAPTION)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrCaptionObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralCAPTION); +} + +basegfx::B2DPolyPolygon SdrCaptionObj::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aPolyPoly(SdrRectObj::TakeXorPoly()); + aPolyPoly.append(aTailPoly.getB2DPolygon()); + + return aPolyPoly; +} + +sal_uInt32 SdrCaptionObj::GetHdlCount() const +{ + sal_uInt32 nCount1(SdrRectObj::GetHdlCount()); + // Currently only dragging the tail's end is implemented. + return nCount1 + 1; +} + +void SdrCaptionObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + SdrRectObj::AddToHdlList(rHdlList); + // Currently only dragging the tail's end is implemented. + std::unique_ptr<SdrHdl> pHdl(new SdrHdl(aTailPoly.GetPoint(0), SdrHdlKind::Poly)); + pHdl->SetPolyNum(1); + pHdl->SetPointNum(0); + rHdlList.AddHdl(std::move(pHdl)); +} + +bool SdrCaptionObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrCaptionObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + rDrag.SetEndDragChangesAttributes(true); + rDrag.SetEndDragChangesGeoAndAttributes(true); + + if(pHdl && 0 == pHdl->GetPolyNum()) + { + return SdrRectObj::beginSpecialDrag(rDrag); + } + else + { + rDrag.SetOrtho8Possible(); + + if(!pHdl) + { + if (m_bMovProt) + return false; + + rDrag.SetNoSnap(); + rDrag.SetActionRect(getRectangle()); + + Point aHit(rDrag.GetStart()); + + if(rDrag.GetPageView() && SdrObjectPrimitiveHit(*this, aHit, {0, 0}, *rDrag.GetPageView(), nullptr, false)) + { + return true; + } + } + else + { + if((1 == pHdl->GetPolyNum()) && (0 == pHdl->GetPointNum())) + return true; + } + } + + return false; +} + +bool SdrCaptionObj::applySpecialDrag(SdrDragStat& rDrag) +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + + if(pHdl && 0 == pHdl->GetPolyNum()) + { + const bool bRet(SdrRectObj::applySpecialDrag(rDrag)); + ImpRecalcTail(); + ActionChanged(); + + return bRet; + } + else + { + Point aDelta(rDrag.GetNow()-rDrag.GetStart()); + + if(!pHdl) + { + moveRectangle(aDelta.X(), aDelta.Y()); + } + else + { + aTailPoly[0] += aDelta; + } + + ImpRecalcTail(); + ActionChanged(); + + return true; + } +} + +OUString SdrCaptionObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment) + { + return OUString(); + } + else + { + const SdrHdl* pHdl = rDrag.GetHdl(); + + if(pHdl && 0 == pHdl->GetPolyNum()) + { + return SdrRectObj::getSpecialDragComment(rDrag); + } + else + { + if(!pHdl) + { + return ImpGetDescriptionStr(STR_DragCaptFram); + } + else + { + return ImpGetDescriptionStr(STR_DragCaptTail); + } + } + } +} + + +void SdrCaptionObj::ImpGetCaptParams(ImpCaptParams& rPara) const +{ + const SfxItemSet& rSet = GetObjectItemSet(); + rPara.eType =rSet.Get(SDRATTR_CAPTIONTYPE ).GetValue(); + rPara.bFixedAngle=rSet.Get(SDRATTR_CAPTIONFIXEDANGLE).GetValue(); + rPara.nGap =static_cast<const SdrCaptionGapItem&> (rSet.Get(SDRATTR_CAPTIONGAP )).GetValue(); + rPara.eEscDir =rSet.Get(SDRATTR_CAPTIONESCDIR ).GetValue(); + rPara.bEscRel =rSet.Get(SDRATTR_CAPTIONESCISREL ).GetValue(); + rPara.nEscRel =rSet.Get(SDRATTR_CAPTIONESCREL ).GetValue(); + rPara.nEscAbs =rSet.Get(SDRATTR_CAPTIONESCABS ).GetValue(); + rPara.nLineLen =rSet.Get(SDRATTR_CAPTIONLINELEN ).GetValue(); + rPara.bFitLineLen=rSet.Get(SDRATTR_CAPTIONFITLINELEN).GetValue(); +} + +void SdrCaptionObj::ImpRecalcTail() +{ + ImpCaptParams aPara; + ImpGetCaptParams(aPara); + ImpCalcTail(aPara, aTailPoly, getRectangle()); + SetBoundAndSnapRectsDirty(); + SetXPolyDirty(); +} + +// #i35971# +// SdrCaptionObj::ImpCalcTail1 does move the object(!). What a hack. +// I really wonder why this had not triggered problems before. I am +// sure there are some places where SetTailPos() is called at least +// twice or SetSnapRect after it again just to work around this. +// Changed this method to not do that. +// Also found why this has been done: For interactive dragging of the +// tail end pos for SdrCaptionType::Type1. This sure was the simplest method +// to achieve this, at the cost of making a whole group of const methods +// of this object implicitly change the object's position. +void SdrCaptionObj::ImpCalcTail1(const ImpCaptParams& rPara, tools::Polygon& rPoly, tools::Rectangle const & rRect) +{ + tools::Polygon aPol(2); + Point aTl(rPoly[0]); + + aPol[0] = aTl; + aPol[1] = aTl; + + EscDir eEscDir; + Point aEscPos; + + rPara.CalcEscPos(aTl, rRect, aEscPos, eEscDir); + aPol[1] = aEscPos; + + if(eEscDir==LKS || eEscDir==RTS) + { + aPol[0].setX( aEscPos.X() ); + } + else + { + aPol[0].setY( aEscPos.Y() ); + } + + rPoly = aPol; +} + +void SdrCaptionObj::ImpCalcTail2(const ImpCaptParams& rPara, tools::Polygon& rPoly, tools::Rectangle const & rRect) +{ // Gap/EscDir/EscPos/Angle + tools::Polygon aPol(2); + Point aTl(rPoly[0]); + aPol[0]=aTl; + + EscDir eEscDir; + Point aEscPos; + rPara.CalcEscPos(aTl,rRect,aEscPos,eEscDir); + aPol[1]=aEscPos; + + if (!rPara.bFixedAngle) { + // TODO: Implementation missing. + } + rPoly=aPol; +} + +void SdrCaptionObj::ImpCalcTail3(const ImpCaptParams& rPara, tools::Polygon& rPoly, tools::Rectangle const & rRect) +{ // Gap/EscDir/EscPos/Angle/LineLen + tools::Polygon aPol(3); + Point aTl(rPoly[0]); + aPol[0]=aTl; + + EscDir eEscDir; + Point aEscPos; + rPara.CalcEscPos(aTl,rRect,aEscPos,eEscDir); + aPol[1]=aEscPos; + aPol[2]=aEscPos; + + if (eEscDir==LKS || eEscDir==RTS) { + if (rPara.bFitLineLen) { + aPol[1].setX((aTl.X()+aEscPos.X())/2 ); + } else { + if (eEscDir==LKS) aPol[1].AdjustX( -(rPara.nLineLen) ); + else aPol[1].AdjustX(rPara.nLineLen ); + } + } else { + if (rPara.bFitLineLen) { + aPol[1].setY((aTl.Y()+aEscPos.Y())/2 ); + } else { + if (eEscDir==OBN) aPol[1].AdjustY( -(rPara.nLineLen) ); + else aPol[1].AdjustY(rPara.nLineLen ); + } + } + if (!rPara.bFixedAngle) { + // TODO: Implementation missing. + } + rPoly=aPol; +} + +void SdrCaptionObj::ImpCalcTail(const ImpCaptParams& rPara, tools::Polygon& rPoly, tools::Rectangle const & rRect) +{ + switch (rPara.eType) { + case SdrCaptionType::Type1: ImpCalcTail1(rPara,rPoly,rRect); break; + case SdrCaptionType::Type2: ImpCalcTail2(rPara,rPoly,rRect); break; + case SdrCaptionType::Type3: ImpCalcTail3(rPara,rPoly,rRect); break; + case SdrCaptionType::Type4: ImpCalcTail3(rPara,rPoly,rRect); break; + } +} + +bool SdrCaptionObj::BegCreate(SdrDragStat& rStat) +{ + if (getRectangle().IsEmpty()) + return false; // Create currently only works with the given Rect + + ImpCaptParams aPara; + ImpGetCaptParams(aPara); + moveRectanglePosition(rStat.GetNow().X(), rStat.GetNow().Y()); + aTailPoly[0]=rStat.GetStart(); + ImpCalcTail(aPara,aTailPoly, getRectangle()); + rStat.SetActionRect(getRectangle()); + return true; +} + +bool SdrCaptionObj::MovCreate(SdrDragStat& rStat) +{ + ImpCaptParams aPara; + ImpGetCaptParams(aPara); + moveRectanglePosition(rStat.GetNow().X(), rStat.GetNow().Y()); + ImpCalcTail(aPara,aTailPoly, getRectangle()); + rStat.SetActionRect(getRectangle()); + SetBoundRectDirty(); + m_bSnapRectDirty=true; + return true; +} + +bool SdrCaptionObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + ImpCaptParams aPara; + ImpGetCaptParams(aPara); + moveRectanglePosition(rStat.GetNow().X(), rStat.GetNow().Y()); + ImpCalcTail(aPara,aTailPoly, getRectangle()); + SetBoundAndSnapRectsDirty(); + return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2); +} + +bool SdrCaptionObj::BckCreate(SdrDragStat& /*rStat*/) +{ + return false; +} + +void SdrCaptionObj::BrkCreate(SdrDragStat& /*rStat*/) +{ +} + +basegfx::B2DPolyPolygon SdrCaptionObj::TakeCreatePoly(const SdrDragStat& /*rDrag*/) const +{ + basegfx::B2DPolyPolygon aRetval; + const basegfx::B2DRange aRange =vcl::unotools::b2DRectangleFromRectangle(getRectangle()); + aRetval.append(basegfx::utils::createPolygonFromRect(aRange)); + aRetval.append(aTailPoly.getB2DPolygon()); + return aRetval; +} + +PointerStyle SdrCaptionObj::GetCreatePointer() const +{ + return PointerStyle::DrawCaption; +} + +void SdrCaptionObj::NbcMove(const Size& rSiz) +{ + SdrRectObj::NbcMove(rSiz); + MovePoly(aTailPoly,rSiz); + if(mbFixedTail) + SetTailPos(GetFixedTailPos()); +} + +void SdrCaptionObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + SdrRectObj::NbcResize(rRef,xFact,yFact); + ResizePoly(aTailPoly,rRef,xFact,yFact); + ImpRecalcTail(); + if(mbFixedTail) + SetTailPos(GetFixedTailPos()); +} + +void SdrCaptionObj::NbcSetRelativePos(const Point& rPnt) +{ + Point aRelPos0(aTailPoly.GetPoint(0)-m_aAnchor); + Size aSiz(rPnt.X()-aRelPos0.X(),rPnt.Y()-aRelPos0.Y()); + NbcMove(aSiz); // This also calls SetRectsDirty() +} + +Point SdrCaptionObj::GetRelativePos() const +{ + return aTailPoly.GetPoint(0)-m_aAnchor; +} + +const tools::Rectangle& SdrCaptionObj::GetLogicRect() const +{ + return getRectangle(); +} + +void SdrCaptionObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + SdrRectObj::NbcSetLogicRect(rRect); + ImpRecalcTail(); +} + +const Point& SdrCaptionObj::GetTailPos() const +{ + return aTailPoly[0]; +} + +void SdrCaptionObj::SetTailPos(const Point& rPos) +{ + if (aTailPoly.GetSize()==0 || aTailPoly[0]!=rPos) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetTailPos(rPos); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrCaptionObj::NbcSetTailPos(const Point& rPos) +{ + aTailPoly[0]=rPos; + ImpRecalcTail(); +} + +sal_uInt32 SdrCaptionObj::GetSnapPointCount() const +{ + // TODO: Implementation missing. + return 0; +} + +Point SdrCaptionObj::GetSnapPoint(sal_uInt32 /*i*/) const +{ + // TODO: Implementation missing. + return Point(0,0); +} + +void SdrCaptionObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + SdrRectObj::Notify(rBC,rHint); + ImpRecalcTail(); +} + +std::unique_ptr<SdrObjGeoData> SdrCaptionObj::NewGeoData() const +{ + return std::make_unique<SdrCaptObjGeoData>(); +} + +void SdrCaptionObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrRectObj::SaveGeoData(rGeo); + SdrCaptObjGeoData& rCGeo=static_cast<SdrCaptObjGeoData&>(rGeo); + rCGeo.aTailPoly=aTailPoly; +} + +void SdrCaptionObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrRectObj::RestoreGeoData(rGeo); + const SdrCaptObjGeoData& rCGeo=static_cast<const SdrCaptObjGeoData&>(rGeo); + aTailPoly=rCGeo.aTailPoly; +} + +rtl::Reference<SdrObject> SdrCaptionObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + rtl::Reference<SdrObject> pRect = SdrRectObj::DoConvertToPolyObj(bBezier, bAddText); + rtl::Reference<SdrObject> pTail = ImpConvertMakeObj(basegfx::B2DPolyPolygon(aTailPoly.getB2DPolygon()), false, bBezier); + rtl::Reference<SdrObject> pRet; + if (pTail && !pRect) + pRet = std::move(pTail); + else if (pRect && !pTail) + pRet = std::move(pRect); + else if (pTail && pRect) + { + if (pTail->GetSubList()) + { + pTail->GetSubList()->NbcInsertObject(pRect.get()); + pRet = std::move(pTail); + } + else if (pRect->GetSubList()) + { + pRect->GetSubList()->NbcInsertObject(pTail.get(),0); + pRet = std::move(pRect); + } + else + { + rtl::Reference<SdrObjGroup> pGrp = new SdrObjGroup(getSdrModelFromSdrObject()); + pGrp->GetSubList()->NbcInsertObject(pRect.get()); + pGrp->GetSubList()->NbcInsertObject(pTail.get(),0); + pRet = pGrp; + } + } + return pRet; +} + +namespace { + +void handleNegativeScale(basegfx::B2DTuple & scale, double * rotate) { + assert(rotate != nullptr); + + // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings + // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly + if(basegfx::fTools::less(scale.getX(), 0.0) && basegfx::fTools::less(scale.getY(), 0.0)) + { + scale.setX(fabs(scale.getX())); + scale.setY(fabs(scale.getY())); + *rotate = fmod(*rotate + M_PI, 2 * M_PI); + } +} + +} + +// #i32599# +// Add own implementation for TRSetBaseGeometry to handle TailPos over changes. +void SdrCaptionObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/) +{ + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + handleNegativeScale(aScale, &fRotate); + + // if anchor is used, make position relative to it + if(getSdrModelFromSdrObject().IsWriter()) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build BaseRect + Point aPoint(FRound(aTranslate.getX()), FRound(aTranslate.getY())); + tools::Rectangle aBaseRect(aPoint, Size(FRound(aScale.getX()), FRound(aScale.getY()))); + + // set BaseRect, but rescue TailPos over this call + const Point aTailPoint = GetTailPos(); + SetSnapRect(aBaseRect); + SetTailPos(aTailPoint); + ImpRecalcTail(); +} + +// geometry access +basegfx::B2DPolygon SdrCaptionObj::getTailPolygon() const +{ + return aTailPoly.getB2DPolygon(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdocirc.cxx b/svx/source/svdraw/svdocirc.cxx new file mode 100644 index 0000000000..becc496c76 --- /dev/null +++ b/svx/source/svdraw/svdocirc.cxx @@ -0,0 +1,1157 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <math.h> +#include <rtl/ustrbuf.hxx> + +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <sdr/contact/viewcontactofsdrcircobj.hxx> +#include <sdr/properties/circleproperties.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdview.hxx> +#include <svx/sxciaitm.hxx> +#include <sxcikitm.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnwtit.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> + +using namespace com::sun::star; + +static Point GetAnglePnt(const tools::Rectangle& rR, Degree100 nAngle) +{ + Point aCenter(rR.Center()); + tools::Long nWdt=rR.Right()-rR.Left(); + tools::Long nHgt=rR.Bottom()-rR.Top(); + tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2; + double a = toRadians(nAngle); + Point aRetval(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad)); + if (nWdt==0) aRetval.setX(0 ); + if (nHgt==0) aRetval.setY(0 ); + if (nWdt!=nHgt) { + if (nWdt>nHgt) { + if (nWdt!=0) { + // stop possible overruns for very large objects + if (std::abs(nHgt)>32767 || std::abs(aRetval.Y())>32767) { + aRetval.setY(BigMulDiv(aRetval.Y(),nHgt,nWdt) ); + } else { + aRetval.setY(aRetval.Y()*nHgt/nWdt ); + } + } + } else { + if (nHgt!=0) { + // stop possible overruns for very large objects + if (std::abs(nWdt)>32767 || std::abs(aRetval.X())>32767) { + aRetval.setX(BigMulDiv(aRetval.X(),nWdt,nHgt) ); + } else { + aRetval.setX(aRetval.X()*nWdt/nHgt ); + } + } + } + } + aRetval+=aCenter; + return aRetval; +} + + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrCircObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::CircleProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrCircObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrCircObj>(*this); +} + +SdrCircKind ToSdrCircKind(SdrObjKind eKind) +{ + switch (eKind) + { + case SdrObjKind::CircleOrEllipse: return SdrCircKind::Full; + case SdrObjKind::CircleSection: return SdrCircKind::Section; + case SdrObjKind::CircleArc: return SdrCircKind::Arc; + case SdrObjKind::CircleCut: return SdrCircKind::Cut; + default: assert(false); + } + return SdrCircKind::Full; +} + +SdrCircObj::SdrCircObj( + SdrModel& rSdrModel, + SdrCircKind eNewKind) +: SdrRectObj(rSdrModel) +{ + nStartAngle=0_deg100; + nEndAngle=36000_deg100; + meCircleKind=eNewKind; + m_bClosedObj=eNewKind!=SdrCircKind::Arc; +} + +SdrCircObj::SdrCircObj(SdrModel& rSdrModel, SdrCircObj const & rSource) +: SdrRectObj(rSdrModel, rSource) +{ + meCircleKind = rSource.meCircleKind; + nStartAngle = rSource.nStartAngle; + nEndAngle = rSource.nEndAngle; + m_bClosedObj = rSource.m_bClosedObj; +} + +SdrCircObj::SdrCircObj( + SdrModel& rSdrModel, + SdrCircKind eNewKind, + const tools::Rectangle& rRect) +: SdrRectObj(rSdrModel, rRect) +{ + nStartAngle=0_deg100; + nEndAngle=36000_deg100; + meCircleKind=eNewKind; + m_bClosedObj=eNewKind!=SdrCircKind::Arc; +} + +SdrCircObj::SdrCircObj( + SdrModel& rSdrModel, + SdrCircKind eNewKind, + const tools::Rectangle& rRect, + Degree100 nNewStartAngle, + Degree100 nNewEndAngle) +: SdrRectObj(rSdrModel, rRect) +{ + Degree100 nAngleDif=nNewEndAngle-nNewStartAngle; + nStartAngle=NormAngle36000(nNewStartAngle); + nEndAngle=NormAngle36000(nNewEndAngle); + if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle + meCircleKind=eNewKind; + m_bClosedObj=eNewKind!=SdrCircKind::Arc; +} + +SdrCircObj::~SdrCircObj() +{ +} + +void SdrCircObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + bool bCanConv=!HasText() || ImpCanConvTextToCurve(); + rInfo.bEdgeRadiusAllowed = false; + rInfo.bCanConvToPath=bCanConv; + rInfo.bCanConvToPoly=bCanConv; + rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrCircObj::GetObjIdentifier() const +{ + switch (meCircleKind) + { + case SdrCircKind::Full: return SdrObjKind::CircleOrEllipse; + case SdrCircKind::Section: return SdrObjKind::CircleSection; + case SdrCircKind::Cut: return SdrObjKind::CircleCut; + case SdrCircKind::Arc: return SdrObjKind::CircleArc; + default: assert(false); + } + return SdrObjKind::CircleOrEllipse; +} + +bool SdrCircObj::PaintNeedsXPolyCirc() const +{ + // XPoly is necessary for all rotated ellipse objects, circle and + // ellipse segments. + // If not WIN, then (for now) also for circle/ellipse segments and circle/ + // ellipse arcs (for precision) + bool bNeed = maGeo.m_nRotationAngle || maGeo.m_nShearAngle || meCircleKind == SdrCircKind::Cut; + // If not WIN, then for everything except full circle (for now!) + if (meCircleKind!=SdrCircKind::Full) bNeed = true; + + const SfxItemSet& rSet = GetObjectItemSet(); + if(!bNeed) + { + // XPoly is necessary for everything that isn't LineSolid or LineNone + drawing::LineStyle eLine = rSet.Get(XATTR_LINESTYLE).GetValue(); + bNeed = eLine != drawing::LineStyle_NONE && eLine != drawing::LineStyle_SOLID; + + // XPoly is necessary for thick lines + if(!bNeed && eLine != drawing::LineStyle_NONE) + bNeed = rSet.Get(XATTR_LINEWIDTH).GetValue() != 0; + + // XPoly is necessary for circle arcs with line ends + if(!bNeed && meCircleKind == SdrCircKind::Arc) + { + // start of the line is here if StartPolygon, StartWidth!=0 + bNeed=rSet.Get(XATTR_LINESTART).GetLineStartValue().count() != 0 && + rSet.Get(XATTR_LINESTARTWIDTH).GetValue() != 0; + + if(!bNeed) + { + // end of the line is here if EndPolygon, EndWidth!=0 + bNeed = rSet.Get(XATTR_LINEEND).GetLineEndValue().count() != 0 && + rSet.Get(XATTR_LINEENDWIDTH).GetValue() != 0; + } + } + } + + // XPoly is necessary if Fill !=None and !=Solid + if(!bNeed && meCircleKind != SdrCircKind::Arc) + { + drawing::FillStyle eFill=rSet.Get(XATTR_FILLSTYLE).GetValue(); + bNeed = eFill != drawing::FillStyle_NONE && eFill != drawing::FillStyle_SOLID; + } + + if(!bNeed && meCircleKind != SdrCircKind::Full && nStartAngle == nEndAngle) + bNeed = true; // otherwise we're drawing a full circle + + return bNeed; +} + +basegfx::B2DPolygon SdrCircObj::ImpCalcXPolyCirc(const SdrCircKind eCircleKind, const tools::Rectangle& rRect1, Degree100 nStart, Degree100 nEnd) const +{ + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRect1); + basegfx::B2DPolygon aCircPolygon; + + if(SdrCircKind::Full == eCircleKind) + { + // create full circle. Do not use createPolygonFromEllipse; it's necessary + // to get the start point to the bottom of the circle to keep compatible to + // old geometry creation + aCircPolygon = basegfx::utils::createPolygonFromUnitCircle(1); + + // needs own scaling and translation from unit circle to target size (same as + // would be in createPolygonFromEllipse) + const basegfx::B2DPoint aCenter(aRange.getCenter()); + const basegfx::B2DHomMatrix aMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix( + aRange.getWidth() / 2.0, aRange.getHeight() / 2.0, + aCenter.getX(), aCenter.getY())); + aCircPolygon.transform(aMatrix); + } + else + { + // mirror start, end for geometry creation since model coordinate system is mirrored in Y + const double fStart(toRadians((36000_deg100 - nEnd) % 36000_deg100)); + const double fEnd(toRadians((36000_deg100 - nStart) % 36000_deg100)); + + // create circle segment. This is not closed by default + aCircPolygon = basegfx::utils::createPolygonFromEllipseSegment( + aRange.getCenter(), aRange.getWidth() / 2.0, aRange.getHeight() / 2.0, + fStart, fEnd); + + // check closing states + const bool bCloseSegment(SdrCircKind::Arc != eCircleKind); + const bool bCloseUsingCenter(SdrCircKind::Section == eCircleKind); + + if(bCloseSegment) + { + if(bCloseUsingCenter) + { + // add center point at start (for historical reasons) + basegfx::B2DPolygon aSector; + aSector.append(aRange.getCenter()); + aSector.append(aCircPolygon); + aCircPolygon = aSector; + } + + // close + aCircPolygon.setClosed(true); + } + } + + // #i76950# + if (maGeo.m_nShearAngle || maGeo.m_nRotationAngle) + { + // translate top left to (0,0) + const basegfx::B2DPoint aTopLeft(aRange.getMinimum()); + basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix( + -aTopLeft.getX(), -aTopLeft.getY())); + + // shear, rotate and back to top left (if needed) + aMatrix = basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + -maGeo.mfTanShearAngle, + maGeo.m_nRotationAngle ? toRadians(36000_deg100 - maGeo.m_nRotationAngle) : 0.0, + aTopLeft) * aMatrix; + + // apply transformation + aCircPolygon.transform(aMatrix); + } + + return aCircPolygon; +} + +void SdrCircObj::RecalcXPoly() +{ + basegfx::B2DPolygon aPolyCirc(ImpCalcXPolyCirc(meCircleKind, getRectangle(), nStartAngle, nEndAngle)); + mpXPoly = XPolygon(aPolyCirc); +} + +OUString SdrCircObj::TakeObjNameSingul() const +{ + TranslateId pID=STR_ObjNameSingulCIRC; + if (getRectangle().GetWidth() == getRectangle().GetHeight() && maGeo.m_nShearAngle == 0_deg100) + { + switch (meCircleKind) { + case SdrCircKind::Full: pID=STR_ObjNameSingulCIRC; break; + case SdrCircKind::Section: pID=STR_ObjNameSingulSECT; break; + case SdrCircKind::Arc: pID=STR_ObjNameSingulCARC; break; + case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUT; break; + default: break; + } + } else { + switch (meCircleKind) { + case SdrCircKind::Full: pID=STR_ObjNameSingulCIRCE; break; + case SdrCircKind::Section: pID=STR_ObjNameSingulSECTE; break; + case SdrCircKind::Arc: pID=STR_ObjNameSingulCARCE; break; + case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUTE; break; + default: break; + } + } + OUString sName(SvxResId(pID)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + return sName; +} + +OUString SdrCircObj::TakeObjNamePlural() const +{ + TranslateId pID=STR_ObjNamePluralCIRC; + if (getRectangle().GetWidth() == getRectangle().GetHeight() && maGeo.m_nShearAngle == 0_deg100) + { + switch (meCircleKind) { + case SdrCircKind::Full: pID=STR_ObjNamePluralCIRC; break; + case SdrCircKind::Section: pID=STR_ObjNamePluralSECT; break; + case SdrCircKind::Arc: pID=STR_ObjNamePluralCARC; break; + case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUT; break; + default: break; + } + } else { + switch (meCircleKind) { + case SdrCircKind::Full: pID=STR_ObjNamePluralCIRCE; break; + case SdrCircKind::Section: pID=STR_ObjNamePluralSECTE; break; + case SdrCircKind::Arc: pID=STR_ObjNamePluralCARCE; break; + case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUTE; break; + default: break; + } + } + return SvxResId(pID); +} + +rtl::Reference<SdrObject> SdrCircObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrCircObj(rTargetModel, *this); +} + +basegfx::B2DPolyPolygon SdrCircObj::TakeXorPoly() const +{ + const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, getRectangle(), nStartAngle, nEndAngle)); + return basegfx::B2DPolyPolygon(aCircPolygon); +} + +namespace { + +struct ImpCircUser : public SdrDragStatUserData +{ + tools::Rectangle aR; + Point aCenter; + Point aP1; + tools::Long nHgt; + tools::Long nWdt; + Degree100 nStart; + Degree100 nEnd; + +public: + ImpCircUser() + : nHgt(0), + nWdt(0), + nStart(0), + nEnd(0) + {} + void SetCreateParams(SdrDragStat const & rStat); +}; + +} + +sal_uInt32 SdrCircObj::GetHdlCount() const +{ + if(SdrCircKind::Full != meCircleKind) + { + return 10; + } + else + { + return 8; + } +} + +void SdrCircObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + for (sal_uInt32 nHdlNum=(SdrCircKind::Full==meCircleKind)?2:0; nHdlNum<=9; ++nHdlNum) + { + Point aPnt; + SdrHdlKind eLocalKind(SdrHdlKind::Move); + sal_uInt32 nPNum(0); + tools::Rectangle aRectangle = getRectangle(); + switch (nHdlNum) + { + case 0: + aPnt = GetAnglePnt(aRectangle, nStartAngle); + eLocalKind = SdrHdlKind::Circle; + nPNum = 1; + break; + case 1: + aPnt = GetAnglePnt(aRectangle, nEndAngle); + eLocalKind = SdrHdlKind::Circle; + nPNum = 2; + break; + case 2: + aPnt = aRectangle.TopLeft(); + eLocalKind = SdrHdlKind::UpperLeft; + break; + case 3: + aPnt = aRectangle.TopCenter(); + eLocalKind = SdrHdlKind::Upper; + break; + case 4: + aPnt = aRectangle.TopRight(); + eLocalKind = SdrHdlKind::UpperRight; + break; + case 5: + aPnt = aRectangle.LeftCenter(); + eLocalKind = SdrHdlKind::Left; + break; + case 6: + aPnt = aRectangle.RightCenter(); + eLocalKind = SdrHdlKind::Right; + break; + case 7: + aPnt = aRectangle.BottomLeft(); + eLocalKind = SdrHdlKind::LowerLeft; + break; + case 8: + aPnt = aRectangle.BottomCenter(); + eLocalKind = SdrHdlKind::Lower; + break; + case 9: + aPnt = aRectangle.BottomRight(); + eLocalKind = SdrHdlKind::LowerRight; + break; + } + + if (maGeo.m_nShearAngle) + { + ShearPoint(aPnt, aRectangle.TopLeft(), maGeo.mfTanShearAngle); + } + + if (maGeo.m_nRotationAngle) + { + RotatePoint(aPnt, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + } + + std::unique_ptr<SdrHdl> pH(new SdrHdl(aPnt,eLocalKind)); + pH->SetPointNum(nPNum); + pH->SetObj(const_cast<SdrCircObj*>(this)); + pH->SetRotationAngle(maGeo.m_nRotationAngle); + rHdlList.AddHdl(std::move(pH)); + } +} + + +bool SdrCircObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrCircObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if(bAngle) + { + if(1 == rDrag.GetHdl()->GetPointNum() || 2 == rDrag.GetHdl()->GetPointNum()) + { + rDrag.SetNoSnap(); + } + + return true; + } + + return SdrTextObj::beginSpecialDrag(rDrag); +} + +bool SdrCircObj::applySpecialDrag(SdrDragStat& rDrag) +{ + const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if(bAngle) + { + Point aPt(rDrag.GetNow()); + + if (maGeo.m_nRotationAngle) + RotatePoint(aPt, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + if (maGeo.m_nShearAngle) + ShearPoint(aPt, getRectangle().TopLeft(), -maGeo.mfTanShearAngle); + + aPt -= getRectangle().Center(); + + tools::Long nWdt = getRectangle().Right() - getRectangle().Left(); + tools::Long nHgt = getRectangle().Bottom() - getRectangle().Top(); + + if(nWdt>=nHgt) + { + aPt.setY(BigMulDiv(aPt.Y(),nWdt,nHgt) ); + } + else + { + aPt.setX(BigMulDiv(aPt.X(),nHgt,nWdt) ); + } + + Degree100 nAngle=NormAngle36000(GetAngle(aPt)); + + if (rDrag.GetView() && rDrag.GetView()->IsAngleSnapEnabled()) + { + Degree100 nSA=rDrag.GetView()->GetSnapAngle(); + + if (nSA) + { + nAngle+=nSA/2_deg100; + nAngle/=nSA; + nAngle*=nSA; + nAngle=NormAngle36000(nAngle); + } + } + + if(1 == rDrag.GetHdl()->GetPointNum()) + { + nStartAngle = nAngle; + } + else if(2 == rDrag.GetHdl()->GetPointNum()) + { + nEndAngle = nAngle; + } + + SetBoundAndSnapRectsDirty(); + SetXPolyDirty(); + ImpSetCircInfoToAttr(); + SetChanged(); + + return true; + } + else + { + return SdrTextObj::applySpecialDrag(rDrag); + } +} + +OUString SdrCircObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment) + { + OUStringBuffer aBuf(ImpGetDescriptionStr(STR_ViewCreateObj)); + const sal_uInt32 nPointCount(rDrag.GetPointCount()); + + if(SdrCircKind::Full != meCircleKind && nPointCount > 2) + { + const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser()); + Degree100 nAngle; + + aBuf.append(" ("); + + if(3 == nPointCount) + { + nAngle = pU->nStart; + } + else + { + nAngle = pU->nEnd; + } + + aBuf.append(SdrModel::GetAngleString(nAngle)); + aBuf.append(')'); + } + + return aBuf.makeStringAndClear(); + } + else + { + const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if(bAngle) + { + const Degree100 nAngle(1 == rDrag.GetHdl()->GetPointNum() ? nStartAngle : nEndAngle); + + return ImpGetDescriptionStr(STR_DragCircAngle) + + " (" + + SdrModel::GetAngleString(nAngle) + + ")"; + } + else + { + return SdrTextObj::getSpecialDragComment(rDrag); + } + } +} + + +void ImpCircUser::SetCreateParams(SdrDragStat const & rStat) +{ + rStat.TakeCreateRect(aR); + aR.Normalize(); + aCenter=aR.Center(); + nWdt=aR.Right()-aR.Left(); + nHgt=aR.Bottom()-aR.Top(); + nStart=0_deg100; + nEnd=36000_deg100; + if (rStat.GetPointCount()>2) { + Point aP(rStat.GetPoint(2)-aCenter); + if (nWdt==0) aP.setX(0 ); + if (nHgt==0) aP.setY(0 ); + if (nWdt>=nHgt) { + if (nHgt!=0) aP.setY(aP.Y()*nWdt/nHgt ); + } else { + if (nWdt!=0) aP.setX(aP.X()*nHgt/nWdt ); + } + nStart=NormAngle36000(GetAngle(aP)); + if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) { + Degree100 nSA=rStat.GetView()->GetSnapAngle(); + if (nSA) { // angle snapping + nStart+=nSA/2_deg100; + nStart/=nSA; + nStart*=nSA; + nStart=NormAngle36000(nStart); + } + } + aP1 = GetAnglePnt(aR,nStart); + nEnd=nStart; + } else aP1=aCenter; + if (rStat.GetPointCount()<=3) + return; + + Point aP(rStat.GetPoint(3)-aCenter); + if (nWdt>=nHgt) { + aP.setY(BigMulDiv(aP.Y(),nWdt,nHgt) ); + } else { + aP.setX(BigMulDiv(aP.X(),nHgt,nWdt) ); + } + nEnd=NormAngle36000(GetAngle(aP)); + if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) { + Degree100 nSA=rStat.GetView()->GetSnapAngle(); + if (nSA) { // angle snapping + nEnd+=nSA/2_deg100; + nEnd/=nSA; + nEnd*=nSA; + nEnd=NormAngle36000(nEnd); + } + } +} + +void SdrCircObj::ImpSetCreateParams(SdrDragStat& rStat) +{ + ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser()); + if (pU==nullptr) { + pU=new ImpCircUser; + rStat.SetUser(std::unique_ptr<ImpCircUser>(pU)); + } + pU->SetCreateParams(rStat); +} + +bool SdrCircObj::BegCreate(SdrDragStat& rStat) +{ + rStat.SetOrtho4Possible(); + tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow()); + aRect1.Normalize(); + rStat.SetActionRect(aRect1); + setRectangle(aRect1); + ImpSetCreateParams(rStat); + return true; +} + +bool SdrCircObj::MovCreate(SdrDragStat& rStat) +{ + ImpSetCreateParams(rStat); + ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser()); + rStat.SetActionRect(pU->aR); + setRectangle(pU->aR); // for ObjName + ImpJustifyRect(maRectangle); + nStartAngle=pU->nStart; + nEndAngle=pU->nEnd; + SetBoundRectDirty(); + m_bSnapRectDirty=true; + SetXPolyDirty(); + + // #i103058# push current angle settings to ItemSet to + // allow FullDrag visualisation + if(rStat.GetPointCount() >= 4) + { + ImpSetCircInfoToAttr(); + } + + return true; +} + +bool SdrCircObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + ImpSetCreateParams(rStat); + ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser()); + bool bRet = false; + if (eCmd==SdrCreateCmd::ForceEnd && rStat.GetPointCount()<4) meCircleKind=SdrCircKind::Full; + if (meCircleKind==SdrCircKind::Full) { + bRet=rStat.GetPointCount()>=2; + if (bRet) { + tools::Rectangle aRectangle(pU->aR); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + } + } else { + rStat.SetNoSnap(rStat.GetPointCount()>=2); + rStat.SetOrtho4Possible(rStat.GetPointCount()<2); + bRet=rStat.GetPointCount()>=4; + if (bRet) { + tools::Rectangle aRectangle(pU->aR); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + nStartAngle=pU->nStart; + nEndAngle=pU->nEnd; + } + } + m_bClosedObj=meCircleKind!=SdrCircKind::Arc; + SetBoundAndSnapRectsDirty(); + SetXPolyDirty(); + ImpSetCircInfoToAttr(); + if (bRet) + rStat.SetUser(nullptr); + return bRet; +} + +void SdrCircObj::BrkCreate(SdrDragStat& rStat) +{ + rStat.SetUser(nullptr); +} + +bool SdrCircObj::BckCreate(SdrDragStat& rStat) +{ + rStat.SetNoSnap(rStat.GetPointCount()>=3); + rStat.SetOrtho4Possible(rStat.GetPointCount()<3); + return meCircleKind!=SdrCircKind::Full; +} + +basegfx::B2DPolyPolygon SdrCircObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser()); + + if(rDrag.GetPointCount() < 4) + { + // force to OBJ_CIRC to get full visualisation + basegfx::B2DPolyPolygon aRetval(ImpCalcXPolyCirc(SdrCircKind::Full, pU->aR, pU->nStart, pU->nEnd)); + + if(3 == rDrag.GetPointCount()) + { + // add edge to first point on ellipse + basegfx::B2DPolygon aNew; + + aNew.append(basegfx::B2DPoint(pU->aCenter.X(), pU->aCenter.Y())); + aNew.append(basegfx::B2DPoint(pU->aP1.X(), pU->aP1.Y())); + aRetval.append(aNew); + } + + return aRetval; + } + else + { + return basegfx::B2DPolyPolygon(ImpCalcXPolyCirc(meCircleKind, pU->aR, pU->nStart, pU->nEnd)); + } +} + +PointerStyle SdrCircObj::GetCreatePointer() const +{ + switch (meCircleKind) { + case SdrCircKind::Full: return PointerStyle::DrawEllipse; + case SdrCircKind::Section: return PointerStyle::DrawPie; + case SdrCircKind::Arc: return PointerStyle::DrawArc; + case SdrCircKind::Cut: return PointerStyle::DrawCircleCut; + default: break; + } // switch + return PointerStyle::Cross; +} + +void SdrCircObj::NbcMove(const Size& aSize) +{ + moveRectangle(aSize.Width(), aSize.Height()); + moveOutRectangle(aSize.Width(), aSize.Height()); + maSnapRect.Move(aSize); + SetXPolyDirty(); + SetBoundAndSnapRectsDirty(true); +} + +void SdrCircObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + Degree100 nAngle0 = maGeo.m_nRotationAngle; + bool bNoShearRota = (maGeo.m_nRotationAngle == 0_deg100 && maGeo.m_nShearAngle == 0_deg100); + SdrTextObj::NbcResize(rRef,xFact,yFact); + bNoShearRota |= (maGeo.m_nRotationAngle == 0_deg100 && maGeo.m_nShearAngle == 0_deg100); + if (meCircleKind!=SdrCircKind::Full) { + bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0); + bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0); + if (bXMirr || bYMirr) { + // At bXMirr!=bYMirr we should actually swap both line ends. + // That, however, is pretty bad (because of forced "hard" formatting). + // Alternatively, we could implement a bMirrored flag (maybe even + // a more general one, e. g. for mirrored text, ...). + Degree100 nS0=nStartAngle; + Degree100 nE0=nEndAngle; + if (bNoShearRota) { + // the RectObj already mirrors at VMirror because of a 180deg rotation + if (! (bXMirr && bYMirr)) { + Degree100 nTmp=nS0; + nS0=18000_deg100-nE0; + nE0=18000_deg100-nTmp; + } + } else { // mirror contorted ellipses + if (bXMirr!=bYMirr) { + nS0+=nAngle0; + nE0+=nAngle0; + if (bXMirr) { + Degree100 nTmp=nS0; + nS0=18000_deg100-nE0; + nE0=18000_deg100-nTmp; + } + if (bYMirr) { + Degree100 nTmp=nS0; + nS0=-nE0; + nE0=-nTmp; + } + nS0 -= maGeo.m_nRotationAngle; + nE0 -= maGeo.m_nRotationAngle; + } + } + Degree100 nAngleDif=nE0-nS0; + nStartAngle=NormAngle36000(nS0); + nEndAngle =NormAngle36000(nE0); + if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle + } + } + SetXPolyDirty(); + ImpSetCircInfoToAttr(); +} + +void SdrCircObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear); + SetXPolyDirty(); + ImpSetCircInfoToAttr(); +} + +void SdrCircObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + bool bFreeMirr=meCircleKind!=SdrCircKind::Full; + Point aTmpPt1; + Point aTmpPt2; + if (bFreeMirr) { // some preparations for using an arbitrary axis of reflection + Point aCenter(getRectangle().Center()); + tools::Long nWdt = getRectangle().GetWidth() - 1; + tools::Long nHgt = getRectangle().GetHeight() - 1; + tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2; + // starting point + double a = toRadians(nStartAngle); + aTmpPt1=Point(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad)); + if (nWdt==0) aTmpPt1.setX(0 ); + if (nHgt==0) aTmpPt1.setY(0 ); + aTmpPt1+=aCenter; + // finishing point + a = toRadians(nEndAngle); + aTmpPt2=Point(FRound(cos(a)*nMaxRad),-FRound(sin(a)*nMaxRad)); + if (nWdt==0) aTmpPt2.setX(0 ); + if (nHgt==0) aTmpPt2.setY(0 ); + aTmpPt2+=aCenter; + if (maGeo.m_nRotationAngle) + { + RotatePoint(aTmpPt1, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + RotatePoint(aTmpPt2, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + } + if (maGeo.m_nShearAngle) + { + ShearPoint(aTmpPt1, getRectangle().TopLeft(), maGeo.mfTanShearAngle); + ShearPoint(aTmpPt2, getRectangle().TopLeft(), maGeo.mfTanShearAngle); + } + } + SdrTextObj::NbcMirror(rRef1,rRef2); + if (meCircleKind!=SdrCircKind::Full) { // adapt starting and finishing angle + MirrorPoint(aTmpPt1,rRef1,rRef2); + MirrorPoint(aTmpPt2,rRef1,rRef2); + // unrotate: + if (maGeo.m_nRotationAngle) + { + RotatePoint(aTmpPt1, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion + RotatePoint(aTmpPt2, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion + } + // unshear: + if (maGeo.m_nShearAngle) + { + ShearPoint(aTmpPt1, getRectangle().TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion + ShearPoint(aTmpPt2, getRectangle().TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion + } + Point aCenter(getRectangle().Center()); + aTmpPt1-=aCenter; + aTmpPt2-=aCenter; + // because it's mirrored, the angles are swapped, too + nStartAngle=GetAngle(aTmpPt2); + nEndAngle =GetAngle(aTmpPt1); + Degree100 nAngleDif=nEndAngle-nStartAngle; + nStartAngle=NormAngle36000(nStartAngle); + nEndAngle =NormAngle36000(nEndAngle); + if (nAngleDif==36000_deg100) nEndAngle+=nAngleDif; // full circle + } + SetXPolyDirty(); + ImpSetCircInfoToAttr(); +} + +std::unique_ptr<SdrObjGeoData> SdrCircObj::NewGeoData() const +{ + return std::make_unique<SdrCircObjGeoData>(); +} + +void SdrCircObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrRectObj::SaveGeoData(rGeo); + SdrCircObjGeoData& rCGeo=static_cast<SdrCircObjGeoData&>(rGeo); + rCGeo.nStartAngle=nStartAngle; + rCGeo.nEndAngle =nEndAngle; +} + +void SdrCircObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrRectObj::RestoreGeoData(rGeo); + const SdrCircObjGeoData& rCGeo=static_cast<const SdrCircObjGeoData&>(rGeo); + nStartAngle=rCGeo.nStartAngle; + nEndAngle =rCGeo.nEndAngle; + SetXPolyDirty(); + ImpSetCircInfoToAttr(); +} + +static void Union(tools::Rectangle& rR, const Point& rP) +{ + if (rP.X()<rR.Left ()) rR.SetLeft(rP.X() ); + if (rP.X()>rR.Right ()) rR.SetRight(rP.X() ); + if (rP.Y()<rR.Top ()) rR.SetTop(rP.Y() ); + if (rP.Y()>rR.Bottom()) rR.SetBottom(rP.Y() ); +} + +void SdrCircObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + rRect = getRectangle(); + if (meCircleKind!=SdrCircKind::Full) { + const Point aPntStart(GetAnglePnt(getRectangle(), nStartAngle)); + const Point aPntEnd(GetAnglePnt(getRectangle(), nEndAngle)); + Degree100 a=nStartAngle; + Degree100 e=nEndAngle; + rRect.SetLeft(getRectangle().Right() ); + rRect.SetRight(getRectangle().Left() ); + rRect.SetTop(getRectangle().Bottom() ); + rRect.SetBottom(getRectangle().Top() ); + Union(rRect,aPntStart); + Union(rRect,aPntEnd); + if ((a<=18000_deg100 && e>=18000_deg100) || (a>e && (a<=18000_deg100 || e>=18000_deg100))) { + Union(rRect, getRectangle().LeftCenter()); + } + if ((a<=27000_deg100 && e>=27000_deg100) || (a>e && (a<=27000_deg100 || e>=27000_deg100))) { + Union(rRect, getRectangle().BottomCenter()); + } + if (a>e) { + Union(rRect, getRectangle().RightCenter()); + } + if ((a<=9000_deg100 && e>=9000_deg100) || (a>e && (a<=9000_deg100 || e>=9000_deg100))) { + Union(rRect, getRectangle().TopCenter()); + } + if (meCircleKind==SdrCircKind::Section) { + Union(rRect, getRectangle().Center()); + } + if (maGeo.m_nRotationAngle) + { + Point aDst(rRect.TopLeft()); + aDst -= getRectangle().TopLeft(); + Point aDst0(aDst); + RotatePoint(aDst,Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aDst-=aDst0; + rRect.Move(aDst.X(),aDst.Y()); + } + } + if (maGeo.m_nShearAngle==0_deg100) + return; + + tools::Long nDst = FRound((rRect.Bottom() - rRect.Top()) * maGeo.mfTanShearAngle); + if (maGeo.m_nShearAngle > 0_deg100) + { + Point aRef(rRect.TopLeft()); + rRect.AdjustLeft( -nDst ); + Point aTmpPt(rRect.TopLeft()); + RotatePoint(aTmpPt, aRef, maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aTmpPt-=rRect.TopLeft(); + rRect.Move(aTmpPt.X(),aTmpPt.Y()); + } else { + rRect.AdjustRight( -nDst ); + } +} + +void SdrCircObj::RecalcSnapRect() +{ + if (PaintNeedsXPolyCirc()) { + maSnapRect=GetXPoly().GetBoundRect(); + } else { + TakeUnrotatedSnapRect(maSnapRect); + } +} + +void SdrCircObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + if (maGeo.m_nRotationAngle || maGeo.m_nShearAngle || meCircleKind != SdrCircKind::Full) + { + tools::Rectangle aSR0(GetSnapRect()); + tools::Long nWdt0=aSR0.Right()-aSR0.Left(); + tools::Long nHgt0=aSR0.Bottom()-aSR0.Top(); + tools::Long nWdt1=rRect.Right()-rRect.Left(); + tools::Long nHgt1=rRect.Bottom()-rRect.Top(); + NbcResize(maSnapRect.TopLeft(),Fraction(nWdt1,nWdt0),Fraction(nHgt1,nHgt0)); + NbcMove(Size(rRect.Left()-aSR0.Left(),rRect.Top()-aSR0.Top())); + } else { + setRectangle(rRect); + ImpJustifyRect(maRectangle); + } + SetBoundAndSnapRectsDirty(); + SetXPolyDirty(); + ImpSetCircInfoToAttr(); +} + +sal_uInt32 SdrCircObj::GetSnapPointCount() const +{ + if (meCircleKind==SdrCircKind::Full) { + return 1; + } else { + return 3; + } +} + +Point SdrCircObj::GetSnapPoint(sal_uInt32 i) const +{ + switch (i) + { + case 1 : return GetAnglePnt(getRectangle(), nStartAngle); + case 2 : return GetAnglePnt(getRectangle(), nEndAngle); + default: return getRectangle().Center(); + } +} + +void SdrCircObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + SetXPolyDirty(); + SdrRectObj::Notify(rBC,rHint); + ImpSetAttrToCircInfo(); +} + + +void SdrCircObj::ImpSetAttrToCircInfo() +{ + const SfxItemSet& rSet = GetObjectItemSet(); + SdrCircKind eNewKind = rSet.Get(SDRATTR_CIRCKIND).GetValue(); + + Degree100 nNewStart = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue(); + Degree100 nNewEnd = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue(); + + bool bKindChg = meCircleKind != eNewKind; + bool bAngleChg = nNewStart != nStartAngle || nNewEnd != nEndAngle; + + if(bKindChg || bAngleChg) + { + meCircleKind = eNewKind; + nStartAngle = nNewStart; + nEndAngle = nNewEnd; + + if(bKindChg || (meCircleKind != SdrCircKind::Full && bAngleChg)) + { + SetXPolyDirty(); + SetBoundAndSnapRectsDirty(); + } + } +} + +void SdrCircObj::ImpSetCircInfoToAttr() +{ + const SfxItemSet& rSet = GetObjectItemSet(); + + SdrCircKind eOldKindA = rSet.Get(SDRATTR_CIRCKIND).GetValue(); + Degree100 nOldStartAngle = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue(); + Degree100 nOldEndAngle = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue(); + + if(meCircleKind == eOldKindA && nStartAngle == nOldStartAngle && nEndAngle == nOldEndAngle) + return; + + // since SetItem() implicitly calls ImpSetAttrToCircInfo() + // setting the item directly is necessary here. + if(meCircleKind != eOldKindA) + { + GetProperties().SetObjectItemDirect(SdrCircKindItem(meCircleKind)); + } + + if(nStartAngle != nOldStartAngle) + { + GetProperties().SetObjectItemDirect(makeSdrCircStartAngleItem(nStartAngle)); + } + + if(nEndAngle != nOldEndAngle) + { + GetProperties().SetObjectItemDirect(makeSdrCircEndAngleItem(nEndAngle)); + } + + SetXPolyDirty(); + ImpSetAttrToCircInfo(); +} + +rtl::Reference<SdrObject> SdrCircObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + const bool bFill(meCircleKind != SdrCircKind::Arc); + const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, getRectangle(), nStartAngle, nEndAngle)); + rtl::Reference<SdrObject> pRet = ImpConvertMakeObj(basegfx::B2DPolyPolygon(aCircPolygon), bFill, bBezier); + + if(bAddText) + { + pRet = ImpConvertAddText(std::move(pRet), bBezier); + } + + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoedge.cxx b/svx/source/svdraw/svdoedge.cxx new file mode 100644 index 0000000000..19c498948d --- /dev/null +++ b/svx/source/svdraw/svdoedge.cxx @@ -0,0 +1,2715 @@ +/* -*- 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/dialmgr.hxx> +#include <svx/strings.hrc> +#include <osl/diagnose.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <svl/hint.hxx> + +#include <sdr/contact/viewcontactofsdredgeobj.hxx> +#include <sdr/properties/connectorproperties.hxx> +#include <svx/compatflags.hxx> +#include <svx/sdrhittesthelper.hxx> +#include <svx/svddrag.hxx> +#include <svx/svddrgmt.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdview.hxx> +#include <svx/sxekitm.hxx> +#include <svx/sxelditm.hxx> +#include <svx/sxenditm.hxx> +#include <svx/xpoly.hxx> +#include <vcl/ptrstyle.hxx> +#include <comphelper/lok.hxx> + +void SdrObjConnection::ResetVars() +{ + m_pSdrObj=nullptr; + m_nConId=0; + m_bBestConn=true; + m_bBestVertex=true; + m_bAutoVertex=false; + m_bAutoCorner=false; +} + +bool SdrObjConnection::TakeGluePoint(SdrGluePoint& rGP) const +{ + bool bRet = false; + if (m_pSdrObj!=nullptr) { // one object has to be docked already! + if (m_bAutoVertex) { + rGP=m_pSdrObj->GetVertexGluePoint(m_nConId); + bRet = true; + } else if (m_bAutoCorner) { + rGP=m_pSdrObj->GetCornerGluePoint(m_nConId); + bRet = true; + } else { + const SdrGluePointList* pGPL=m_pSdrObj->GetGluePointList(); + if (pGPL!=nullptr) { + sal_uInt16 nNum=pGPL->FindGluePoint(m_nConId); + if (nNum!=SDRGLUEPOINT_NOTFOUND) { + rGP=(*pGPL)[nNum]; + bRet = true; + } + } + } + } + if (bRet) { + Point aPt(rGP.GetAbsolutePos(*m_pSdrObj)); + aPt+=m_aObjOfs; + rGP.SetPos(aPt); + } + return bRet; +} + +Point& SdrEdgeInfoRec::ImpGetLineOffsetPoint(SdrEdgeLineCode eLineCode) +{ + switch (eLineCode) { + case SdrEdgeLineCode::Obj1Line2 : return m_aObj1Line2; + case SdrEdgeLineCode::Obj1Line3 : return m_aObj1Line3; + case SdrEdgeLineCode::Obj2Line2 : return m_aObj2Line2; + case SdrEdgeLineCode::Obj2Line3 : return m_aObj2Line3; + case SdrEdgeLineCode::MiddleLine: return m_aMiddleLine; + } // switch + return m_aMiddleLine; +} + +sal_uInt16 SdrEdgeInfoRec::ImpGetPolyIdx(SdrEdgeLineCode eLineCode, const XPolygon& rXP) const +{ + switch (eLineCode) { + case SdrEdgeLineCode::Obj1Line2 : return 1; + case SdrEdgeLineCode::Obj1Line3 : return 2; + case SdrEdgeLineCode::Obj2Line2 : return rXP.GetPointCount()-3; + case SdrEdgeLineCode::Obj2Line3 : return rXP.GetPointCount()-4; + case SdrEdgeLineCode::MiddleLine: return m_nMiddleLine; + } // switch + return 0; +} + +bool SdrEdgeInfoRec::ImpIsHorzLine(SdrEdgeLineCode eLineCode, const XPolygon& rXP) const +{ + sal_uInt16 nIdx=ImpGetPolyIdx(eLineCode,rXP); + bool bHorz=m_nAngle1==0 || m_nAngle1==18000; + if (eLineCode==SdrEdgeLineCode::Obj2Line2 || eLineCode==SdrEdgeLineCode::Obj2Line3) { + nIdx=rXP.GetPointCount()-nIdx; + bHorz=m_nAngle2==0 || m_nAngle2==18000; + } + if ((nIdx & 1)==1) bHorz=!bHorz; + return bHorz; +} + +void SdrEdgeInfoRec::ImpSetLineOffset(SdrEdgeLineCode eLineCode, const XPolygon& rXP, tools::Long nVal) +{ + Point& rPt=ImpGetLineOffsetPoint(eLineCode); + if (ImpIsHorzLine(eLineCode,rXP)) rPt.setY(nVal ); + else rPt.setX(nVal ); +} + +tools::Long SdrEdgeInfoRec::ImpGetLineOffset(SdrEdgeLineCode eLineCode, const XPolygon& rXP) const +{ + const Point& rPt = const_cast<SdrEdgeInfoRec*>(this)->ImpGetLineOffsetPoint(eLineCode); + if (ImpIsHorzLine(eLineCode,rXP)) + return rPt.Y(); + else + return rPt.X(); +} + + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrEdgeObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::ConnectorProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrEdgeObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrEdgeObj>(*this); +} + + +SdrEdgeObj::SdrEdgeObj(SdrModel& rSdrModel) +: SdrTextObj(rSdrModel), + m_nNotifyingCount(0), + m_bEdgeTrackDirty(false), + m_bEdgeTrackUserDefined(false), + // Default is to allow default connects + mbSuppressDefaultConnect(false), + mbBoundRectCalculationRunning(false), + mbSuppressed(false) +{ + m_bClosedObj=false; + m_bIsEdge=true; + m_pEdgeTrack = XPolygon(); +} + +SdrEdgeObj::SdrEdgeObj(SdrModel& rSdrModel, SdrEdgeObj const & rSource) +: SdrTextObj(rSdrModel, rSource), + m_nNotifyingCount(0), + m_bEdgeTrackDirty(false), + m_bEdgeTrackUserDefined(false), + // Default is to allow default connects + mbSuppressDefaultConnect(false), + mbBoundRectCalculationRunning(false), + mbSuppressed(false) +{ + m_bClosedObj = false; + m_bIsEdge = true; + m_pEdgeTrack = rSource.m_pEdgeTrack; + m_bEdgeTrackDirty=rSource.m_bEdgeTrackDirty; + m_aCon1 =rSource.m_aCon1; + m_aCon2 =rSource.m_aCon2; + m_aCon1.m_pSdrObj=nullptr; + m_aCon2.m_pSdrObj=nullptr; + m_aEdgeInfo=rSource.m_aEdgeInfo; +} + +SdrEdgeObj::~SdrEdgeObj() +{ + SdrEdgeObj::DisconnectFromNode(true); + SdrEdgeObj::DisconnectFromNode(false); +} + +void SdrEdgeObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + // call parent + SdrTextObj::handlePageChange(pOldPage, pNewPage); + + if(nullptr != GetConnection(true).GetSdrObject() || nullptr != GetConnection(false).GetSdrObject()) + { + // check broadcasters; when we are not inserted we do not need broadcasters + // TTTT not yet added, but keep hint to do this here + // mpCon1->ownerPageChange(); + // mpCon2->ownerPageChange(); + } +} + +void SdrEdgeObj::ImpSetAttrToEdgeInfo() +{ + const SfxItemSet& rSet = GetObjectItemSet(); + SdrEdgeKind eKind = rSet.Get(SDRATTR_EDGEKIND).GetValue(); + sal_Int32 nVal1 = rSet.Get(SDRATTR_EDGELINE1DELTA).GetValue(); + sal_Int32 nVal2 = rSet.Get(SDRATTR_EDGELINE2DELTA).GetValue(); + sal_Int32 nVal3 = rSet.Get(SDRATTR_EDGELINE3DELTA).GetValue(); + + if(eKind == SdrEdgeKind::OrthoLines || eKind == SdrEdgeKind::Bezier) + { + sal_Int32 nVals[3] = { nVal1, nVal2, nVal3 }; + sal_uInt16 n = 0; + + if(m_aEdgeInfo.m_nObj1Lines >= 2 && n < 3) + { + m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::Obj1Line2, *m_pEdgeTrack, nVals[n]); + n++; + } + + if(m_aEdgeInfo.m_nObj1Lines >= 3 && n < 3) + { + m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::Obj1Line3, *m_pEdgeTrack, nVals[n]); + n++; + } + + if(m_aEdgeInfo.m_nMiddleLine != 0xFFFF && n < 3) + { + m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::MiddleLine, *m_pEdgeTrack, nVals[n]); + n++; + } + + if(m_aEdgeInfo.m_nObj2Lines >= 3 && n < 3) + { + m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::Obj2Line3, *m_pEdgeTrack, nVals[n]); + n++; + } + + if(m_aEdgeInfo.m_nObj2Lines >= 2 && n < 3) + { + m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::Obj2Line2, *m_pEdgeTrack, nVals[n]); + n++; + } + + // Do not overwrite existing value with default. ImpSetAttrToEdgeInfo() is called several + // times with a set, that does not have SDRATTR_EDGEOOXMLCURVE item. + if (rSet.HasItem(SDRATTR_EDGEOOXMLCURVE)) + m_aEdgeInfo.m_bUseOOXMLCurve = rSet.Get(SDRATTR_EDGEOOXMLCURVE).GetValue(); + } + else if(eKind == SdrEdgeKind::ThreeLines) + { + bool bHor1 = m_aEdgeInfo.m_nAngle1 == 0 || m_aEdgeInfo.m_nAngle1 == 18000; + bool bHor2 = m_aEdgeInfo.m_nAngle2 == 0 || m_aEdgeInfo.m_nAngle2 == 18000; + + if(bHor1) + { + m_aEdgeInfo.m_aObj1Line2.setX( nVal1 ); + } + else + { + m_aEdgeInfo.m_aObj1Line2.setY( nVal1 ); + } + + if(bHor2) + { + m_aEdgeInfo.m_aObj2Line2.setX( nVal2 ); + } + else + { + m_aEdgeInfo.m_aObj2Line2.setY( nVal2 ); + } + } + + ImpDirtyEdgeTrack(); +} + +void SdrEdgeObj::ImpSetEdgeInfoToAttr() +{ + const SfxItemSet& rSet = GetObjectItemSet(); + SdrEdgeKind eKind = rSet.Get(SDRATTR_EDGEKIND).GetValue(); + sal_Int32 nValCnt = rSet.Get(SDRATTR_EDGELINEDELTACOUNT).GetValue(); + sal_Int32 nVal1 = rSet.Get(SDRATTR_EDGELINE1DELTA).GetValue(); + sal_Int32 nVal2 = rSet.Get(SDRATTR_EDGELINE2DELTA).GetValue(); + sal_Int32 nVal3 = rSet.Get(SDRATTR_EDGELINE3DELTA).GetValue(); + sal_Int32 nVals[3] = { nVal1, nVal2, nVal3 }; + sal_uInt16 n = 0; + + if(eKind == SdrEdgeKind::OrthoLines || eKind == SdrEdgeKind::Bezier) + { + if(m_aEdgeInfo.m_nObj1Lines >= 2 && n < 3) + { + nVals[n] = m_aEdgeInfo.ImpGetLineOffset(SdrEdgeLineCode::Obj1Line2, *m_pEdgeTrack); + n++; + } + + if(m_aEdgeInfo.m_nObj1Lines >= 3 && n < 3) + { + nVals[n] = m_aEdgeInfo.ImpGetLineOffset(SdrEdgeLineCode::Obj1Line3, *m_pEdgeTrack); + n++; + } + + if(m_aEdgeInfo.m_nMiddleLine != 0xFFFF && n < 3) + { + nVals[n] = m_aEdgeInfo.ImpGetLineOffset(SdrEdgeLineCode::MiddleLine, *m_pEdgeTrack); + n++; + } + + if(m_aEdgeInfo.m_nObj2Lines >= 3 && n < 3) + { + nVals[n] = m_aEdgeInfo.ImpGetLineOffset(SdrEdgeLineCode::Obj2Line3, *m_pEdgeTrack); + n++; + } + + if(m_aEdgeInfo.m_nObj2Lines >= 2 && n < 3) + { + nVals[n] = m_aEdgeInfo.ImpGetLineOffset(SdrEdgeLineCode::Obj2Line2, *m_pEdgeTrack); + n++; + } + } + else if(eKind == SdrEdgeKind::ThreeLines) + { + bool bHor1 = m_aEdgeInfo.m_nAngle1 == 0 || m_aEdgeInfo.m_nAngle1 == 18000; + bool bHor2 = m_aEdgeInfo.m_nAngle2 == 0 || m_aEdgeInfo.m_nAngle2 == 18000; + + n = 2; + nVals[0] = bHor1 ? m_aEdgeInfo.m_aObj1Line2.X() : m_aEdgeInfo.m_aObj1Line2.Y(); + nVals[1] = bHor2 ? m_aEdgeInfo.m_aObj2Line2.X() : m_aEdgeInfo.m_aObj2Line2.Y(); + } + + if(!(n != nValCnt || nVals[0] != nVal1 || nVals[1] != nVal2 || nVals[2] != nVal3)) + return; + + // Here no more notifying is necessary, just local changes are OK. + if(n != nValCnt) + { + GetProperties().SetObjectItemDirect(SdrEdgeLineDeltaCountItem(n)); + } + + if(nVals[0] != nVal1) + { + GetProperties().SetObjectItemDirect(makeSdrEdgeLine1DeltaItem(nVals[0])); + } + + if(nVals[1] != nVal2) + { + GetProperties().SetObjectItemDirect(makeSdrEdgeLine2DeltaItem(nVals[1])); + } + + if(nVals[2] != nVal3) + { + GetProperties().SetObjectItemDirect(makeSdrEdgeLine3DeltaItem(nVals[2])); + } + + if(n < 3) + { + GetProperties().ClearObjectItemDirect(SDRATTR_EDGELINE3DELTA); + } + + if(n < 2) + { + GetProperties().ClearObjectItemDirect(SDRATTR_EDGELINE2DELTA); + } + + if(n < 1) + { + GetProperties().ClearObjectItemDirect(SDRATTR_EDGELINE1DELTA); + } + + GetProperties().SetObjectItemDirect( + SfxBoolItem(SDRATTR_EDGEOOXMLCURVE, m_aEdgeInfo.m_bUseOOXMLCurve)); +} + +void SdrEdgeObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + // #i54102# allow rotation, mirror and shear + rInfo.bRotateFreeAllowed = true; + rInfo.bRotate90Allowed = true; + rInfo.bMirrorFreeAllowed = true; + rInfo.bMirror45Allowed = true; + rInfo.bMirror90Allowed = true; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed = true; + rInfo.bEdgeRadiusAllowed = false; + bool bCanConv=!HasText() || ImpCanConvTextToCurve(); + rInfo.bCanConvToPath=bCanConv; + rInfo.bCanConvToPoly=bCanConv; + rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrEdgeObj::GetObjIdentifier() const +{ + return SdrObjKind::Edge; +} + +const tools::Rectangle& SdrEdgeObj::GetCurrentBoundRect() const +{ + if(m_bEdgeTrackDirty) + { + const_cast<SdrEdgeObj*>(this)->ImpRecalcEdgeTrack(); + } + + return SdrTextObj::GetCurrentBoundRect(); +} + +const tools::Rectangle& SdrEdgeObj::GetSnapRect() const +{ + if(m_bEdgeTrackDirty) + { + const_cast<SdrEdgeObj*>(this)->ImpRecalcEdgeTrack(); + } + + return SdrTextObj::GetSnapRect(); +} + +void SdrEdgeObj::RecalcSnapRect() +{ + maSnapRect=m_pEdgeTrack->GetBoundRect(); +} + +void SdrEdgeObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + rRect=GetSnapRect(); +} + +SdrGluePoint SdrEdgeObj::GetVertexGluePoint(sal_uInt16 nNum) const +{ + Point aPt; + sal_uInt16 nPointCount=m_pEdgeTrack->GetPointCount(); + if (nPointCount>0) + { + Point aOfs = GetSnapRect().Center(); + if (nNum==2 && GetConnectedNode(true)==nullptr) aPt=(*m_pEdgeTrack)[0]; + else if (nNum==3 && GetConnectedNode(false)==nullptr) aPt=(*m_pEdgeTrack)[nPointCount-1]; + else { + if ((nPointCount & 1) ==1) { + aPt=(*m_pEdgeTrack)[nPointCount/2]; + } else { + Point aPt1((*m_pEdgeTrack)[nPointCount/2-1]); + Point aPt2((*m_pEdgeTrack)[nPointCount/2]); + aPt1+=aPt2; + aPt1.setX( aPt1.X() / 2 ); + aPt1.setY( aPt1.Y() / 2 ); + aPt=aPt1; + } + } + aPt-=aOfs; + } + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + return aGP; +} + +SdrGluePoint SdrEdgeObj::GetCornerGluePoint(sal_uInt16 nNum) const +{ + return GetVertexGluePoint(nNum); +} + +const SdrGluePointList* SdrEdgeObj::GetGluePointList() const +{ + return nullptr; // no user defined gluepoints for connectors +} + +SdrGluePointList* SdrEdgeObj::ForceGluePointList() +{ + return nullptr; // no user defined gluepoints for connectors +} + +void SdrEdgeObj::ConnectToNode(bool bTail1, SdrObject* pObj) +{ + SdrObjConnection& rCon=GetConnection(bTail1); + DisconnectFromNode(bTail1); + if (pObj!=nullptr) { + pObj->AddListener(*this); + rCon.m_pSdrObj=pObj; + + // #i120437# If connection is set, reset bEdgeTrackUserDefined + m_bEdgeTrackUserDefined = false; + + ImpDirtyEdgeTrack(); + } +} + +void SdrEdgeObj::DisconnectFromNode(bool bTail1) +{ + SdrObjConnection& rCon=GetConnection(bTail1); + if (rCon.m_pSdrObj!=nullptr) { + rCon.m_pSdrObj->RemoveListener(*this); + rCon.m_pSdrObj=nullptr; + } +} + +SdrObject* SdrEdgeObj::GetConnectedNode(bool bTail1) const +{ + SdrObject* pObj(GetConnection(bTail1).m_pSdrObj); + + if(nullptr != pObj + && (pObj->getSdrPageFromSdrObject() != getSdrPageFromSdrObject() || !pObj->IsInserted())) + { + pObj = nullptr; + } + + return pObj; +} + +bool SdrEdgeObj::CheckNodeConnection(bool bTail1) const +{ + bool bRet = false; + const SdrObjConnection& rCon=GetConnection(bTail1); + sal_uInt16 nPointCount=m_pEdgeTrack->GetPointCount(); + + if(nullptr != rCon.m_pSdrObj && rCon.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject() && 0 != nPointCount) + { + const SdrGluePointList* pGPL=rCon.m_pSdrObj->GetGluePointList(); + sal_uInt16 nGluePointCnt=pGPL==nullptr ? 0 : pGPL->GetCount(); + sal_uInt16 nGesAnz=nGluePointCnt+8; + Point aTail(bTail1 ? (*m_pEdgeTrack)[0] : (*m_pEdgeTrack)[sal_uInt16(nPointCount-1)]); + for (sal_uInt16 i=0; i<nGesAnz && !bRet; i++) { + if (i<nGluePointCnt) { // UserDefined + bRet=aTail==(*pGPL)[i].GetAbsolutePos(*rCon.m_pSdrObj); + } else if (i<nGluePointCnt+4) { // Vertex + SdrGluePoint aPt(rCon.m_pSdrObj->GetVertexGluePoint(i-nGluePointCnt)); + bRet=aTail==aPt.GetAbsolutePos(*rCon.m_pSdrObj); + } else { // Corner + SdrGluePoint aPt(rCon.m_pSdrObj->GetCornerGluePoint(i-nGluePointCnt-4)); + bRet=aTail==aPt.GetAbsolutePos(*rCon.m_pSdrObj); + } + } + } + return bRet; +} + +void SdrEdgeObj::ImpSetTailPoint(bool bTail1, const Point& rPt) +{ + sal_uInt16 nPointCount=m_pEdgeTrack->GetPointCount(); + if (nPointCount==0) { + (*m_pEdgeTrack)[0]=rPt; + (*m_pEdgeTrack)[1]=rPt; + } else if (nPointCount==1) { + if (!bTail1) (*m_pEdgeTrack)[1]=rPt; + else { (*m_pEdgeTrack)[1]=(*m_pEdgeTrack)[0]; (*m_pEdgeTrack)[0]=rPt; } + } else { + if (!bTail1) (*m_pEdgeTrack)[sal_uInt16(nPointCount-1)]=rPt; + else (*m_pEdgeTrack)[0]=rPt; + } + ImpRecalcEdgeTrack(); + SetBoundAndSnapRectsDirty(); +} + +void SdrEdgeObj::ImpDirtyEdgeTrack() +{ + if ( !m_bEdgeTrackUserDefined || !getSdrModelFromSdrObject().isLocked() ) + m_bEdgeTrackDirty = true; +} + +void SdrEdgeObj::ImpUndirtyEdgeTrack() +{ + if (m_bEdgeTrackDirty && getSdrModelFromSdrObject().isLocked()) + { + ImpRecalcEdgeTrack(); + } +} + +void SdrEdgeObj::ImpRecalcEdgeTrack() +{ + // #i120437# if bEdgeTrackUserDefined, do not recalculate + if(m_bEdgeTrackUserDefined) + { + return; + } + + // #i120437# also not when model locked during import, but remember + if(getSdrModelFromSdrObject().isLocked()) + { + mbSuppressed = true; + return; + } + + // #i110649# + if(mbBoundRectCalculationRunning) + { + // This object is involved into another ImpRecalcEdgeTrack() call + // from another SdrEdgeObj. Do not calculate again to avoid loop. + // Also, do not change bEdgeTrackDirty so that it gets recalculated + // later at the first non-looping call. + } + else + { + // To not run in a depth loop, use a coloring algorithm on + // SdrEdgeObj BoundRect calculations + mbBoundRectCalculationRunning = true; + + if(mbSuppressed) + { + // #i123048# If layouting was ever suppressed, it needs to be done once + // and the attr need to be set at EdgeInfo, else these attr *will be lost* + // in the following call to ImpSetEdgeInfoToAttr() since they were never + // set before (!) + *m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo); + ImpSetAttrToEdgeInfo(); + mbSuppressed = false; + } + + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetCurrentBoundRect(); + SetBoundAndSnapRectsDirty(); + *m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo); + ImpSetEdgeInfoToAttr(); // copy values from aEdgeInfo into the pool + m_bEdgeTrackDirty=false; + + // Only redraw here, no object change + ActionChanged(); + + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + + mbBoundRectCalculationRunning = false; + } +} + +SdrEscapeDirection SdrEdgeObj::ImpCalcEscAngle(SdrObject const * pObj, const Point& rPt) +{ + if (pObj==nullptr) return SdrEscapeDirection::ALL; + tools::Rectangle aR(pObj->GetSnapRect()); + tools::Long dxl=rPt.X()-aR.Left(); + tools::Long dyo=rPt.Y()-aR.Top(); + tools::Long dxr=aR.Right()-rPt.X(); + tools::Long dyu=aR.Bottom()-rPt.Y(); + bool bxMitt=std::abs(dxl-dxr)<2; + bool byMitt=std::abs(dyo-dyu)<2; + tools::Long dx=std::min(dxl,dxr); + tools::Long dy=std::min(dyo,dyu); + bool bDiag=std::abs(dx-dy)<2; + if (bxMitt && byMitt) return SdrEscapeDirection::ALL; // in the center + if (bDiag) { // diagonally + SdrEscapeDirection nRet=SdrEscapeDirection::SMART; + if (byMitt) nRet|=SdrEscapeDirection::VERT; + if (bxMitt) nRet|=SdrEscapeDirection::HORZ; + if (dxl<dxr) { // left + if (dyo<dyu) nRet|=SdrEscapeDirection::LEFT | SdrEscapeDirection::TOP; + else nRet|=SdrEscapeDirection::LEFT | SdrEscapeDirection::BOTTOM; + } else { // right + if (dyo<dyu) nRet|=SdrEscapeDirection::RIGHT | SdrEscapeDirection::TOP; + else nRet|=SdrEscapeDirection::RIGHT | SdrEscapeDirection::BOTTOM; + } + return nRet; + } + if (dx<dy) { // horizontal + if (bxMitt) return SdrEscapeDirection::HORZ; + if (dxl<dxr) return SdrEscapeDirection::LEFT; + else return SdrEscapeDirection::RIGHT; + } else { // vertical + if (byMitt) return SdrEscapeDirection::VERT; + if (dyo<dyu) return SdrEscapeDirection::TOP; + else return SdrEscapeDirection::BOTTOM; + } +} + +XPolygon SdrEdgeObj::ImpCalcObjToCenter(const Point& rStPt, tools::Long nEscAngle, const tools::Rectangle& rRect, const Point& rMeeting) +{ + XPolygon aXP; + aXP.Insert(XPOLY_APPEND,rStPt,PolyFlags::Normal); + bool bRts=nEscAngle==0; + bool bObn=nEscAngle==9000; + bool bLks=nEscAngle==18000; + bool bUnt=nEscAngle==27000; + + Point aP1(rStPt); // mandatory difference first,... + if (bLks) aP1.setX(rRect.Left() ); + if (bRts) aP1.setX(rRect.Right() ); + if (bObn) aP1.setY(rRect.Top() ); + if (bUnt) aP1.setY(rRect.Bottom() ); + + Point aP2(aP1); // ...now increase to Meeting height, if necessary + if (bLks && rMeeting.X()<=aP2.X()) aP2.setX(rMeeting.X() ); + if (bRts && rMeeting.X()>=aP2.X()) aP2.setX(rMeeting.X() ); + if (bObn && rMeeting.Y()<=aP2.Y()) aP2.setY(rMeeting.Y() ); + if (bUnt && rMeeting.Y()>=aP2.Y()) aP2.setY(rMeeting.Y() ); + aXP.Insert(XPOLY_APPEND,aP2,PolyFlags::Normal); + + Point aP3(aP2); + if ((bLks && rMeeting.X()>aP2.X()) || (bRts && rMeeting.X()<aP2.X())) { // around + if (rMeeting.Y()<aP2.Y()) { + aP3.setY(rRect.Top() ); + if (rMeeting.Y()<aP3.Y()) aP3.setY(rMeeting.Y() ); + } else { + aP3.setY(rRect.Bottom() ); + if (rMeeting.Y()>aP3.Y()) aP3.setY(rMeeting.Y() ); + } + aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); + if (aP3.Y()!=rMeeting.Y()) { + aP3.setX(rMeeting.X() ); + aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); + } + } + if ((bObn && rMeeting.Y()>aP2.Y()) || (bUnt && rMeeting.Y()<aP2.Y())) { // around + if (rMeeting.X()<aP2.X()) { + aP3.setX(rRect.Left() ); + if (rMeeting.X()<aP3.X()) aP3.setX(rMeeting.X() ); + } else { + aP3.setX(rRect.Right() ); + if (rMeeting.X()>aP3.X()) aP3.setX(rMeeting.X() ); + } + aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); + if (aP3.X()!=rMeeting.X()) { + aP3.setY(rMeeting.Y() ); + aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); + } + } +#ifdef DBG_UTIL + if (aXP.GetPointCount()>4) { + OSL_FAIL("SdrEdgeObj::ImpCalcObjToCenter(): Polygon has more than 4 points!"); + } +#endif + return aXP; +} + +XPolygon SdrEdgeObj::ImpCalcEdgeTrack(const XPolygon& rTrack0, SdrObjConnection& rCon1, SdrObjConnection& rCon2, SdrEdgeInfoRec* pInfo) const +{ + Point aPt1,aPt2; + SdrGluePoint aGP1,aGP2; + SdrEscapeDirection nEsc1=SdrEscapeDirection::ALL,nEsc2=SdrEscapeDirection::ALL; + tools::Rectangle aBoundRect1; + tools::Rectangle aBoundRect2; + tools::Rectangle aBewareRect1; + tools::Rectangle aBewareRect2; + // first, get the old corner points + if (rTrack0.GetPointCount()!=0) { + aPt1=rTrack0[0]; + sal_uInt16 nSiz=rTrack0.GetPointCount(); + nSiz--; + aPt2=rTrack0[nSiz]; + } + else + { + auto aRectangle = getOutRectangle(); + if (!aRectangle.IsEmpty()) { + aPt1 = aRectangle.TopLeft(); + aPt2 = aRectangle.BottomRight(); + } + } + + // #i54102# To allow interactive preview, do also if not inserted + const bool bCon1(nullptr != rCon1.m_pSdrObj && rCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + const bool bCon2(nullptr != rCon2.m_pSdrObj && rCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + const SfxItemSet& rSet = GetObjectItemSet(); + + if (bCon1) + { + if (rCon1.m_pSdrObj==static_cast<SdrObject const *>(this)) + { + // check, just in case + aBoundRect1 = getOutRectangle(); + } + else + { + if (getSdrModelFromSdrObject().GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect)) + aBoundRect1 = rCon1.m_pSdrObj->GetSnapRect(); + else + aBoundRect1 = rCon1.m_pSdrObj->GetCurrentBoundRect(); + } + + aBoundRect1.Move(rCon1.m_aObjOfs.X(),rCon1.m_aObjOfs.Y()); + aBewareRect1=aBoundRect1; + sal_Int32 nH = rSet.Get(SDRATTR_EDGENODE1HORZDIST).GetValue(); + sal_Int32 nV = rSet.Get(SDRATTR_EDGENODE1VERTDIST).GetValue(); + aBewareRect1.AdjustLeft( -nH ); + aBewareRect1.AdjustRight(nH ); + aBewareRect1.AdjustTop( -nV ); + aBewareRect1.AdjustBottom(nV ); + } + else + { + aBoundRect1=tools::Rectangle(aPt1,aPt1); + aBoundRect1.Move(rCon1.m_aObjOfs.X(),rCon1.m_aObjOfs.Y()); + aBewareRect1=aBoundRect1; + } + + if (bCon2) + { + if (rCon2.m_pSdrObj==static_cast<SdrObject const *>(this)) + { // check, just in case + aBoundRect2 = getOutRectangle(); + } + else + { + if (getSdrModelFromSdrObject().GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect)) + aBoundRect2 = rCon2.m_pSdrObj->GetSnapRect(); + else + aBoundRect2 = rCon2.m_pSdrObj->GetCurrentBoundRect(); + } + + aBoundRect2.Move(rCon2.m_aObjOfs.X(),rCon2.m_aObjOfs.Y()); + aBewareRect2=aBoundRect2; + sal_Int32 nH = rSet.Get(SDRATTR_EDGENODE2HORZDIST).GetValue(); + sal_Int32 nV = rSet.Get(SDRATTR_EDGENODE2VERTDIST).GetValue(); + aBewareRect2.AdjustLeft( -nH ); + aBewareRect2.AdjustRight(nH ); + aBewareRect2.AdjustTop( -nV ); + aBewareRect2.AdjustBottom(nV ); + } + else + { + aBoundRect2=tools::Rectangle(aPt2,aPt2); + aBoundRect2.Move(rCon2.m_aObjOfs.X(),rCon2.m_aObjOfs.Y()); + aBewareRect2=aBoundRect2; + } + + XPolygon aBestXP; + sal_uIntPtr nBestQual=0xFFFFFFFF; + SdrEdgeInfoRec aBestInfo; + bool bAuto1=bCon1 && rCon1.m_bBestVertex; + bool bAuto2=bCon2 && rCon2.m_bBestVertex; + if (bAuto1) rCon1.m_bAutoVertex=true; + if (bAuto2) rCon2.m_bAutoVertex=true; + sal_uInt16 nBestAuto1=0; + sal_uInt16 nBestAuto2=0; + sal_uInt16 nCount1=bAuto1 ? 4 : 1; + sal_uInt16 nCount2=bAuto2 ? 4 : 1; + + for (sal_uInt16 nNum1=0; nNum1<nCount1; nNum1++) + { + if (bAuto1) rCon1.m_nConId=nNum1; + if (bCon1 && rCon1.TakeGluePoint(aGP1)) + { + aPt1=aGP1.GetPos(); + nEsc1=aGP1.GetEscDir(); + if (nEsc1==SdrEscapeDirection::SMART) nEsc1=ImpCalcEscAngle(rCon1.m_pSdrObj,aPt1-rCon1.m_aObjOfs); + } + for (sal_uInt16 nNum2=0; nNum2<nCount2; nNum2++) + { + if (bAuto2) rCon2.m_nConId=nNum2; + if (bCon2 && rCon2.TakeGluePoint(aGP2)) + { + aPt2=aGP2.GetPos(); + nEsc2=aGP2.GetEscDir(); + if (nEsc2==SdrEscapeDirection::SMART) nEsc2=ImpCalcEscAngle(rCon2.m_pSdrObj,aPt2-rCon2.m_aObjOfs); + } + for (tools::Long nA1=0; nA1<36000; nA1+=9000) + { + SdrEscapeDirection nE1 = nA1==0 ? SdrEscapeDirection::RIGHT : nA1==9000 ? SdrEscapeDirection::TOP : nA1==18000 ? SdrEscapeDirection::LEFT : nA1==27000 ? SdrEscapeDirection::BOTTOM : SdrEscapeDirection::SMART; + for (tools::Long nA2=0; nA2<36000; nA2+=9000) + { + SdrEscapeDirection nE2 = nA2==0 ? SdrEscapeDirection::RIGHT : nA2==9000 ? SdrEscapeDirection::TOP : nA2==18000 ? SdrEscapeDirection::LEFT : nA2==27000 ? SdrEscapeDirection::BOTTOM : SdrEscapeDirection::SMART; + if ((nEsc1&nE1) && (nEsc2&nE2)) + { + sal_uIntPtr nQual=0; + SdrEdgeInfoRec aInfo; + if (pInfo!=nullptr) aInfo=*pInfo; + XPolygon aXP(ImpCalcEdgeTrack(aPt1,nA1,aBoundRect1,aBewareRect1,aPt2,nA2,aBoundRect2,aBewareRect2,&nQual,&aInfo)); + if (nQual<nBestQual) + { + aBestXP=std::move(aXP); + nBestQual=nQual; + aBestInfo=aInfo; + nBestAuto1=nNum1; + nBestAuto2=nNum2; + } + } + } + } + } + } + if (bAuto1) rCon1.m_nConId=nBestAuto1; + if (bAuto2) rCon2.m_nConId=nBestAuto2; + if (pInfo!=nullptr) *pInfo=aBestInfo; + return aBestXP; +} + +XPolygon SdrEdgeObj::ImpCalcEdgeTrack(const Point& rPt1, tools::Long nAngle1, const tools::Rectangle& rBoundRect1, const tools::Rectangle& rBewareRect1, + const Point& rPt2, tools::Long nAngle2, const tools::Rectangle& rBoundRect2, const tools::Rectangle& rBewareRect2, + sal_uIntPtr* pnQuality, SdrEdgeInfoRec* pInfo) const +{ + SdrEdgeKind eKind=GetObjectItem(SDRATTR_EDGEKIND).GetValue(); + bool bRts1=nAngle1==0; + bool bObn1=nAngle1==9000; + bool bLks1=nAngle1==18000; + bool bUnt1=nAngle1==27000; + bool bHor1=bLks1 || bRts1; + bool bVer1=bObn1 || bUnt1; + bool bRts2=nAngle2==0; + bool bObn2=nAngle2==9000; + bool bLks2=nAngle2==18000; + bool bUnt2=nAngle2==27000; + bool bHor2=bLks2 || bRts2; + bool bVer2=bObn2 || bUnt2; + if (pInfo) { + pInfo->m_nAngle1=nAngle1; + pInfo->m_nAngle2=nAngle2; + pInfo->m_nObj1Lines=1; + pInfo->m_nObj2Lines=1; + pInfo->m_nMiddleLine=0xFFFF; + } + Point aPt1(rPt1); + Point aPt2(rPt2); + tools::Rectangle aBoundRect1 (rBoundRect1 ); + tools::Rectangle aBoundRect2 (rBoundRect2 ); + tools::Rectangle aBewareRect1(rBewareRect1); + tools::Rectangle aBewareRect2(rBewareRect2); + Point aMeeting((aPt1.X()+aPt2.X()+1)/2,(aPt1.Y()+aPt2.Y()+1)/2); + if (eKind==SdrEdgeKind::OneLine) { + XPolygon aXP(2); + aXP[0]=rPt1; + aXP[1]=rPt2; + if (pnQuality!=nullptr) { + *pnQuality=std::abs(rPt1.X()-rPt2.X())+std::abs(rPt1.Y()-rPt2.Y()); + } + return aXP; + } else if (eKind==SdrEdgeKind::ThreeLines) { + XPolygon aXP(4); + aXP[0]=rPt1; + aXP[1]=rPt1; + aXP[2]=rPt2; + aXP[3]=rPt2; + if (bRts1) aXP[1].setX(aBewareRect1.Right() ); //+=500; + if (bObn1) aXP[1].setY(aBewareRect1.Top() ); //-=500; + if (bLks1) aXP[1].setX(aBewareRect1.Left() ); //-=500; + if (bUnt1) aXP[1].setY(aBewareRect1.Bottom() ); //+=500; + if (bRts2) aXP[2].setX(aBewareRect2.Right() ); //+=500; + if (bObn2) aXP[2].setY(aBewareRect2.Top() ); //-=500; + if (bLks2) aXP[2].setX(aBewareRect2.Left() ); //-=500; + if (bUnt2) aXP[2].setY(aBewareRect2.Bottom() ); //+=500; + if (pnQuality!=nullptr) { + tools::Long nQ=std::abs(aXP[1].X()-aXP[0].X())+std::abs(aXP[1].Y()-aXP[0].Y()); + nQ+=std::abs(aXP[2].X()-aXP[1].X())+std::abs(aXP[2].Y()-aXP[1].Y()); + nQ+=std::abs(aXP[3].X()-aXP[2].X())+std::abs(aXP[3].Y()-aXP[2].Y()); + *pnQuality=nQ; + } + if (pInfo) { + pInfo->m_nObj1Lines=2; + pInfo->m_nObj2Lines=2; + if (bHor1) { + aXP[1].AdjustX(pInfo->m_aObj1Line2.X() ); + } else { + aXP[1].AdjustY(pInfo->m_aObj1Line2.Y() ); + } + if (bHor2) { + aXP[2].AdjustX(pInfo->m_aObj2Line2.X() ); + } else { + aXP[2].AdjustY(pInfo->m_aObj2Line2.Y() ); + } + } + return aXP; + } + sal_uInt16 nIntersections=0; + { + Point aC1(aBewareRect1.Center()); + Point aC2(aBewareRect2.Center()); + if (aBewareRect1.Left()<=aBewareRect2.Right() && aBewareRect1.Right()>=aBewareRect2.Left()) { + // overlapping on the x axis + tools::Long n1=std::max(aBewareRect1.Left(),aBewareRect2.Left()); + tools::Long n2=std::min(aBewareRect1.Right(),aBewareRect2.Right()); + aMeeting.setX((n1+n2+1)/2 ); + } else { + // otherwise the center point of the empty space + if (aC1.X()<aC2.X()) { + aMeeting.setX((aBewareRect1.Right()+aBewareRect2.Left()+1)/2 ); + } else { + aMeeting.setX((aBewareRect1.Left()+aBewareRect2.Right()+1)/2 ); + } + } + if (aBewareRect1.Top()<=aBewareRect2.Bottom() && aBewareRect1.Bottom()>=aBewareRect2.Top()) { + // overlapping on the x axis + tools::Long n1=std::max(aBewareRect1.Top(),aBewareRect2.Top()); + tools::Long n2=std::min(aBewareRect1.Bottom(),aBewareRect2.Bottom()); + aMeeting.setY((n1+n2+1)/2 ); + } else { + // otherwise the center point of the empty space + if (aC1.Y()<aC2.Y()) { + aMeeting.setY((aBewareRect1.Bottom()+aBewareRect2.Top()+1)/2 ); + } else { + aMeeting.setY((aBewareRect1.Top()+aBewareRect2.Bottom()+1)/2 ); + } + } + // Here, there are three cases: + // 1. both go into the same direction + // 2. both go into opposite directions + // 3. one is vertical, the other is horizontal + tools::Long nXMin=std::min(aBewareRect1.Left(),aBewareRect2.Left()); + tools::Long nXMax=std::max(aBewareRect1.Right(),aBewareRect2.Right()); + tools::Long nYMin=std::min(aBewareRect1.Top(),aBewareRect2.Top()); + tools::Long nYMax=std::max(aBewareRect1.Bottom(),aBewareRect2.Bottom()); + bool bBewareOverlap=aBewareRect1.Right()>aBewareRect2.Left() && aBewareRect1.Left()<aBewareRect2.Right() && + aBewareRect1.Bottom()>aBewareRect2.Top() && aBewareRect1.Top()<aBewareRect2.Bottom(); + unsigned nMainCase=3; + if (nAngle1==nAngle2) nMainCase=1; + else if ((bHor1 && bHor2) || (bVer1 && bVer2)) nMainCase=2; + if (nMainCase==1) { // case 1 (both go in one direction) is possible + if (bVer1) aMeeting.setX((aPt1.X()+aPt2.X()+1)/2 ); // Here, this is better than + if (bHor1) aMeeting.setY((aPt1.Y()+aPt2.Y()+1)/2 ); // using center point of empty space + // bX1Ok means that the vertical exiting Obj1 doesn't conflict with Obj2, ... + bool bX1Ok=aPt1.X()<=aBewareRect2.Left() || aPt1.X()>=aBewareRect2.Right(); + bool bX2Ok=aPt2.X()<=aBewareRect1.Left() || aPt2.X()>=aBewareRect1.Right(); + bool bY1Ok=aPt1.Y()<=aBewareRect2.Top() || aPt1.Y()>=aBewareRect2.Bottom(); + bool bY2Ok=aPt2.Y()<=aBewareRect1.Top() || aPt2.Y()>=aBewareRect1.Bottom(); + if (bLks1 && (bY1Ok || aBewareRect1.Left()<aBewareRect2.Right()) && (bY2Ok || aBewareRect2.Left()<aBewareRect1.Right())) { + aMeeting.setX(nXMin ); + } + if (bRts1 && (bY1Ok || aBewareRect1.Right()>aBewareRect2.Left()) && (bY2Ok || aBewareRect2.Right()>aBewareRect1.Left())) { + aMeeting.setX(nXMax ); + } + if (bObn1 && (bX1Ok || aBewareRect1.Top()<aBewareRect2.Bottom()) && (bX2Ok || aBewareRect2.Top()<aBewareRect1.Bottom())) { + aMeeting.setY(nYMin ); + } + if (bUnt1 && (bX1Ok || aBewareRect1.Bottom()>aBewareRect2.Top()) && (bX2Ok || aBewareRect2.Bottom()>aBewareRect1.Top())) { + aMeeting.setY(nYMax ); + } + } else if (nMainCase==2) { + // case 2: + if (bHor1) { // both horizontal + /* 9 sub-cases: + (legend: line exits to the left (-|), right (|-)) + + 2.1: Facing; overlap only on y axis + * * * + |--| * + * * * + + 2.2, 2.3: Facing, offset vertically; no overlap on either + axis + |- * * * * * + * -| * * -| * + * * * , * * * + + 2.4, 2.5: One below the other; overlap only on y axis + * |- * * * * + * -| * * -| * + * * * , * |- * + + 2.6, 2.7: Not facing, offset vertically; no overlap on either + axis + * * |- * * * + * -| * * -| * + * * * , * * |- + + 2.8: Not facing; overlap only on y axis + * * * + * -| |- + * * * + + 2.9: The objects's BewareRects overlap on x and y axis + + These cases, with some modifications are also valid for + horizontal line exits. + Cases 2.1 through 2.7 are covered well enough with the + default meetings. Only for cases 2.8 and 2.9 do we determine + special meeting points here. + */ + + // normalization; be aR1 the one exiting to the right, + // be aR2 the one exiting to the left + tools::Rectangle aBewR1(bRts1 ? aBewareRect1 : aBewareRect2); + tools::Rectangle aBewR2(bRts1 ? aBewareRect2 : aBewareRect1); + tools::Rectangle aBndR1(bRts1 ? aBoundRect1 : aBoundRect2); + tools::Rectangle aBndR2(bRts1 ? aBoundRect2 : aBoundRect1); + if (aBewR1.Bottom()>aBewR2.Top() && aBewR1.Top()<aBewR2.Bottom()) { + // overlap on y axis; cases 2.1, 2.8, 2.9 + if (aBewR1.Right()>aBewR2.Left()) { + /* Cases 2.8, 2.9: + Case 2.8: always going around on the outside + (bDirect=false). + + Case 2.9 could also be a direct connection (in the + case that the BewareRects overlap only slightly and + the BoundRects don't overlap at all and if the + line exits would otherwise violate the respective + other object's BewareRect). + */ + bool bCase29Direct = false; + bool bCase29=aBewR1.Right()>aBewR2.Left(); + if (aBndR1.Right()<=aBndR2.Left()) { // case 2.9 without BoundRect overlap + if ((aPt1.Y()>aBewareRect2.Top() && aPt1.Y()<aBewareRect2.Bottom()) || + (aPt2.Y()>aBewareRect1.Top() && aPt2.Y()<aBewareRect1.Bottom())) { + bCase29Direct = true; + } + } + if (!bCase29Direct) { + bool bObenLang=std::abs(nYMin-aMeeting.Y())<=std::abs(nYMax-aMeeting.Y()); + if (bObenLang) { + aMeeting.setY(nYMin ); + } else { + aMeeting.setY(nYMax ); + } + if (bCase29) { + // now make sure that the surrounded object + // isn't traversed + if ((aBewR1.Center().Y()<aBewR2.Center().Y()) != bObenLang) { + aMeeting.setX(aBewR2.Right() ); + } else { + aMeeting.setX(aBewR1.Left() ); + } + } + } else { + // We need a direct connection (3-line Z connection), + // because we have to violate the BewareRects. + // Use rule of three to scale down the BewareRects. + tools::Long nWant1=aBewR1.Right()-aBndR1.Right(); // distance at Obj1 + tools::Long nWant2=aBndR2.Left()-aBewR2.Left(); // distance at Obj2 + tools::Long nSpace=aBndR2.Left()-aBndR1.Right(); // available space + tools::Long nGet1=BigMulDiv(nWant1,nSpace,nWant1+nWant2); + tools::Long nGet2=nSpace-nGet1; + if (bRts1) { // revert normalization + aBewareRect1.AdjustRight(nGet1-nWant1 ); + aBewareRect2.AdjustLeft( -(nGet2-nWant2) ); + } else { + aBewareRect2.AdjustRight(nGet1-nWant1 ); + aBewareRect1.AdjustLeft( -(nGet2-nWant2) ); + } + nIntersections++; // lower quality + } + } + } + } else if (bVer1) { // both horizontal + tools::Rectangle aBewR1(bUnt1 ? aBewareRect1 : aBewareRect2); + tools::Rectangle aBewR2(bUnt1 ? aBewareRect2 : aBewareRect1); + tools::Rectangle aBndR1(bUnt1 ? aBoundRect1 : aBoundRect2); + tools::Rectangle aBndR2(bUnt1 ? aBoundRect2 : aBoundRect1); + if (aBewR1.Right()>aBewR2.Left() && aBewR1.Left()<aBewR2.Right()) { + // overlap on y axis; cases 2.1, 2.8, 2.9 + if (aBewR1.Bottom()>aBewR2.Top()) { + /* Cases 2.8, 2.9 + Case 2.8 always going around on the outside (bDirect=false). + + Case 2.9 could also be a direct connection (in the + case that the BewareRects overlap only slightly and + the BoundRects don't overlap at all and if the + line exits would otherwise violate the respective + other object's BewareRect). + */ + bool bCase29Direct = false; + bool bCase29=aBewR1.Bottom()>aBewR2.Top(); + if (aBndR1.Bottom()<=aBndR2.Top()) { // case 2.9 without BoundRect overlap + if ((aPt1.X()>aBewareRect2.Left() && aPt1.X()<aBewareRect2.Right()) || + (aPt2.X()>aBewareRect1.Left() && aPt2.X()<aBewareRect1.Right())) { + bCase29Direct = true; + } + } + if (!bCase29Direct) { + bool bLinksLang=std::abs(nXMin-aMeeting.X())<=std::abs(nXMax-aMeeting.X()); + if (bLinksLang) { + aMeeting.setX(nXMin ); + } else { + aMeeting.setX(nXMax ); + } + if (bCase29) { + // now make sure that the surrounded object + // isn't traversed + if ((aBewR1.Center().X()<aBewR2.Center().X()) != bLinksLang) { + aMeeting.setY(aBewR2.Bottom() ); + } else { + aMeeting.setY(aBewR1.Top() ); + } + } + } else { + // We need a direct connection (3-line Z connection), + // because we have to violate the BewareRects. + // Use rule of three to scale down the BewareRects. + tools::Long nWant1=aBewR1.Bottom()-aBndR1.Bottom(); // difference at Obj1 + tools::Long nWant2=aBndR2.Top()-aBewR2.Top(); // difference at Obj2 + tools::Long nSpace=aBndR2.Top()-aBndR1.Bottom(); // available space + tools::Long nGet1=BigMulDiv(nWant1,nSpace,nWant1+nWant2); + tools::Long nGet2=nSpace-nGet1; + if (bUnt1) { // revert normalization + aBewareRect1.AdjustBottom(nGet1-nWant1 ); + aBewareRect2.AdjustTop( -(nGet2-nWant2) ); + } else { + aBewareRect2.AdjustBottom(nGet1-nWant1 ); + aBewareRect1.AdjustTop( -(nGet2-nWant2) ); + } + nIntersections++; // lower quality + } + } + } + } + } else if (nMainCase==3) { // case 3: one horizontal, the other vertical + /* legend: + The line exits to the: + -| left + + |- right + + _|_ top + + T bottom + + * . * . * -- no overlap, at most might touch + . . . . . -- overlap + * . |- . * -- same height + . . . . . -- overlap + * . * . * -- no overlap, at most might touch + + Overall, there are 96 possible constellations, some of these can't even + be unambiguously assigned to a certain case/method of handling. + + + 3.1: All those constellations that are covered reasonably well + by the default MeetingPoint (20+12). + + T T T . _|_ _|_ . T T T these 12 * . * T * * . * . * * T * . * * . * . * + . . . . _|_ _|_ . . . . constellations . . . . . . . . . T . . . . . T . . . . + * . |- . * * . -| . * are covered * . |- . _|_ * . |- . T _|_ . -| . * T . -| . * + . . . . T T . . . . only in . . . . _|_ . . . . . _|_ . . . . . . . . . + _|__|__|_ . T T . _|__|__|_ part: * . * _|_ * * . * . * * _|_ * . * * . * . * + + The last 16 of these cases can be excluded, if the objects face each other openly. + + + 3.2: The objects face each other openly, thus a connection using only two lines is possible (4+20); + This case is priority #1. + * . * . T T . * . * these 20 * . * T * * T * . * * . * . * * . * . * + . . . . . . . . . . constellations . . . T T T T . . . . . . . . . . . . . + * . |- . * * . -| . * are covered * . |-_|__|_ _|__|_-| . * * . |- T T T T -| . * + . . . . . . . . . . only in . . . _|__|_ _|__|_ . . . . . . . . . . . . . + * . * . _|_ _|_ . * . * part: * . * _|_ * * _|_ * . * * . * . * * . * . * + + 3.3: The line exits point away from the other object or miss its back (52+4). + _|__|__|__|_ * * _|__|__|__|_ * . . . * * . * . * these 4 * . * . * * . * . * + _|__|__|__|_ . . _|__|__|__|_ T T T . . . . T T T constellations . . . T . . T . . . + _|__|_ |- . * * . -| _|__|_ T T |- . * * . -| T T are covered * . |- . * * . -| . * + _|__|__|_ . . . . _|__|__|_ T T T T . . T T T T only in . . . _|_ . . _|_ . . . + * . * . * * . * . * T T T T * * T T T T part: * . * . * * . * . * + */ + + // case 3.2 + tools::Rectangle aTmpR1(aBewareRect1); + tools::Rectangle aTmpR2(aBewareRect2); + if (bBewareOverlap) { + // overlapping BewareRects: use BoundRects for checking for case 3.2 + aTmpR1=aBoundRect1; + aTmpR2=aBoundRect2; + } + if ((((bRts1 && aTmpR1.Right ()<=aPt2.X()) || (bLks1 && aTmpR1.Left()>=aPt2.X())) && + ((bUnt2 && aTmpR2.Bottom()<=aPt1.Y()) || (bObn2 && aTmpR2.Top ()>=aPt1.Y()))) || + (((bRts2 && aTmpR2.Right ()<=aPt1.X()) || (bLks2 && aTmpR2.Left()>=aPt1.X())) && + ((bUnt1 && aTmpR1.Bottom()<=aPt2.Y()) || (bObn1 && aTmpR1.Top ()>=aPt2.Y())))) { + // case 3.2 applies: connector with only 2 lines + if (bHor1) { + aMeeting.setX(aPt2.X() ); + aMeeting.setY(aPt1.Y() ); + } else { + aMeeting.setX(aPt1.X() ); + aMeeting.setY(aPt2.Y() ); + } + // in the case of overlapping BewareRects: + aBewareRect1=aTmpR1; + aBewareRect2=aTmpR2; + } else if ((((bRts1 && aBewareRect1.Right ()>aBewareRect2.Left ()) || + (bLks1 && aBewareRect1.Left ()<aBewareRect2.Right ())) && + ((bUnt2 && aBewareRect2.Bottom()>aBewareRect1.Top ()) || + (bObn2 && aBewareRect2.Top ()<aBewareRect1.Bottom()))) || + (((bRts2 && aBewareRect2.Right ()>aBewareRect1.Left ()) || + (bLks2 && aBewareRect2.Left ()<aBewareRect1.Right ())) && + ((bUnt1 && aBewareRect1.Bottom()>aBewareRect2.Top ()) || + (bObn1 && aBewareRect1.Top ()<aBewareRect2.Bottom())))) { + // case 3.3 + if (bRts1 || bRts2) { aMeeting.setX(nXMax ); } + if (bLks1 || bLks2) { aMeeting.setX(nXMin ); } + if (bUnt1 || bUnt2) { aMeeting.setY(nYMax ); } + if (bObn1 || bObn2) { aMeeting.setY(nYMin ); } + } + } + } + + XPolygon aXP1(ImpCalcObjToCenter(aPt1,nAngle1,aBewareRect1,aMeeting)); + XPolygon aXP2(ImpCalcObjToCenter(aPt2,nAngle2,aBewareRect2,aMeeting)); + sal_uInt16 nXP1Cnt=aXP1.GetPointCount(); + sal_uInt16 nXP2Cnt=aXP2.GetPointCount(); + if (pInfo) { + pInfo->m_nObj1Lines=nXP1Cnt; if (nXP1Cnt>1) pInfo->m_nObj1Lines--; + pInfo->m_nObj2Lines=nXP2Cnt; if (nXP2Cnt>1) pInfo->m_nObj2Lines--; + } + Point aEP1(aXP1[nXP1Cnt-1]); + Point aEP2(aXP2[nXP2Cnt-1]); + bool bInsMeetingPoint=aEP1.X()!=aEP2.X() && aEP1.Y()!=aEP2.Y(); + bool bHorzE1=aEP1.Y()==aXP1[nXP1Cnt-2].Y(); // is last line of XP1 horizontal? + bool bHorzE2=aEP2.Y()==aXP2[nXP2Cnt-2].Y(); // is last line of XP2 horizontal? + if (aEP1==aEP2 && ((bHorzE1 && bHorzE2 && aEP1.Y()==aEP2.Y()) || (!bHorzE1 && !bHorzE2 && aEP1.X()==aEP2.X()))) { + // special casing 'I' connectors + nXP1Cnt--; aXP1.Remove(nXP1Cnt,1); + nXP2Cnt--; aXP2.Remove(nXP2Cnt,1); + } + if (bInsMeetingPoint) { + aXP1.Insert(XPOLY_APPEND,aMeeting,PolyFlags::Normal); + if (pInfo) { + // Inserting a MeetingPoint adds 2 new lines, + // either might become the center line. + if (pInfo->m_nObj1Lines==pInfo->m_nObj2Lines) { + pInfo->m_nObj1Lines++; + pInfo->m_nObj2Lines++; + } else { + if (pInfo->m_nObj1Lines>pInfo->m_nObj2Lines) { + pInfo->m_nObj2Lines++; + pInfo->m_nMiddleLine=nXP1Cnt-1; + } else { + pInfo->m_nObj1Lines++; + pInfo->m_nMiddleLine=nXP1Cnt; + } + } + } + } else if (pInfo && aEP1!=aEP2 && nXP1Cnt+nXP2Cnt>=4) { + // By connecting both ends, another line is added, this becomes the center line. + pInfo->m_nMiddleLine=nXP1Cnt-1; + } + sal_uInt16 nNum=aXP2.GetPointCount(); + if (aXP1[nXP1Cnt-1]==aXP2[nXP2Cnt-1] && nXP1Cnt>1 && nXP2Cnt>1) nNum--; + while (nNum>0) { + nNum--; + aXP1.Insert(XPOLY_APPEND,aXP2[nNum],PolyFlags::Normal); + } + sal_uInt16 nPointCount=aXP1.GetPointCount(); + char cForm; + if (pInfo || pnQuality!=nullptr) { + if (nPointCount==2) cForm='I'; + else if (nPointCount==3) cForm='L'; + else if (nPointCount==4) { // Z or U + if (nAngle1==nAngle2) cForm='U'; + else cForm='Z'; + } else if (nPointCount==6) { // S or C or ... + if (nAngle1!=nAngle2) { + // For type S, line 2 has the same direction as line 4. + // For type C, the opposite is true. + Point aP1(aXP1[1]); + Point aP2(aXP1[2]); + Point aP3(aXP1[3]); + Point aP4(aXP1[4]); + if (aP1.Y()==aP2.Y()) { // else both lines are horizontal + if ((aP1.X()<aP2.X())==(aP3.X()<aP4.X())) cForm='S'; + else cForm='C'; + } else { // else both lines are vertical + if ((aP1.Y()<aP2.Y())==(aP3.Y()<aP4.Y())) cForm='S'; + else cForm='C'; + } + } else cForm='4'; // else is case 3 with 5 lines + } else cForm='?'; + // more shapes: + if (pInfo) { + if (cForm=='I' || cForm=='L' || cForm=='Z' || cForm=='U') { + pInfo->m_nObj1Lines=1; + pInfo->m_nObj2Lines=1; + if (cForm=='Z' || cForm=='U') { + pInfo->m_nMiddleLine=1; + } else { + pInfo->m_nMiddleLine=0xFFFF; + } + } else if (cForm=='S' || cForm=='C') { + pInfo->m_nObj1Lines=2; + pInfo->m_nObj2Lines=2; + pInfo->m_nMiddleLine=2; + } + } + } + else + { + cForm = 0; + } + if (pnQuality!=nullptr) { + sal_uIntPtr nQual=0; + sal_uIntPtr nQual0=nQual; // prevent overruns + bool bOverflow = false; + Point aPt0(aXP1[0]); + for (sal_uInt16 nPntNum=1; nPntNum<nPointCount; nPntNum++) { + Point aPt1b(aXP1[nPntNum]); + nQual+=std::abs(aPt1b.X()-aPt0.X())+std::abs(aPt1b.Y()-aPt0.Y()); + if (nQual<nQual0) bOverflow = true; + nQual0=nQual; + aPt0=aPt1b; + } + + sal_uInt16 nTmp=nPointCount; + if (cForm=='Z') { + nTmp=2; // Z shape with good quality (nTmp=2 instead of 4) + sal_uIntPtr n1=std::abs(aXP1[1].X()-aXP1[0].X())+std::abs(aXP1[1].Y()-aXP1[0].Y()); + sal_uIntPtr n2=std::abs(aXP1[2].X()-aXP1[1].X())+std::abs(aXP1[2].Y()-aXP1[1].Y()); + sal_uIntPtr n3=std::abs(aXP1[3].X()-aXP1[2].X())+std::abs(aXP1[3].Y()-aXP1[2].Y()); + // try to make lines lengths similar + sal_uIntPtr nBesser=0; + n1+=n3; + n3=n2/4; + if (n1>=n2) nBesser=6; + else if (n1>=3*n3) nBesser=4; + else if (n1>=2*n3) nBesser=2; + if (aXP1[0].Y()!=aXP1[1].Y()) nBesser++; // vertical starting line gets a plus (for H/V-Prio) + if (nQual>nBesser) nQual-=nBesser; else nQual=0; + } + if (nTmp>=3) { + nQual0=nQual; + nQual+=static_cast<sal_uIntPtr>(nTmp)*0x01000000; + if (nQual<nQual0 || nTmp>15) bOverflow = true; + } + if (nPointCount>=2) { // check exit angle again + Point aP1(aXP1[1]); aP1-=aXP1[0]; + Point aP2(aXP1[nPointCount-2]); aP2-=aXP1[nPointCount-1]; + tools::Long nAng1=0; if (aP1.X()<0) nAng1=18000; if (aP1.Y()>0) nAng1=27000; + if (aP1.Y()<0) nAng1=9000; + if (aP1.X()!=0 && aP1.Y()!=0) nAng1=1; // slant?! + tools::Long nAng2=0; if (aP2.X()<0) nAng2=18000; if (aP2.Y()>0) nAng2=27000; + if (aP2.Y()<0) nAng2=9000; + if (aP2.X()!=0 && aP2.Y()!=0) nAng2=1; // slant?! + if (nAng1!=nAngle1) nIntersections++; + if (nAng2!=nAngle2) nIntersections++; + } + + // For the quality check, use the original Rects and at the same time + // check whether one them was scaled down for the calculation of the + // Edges (e. g. case 2.9) + aBewareRect1=rBewareRect1; + aBewareRect2=rBewareRect2; + + for (sal_uInt16 i=0; i<nPointCount; i++) { + Point aPt1b(aXP1[i]); + bool b1=aPt1b.X()>aBewareRect1.Left() && aPt1b.X()<aBewareRect1.Right() && + aPt1b.Y()>aBewareRect1.Top() && aPt1b.Y()<aBewareRect1.Bottom(); + bool b2=aPt1b.X()>aBewareRect2.Left() && aPt1b.X()<aBewareRect2.Right() && + aPt1b.Y()>aBewareRect2.Top() && aPt1b.Y()<aBewareRect2.Bottom(); + sal_uInt16 nInt0=nIntersections; + if (i==0 || i==nPointCount-1) { + if (b1 && b2) nIntersections++; + } else { + if (b1) nIntersections++; + if (b2) nIntersections++; + } + // check for overlaps + if (i>0 && nInt0==nIntersections) { + if (aPt0.Y()==aPt1b.Y()) { // horizontal line + if (aPt0.Y()>aBewareRect1.Top() && aPt0.Y()<aBewareRect1.Bottom() && + ((aPt0.X()<=aBewareRect1.Left() && aPt1b.X()>=aBewareRect1.Right()) || + (aPt1b.X()<=aBewareRect1.Left() && aPt0.X()>=aBewareRect1.Right()))) nIntersections++; + if (aPt0.Y()>aBewareRect2.Top() && aPt0.Y()<aBewareRect2.Bottom() && + ((aPt0.X()<=aBewareRect2.Left() && aPt1b.X()>=aBewareRect2.Right()) || + (aPt1b.X()<=aBewareRect2.Left() && aPt0.X()>=aBewareRect2.Right()))) nIntersections++; + } else { // vertical line + if (aPt0.X()>aBewareRect1.Left() && aPt0.X()<aBewareRect1.Right() && + ((aPt0.Y()<=aBewareRect1.Top() && aPt1b.Y()>=aBewareRect1.Bottom()) || + (aPt1b.Y()<=aBewareRect1.Top() && aPt0.Y()>=aBewareRect1.Bottom()))) nIntersections++; + if (aPt0.X()>aBewareRect2.Left() && aPt0.X()<aBewareRect2.Right() && + ((aPt0.Y()<=aBewareRect2.Top() && aPt1b.Y()>=aBewareRect2.Bottom()) || + (aPt1b.Y()<=aBewareRect2.Top() && aPt0.Y()>=aBewareRect2.Bottom()))) nIntersections++; + } + } + aPt0=aPt1b; + } + if (nPointCount<=1) nIntersections++; + nQual0=nQual; + nQual+=static_cast<sal_uIntPtr>(nIntersections)*0x10000000; + if (nQual<nQual0 || nIntersections>15) bOverflow = true; + + if (bOverflow || nQual==0xFFFFFFFF) nQual=0xFFFFFFFE; + *pnQuality=nQual; + } + if (pInfo) { // now apply line offsets to aXP1 + if (pInfo->m_nMiddleLine!=0xFFFF) { + sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::MiddleLine,aXP1); + if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::MiddleLine,aXP1)) { + aXP1[nIdx].AdjustY(pInfo->m_aMiddleLine.Y() ); + aXP1[nIdx+1].AdjustY(pInfo->m_aMiddleLine.Y() ); + } else { + aXP1[nIdx].AdjustX(pInfo->m_aMiddleLine.X() ); + aXP1[nIdx+1].AdjustX(pInfo->m_aMiddleLine.X() ); + } + } + if (pInfo->m_nObj1Lines>=2) { + sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj1Line2,aXP1); + if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj1Line2,aXP1)) { + aXP1[nIdx].AdjustY(pInfo->m_aObj1Line2.Y() ); + aXP1[nIdx+1].AdjustY(pInfo->m_aObj1Line2.Y() ); + } else { + aXP1[nIdx].AdjustX(pInfo->m_aObj1Line2.X() ); + aXP1[nIdx+1].AdjustX(pInfo->m_aObj1Line2.X() ); + } + } + if (pInfo->m_nObj1Lines>=3) { + sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj1Line3,aXP1); + if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj1Line3,aXP1)) { + aXP1[nIdx].AdjustY(pInfo->m_aObj1Line3.Y() ); + aXP1[nIdx+1].AdjustY(pInfo->m_aObj1Line3.Y() ); + } else { + aXP1[nIdx].AdjustX(pInfo->m_aObj1Line3.X() ); + aXP1[nIdx+1].AdjustX(pInfo->m_aObj1Line3.X() ); + } + } + if (pInfo->m_nObj2Lines>=2) { + sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj2Line2,aXP1); + if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj2Line2,aXP1)) { + aXP1[nIdx].AdjustY(pInfo->m_aObj2Line2.Y() ); + aXP1[nIdx+1].AdjustY(pInfo->m_aObj2Line2.Y() ); + } else { + aXP1[nIdx].AdjustX(pInfo->m_aObj2Line2.X() ); + aXP1[nIdx+1].AdjustX(pInfo->m_aObj2Line2.X() ); + } + } + if (pInfo->m_nObj2Lines>=3) { + sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj2Line3,aXP1); + if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj2Line3,aXP1)) { + aXP1[nIdx].AdjustY(pInfo->m_aObj2Line3.Y() ); + aXP1[nIdx+1].AdjustY(pInfo->m_aObj2Line3.Y() ); + } else { + aXP1[nIdx].AdjustX(pInfo->m_aObj2Line3.X() ); + aXP1[nIdx+1].AdjustX(pInfo->m_aObj2Line3.X() ); + } + } + } + // make the connector a bezier curve, if appropriate + if (eKind != SdrEdgeKind::Bezier || nPointCount <= 2) + return aXP1; + + if (pInfo && pInfo->m_bUseOOXMLCurve) // Routing method OOXML + { + // The additional points needed are located on the segments of the path of the + // corresponding bentConnector as calculated above. + auto SegmentPoint = [&aXP1](const sal_uInt16& nEnd, const double& fFactor) { + return Point( + aXP1[nEnd - 1].X() + FRound(fFactor * (aXP1[nEnd].X() - aXP1[nEnd - 1].X())), + aXP1[nEnd - 1].Y() + FRound(fFactor * (aXP1[nEnd].Y() - aXP1[nEnd - 1].Y()))); + }; + + // We change the path going from end to start. Thus inserting points does not affect the index + // of the preceding points. + // The end point has index nPointCount-1 and is a normal point and kept. + // Insert new control point in the middle of last segments. + Point aControl = SegmentPoint(nPointCount - 1, 0.5); + // Insert happens before specified index. + aXP1.Insert(nPointCount - 1, aControl, PolyFlags::Control); + for (sal_uInt16 nSegment = nPointCount - 2; nSegment > 1; --nSegment) + { + // We need a normal point at center of segment and control points at 1/4 and 3/4 of + // segment. At center and 1/4 are new points, at 3/4 will be replacement for the end + // point of the segment. + aControl = SegmentPoint(nSegment, 0.25); + Point aNormal = SegmentPoint(nSegment, 0.5); + aXP1.SetFlags(nSegment, PolyFlags::Control); + aXP1[nSegment] = SegmentPoint(nSegment, 0.75); + aXP1.Insert(nSegment, aNormal, PolyFlags::Normal); + aXP1.Insert(nSegment, aControl, PolyFlags::Control); + } + // The first segments needs a control point in the middle. It is replacement for the + // second point. + aXP1.SetFlags(1, PolyFlags::Control); + aXP1[1] = SegmentPoint(1, 0.5); + } + else // Routing method LO + { + Point* pPt1=&aXP1[0]; + Point* pPt2=&aXP1[1]; + Point* pPt3=&aXP1[nPointCount-2]; + Point* pPt4=&aXP1[nPointCount-1]; + tools::Long dx1=pPt2->X()-pPt1->X(); + tools::Long dy1=pPt2->Y()-pPt1->Y(); + tools::Long dx2=pPt3->X()-pPt4->X(); + tools::Long dy2=pPt3->Y()-pPt4->Y(); + if (cForm=='L') { // nPointCount==3 + aXP1.SetFlags(1,PolyFlags::Control); + Point aPt3(*pPt2); + aXP1.Insert(2,aPt3,PolyFlags::Control); + nPointCount=aXP1.GetPointCount(); + pPt2=&aXP1[1]; + pPt3=&aXP1[nPointCount-2]; + pPt2->AdjustX( -(dx1/3) ); + pPt2->AdjustY( -(dy1/3) ); + pPt3->AdjustX( -(dx2/3) ); + pPt3->AdjustY( -(dy2/3) ); + } else if (nPointCount>=4 && nPointCount<=6) { // Z or U or ... + // To all others, the end points of the original lines become control + // points for now. Thus, we need to do some more work for nPointCount>4! + aXP1.SetFlags(1,PolyFlags::Control); + aXP1.SetFlags(nPointCount-2,PolyFlags::Control); + // distance x1.5 + pPt2->AdjustX(dx1/2 ); + pPt2->AdjustY(dy1/2 ); + pPt3->AdjustX(dx2/2 ); + pPt3->AdjustY(dy2/2 ); + if (nPointCount==5) { + // add a control point before and after center + Point aCenter(aXP1[2]); + tools::Long dx1b=aCenter.X()-aXP1[1].X(); + tools::Long dy1b=aCenter.Y()-aXP1[1].Y(); + tools::Long dx2b=aCenter.X()-aXP1[3].X(); + tools::Long dy2b=aCenter.Y()-aXP1[3].Y(); + aXP1.Insert(2,aCenter,PolyFlags::Control); + aXP1.SetFlags(3,PolyFlags::Symmetric); + aXP1.Insert(4,aCenter,PolyFlags::Control); + aXP1[2].AdjustX( -(dx1b/2) ); + aXP1[2].AdjustY( -(dy1b/2) ); + aXP1[3].AdjustX( -((dx1b+dx2b)/4) ); + aXP1[3].AdjustY( -((dy1b+dy2b)/4) ); + aXP1[4].AdjustX( -(dx2b/2) ); + aXP1[4].AdjustY( -(dy2b/2) ); + } + if (nPointCount==6) { + Point aPt1b(aXP1[2]); + Point aPt2b(aXP1[3]); + aXP1.Insert(2,aPt1b,PolyFlags::Control); + aXP1.Insert(5,aPt2b,PolyFlags::Control); + tools::Long dx=aPt1b.X()-aPt2b.X(); + tools::Long dy=aPt1b.Y()-aPt2b.Y(); + aXP1[3].AdjustX( -(dx/2) ); + aXP1[3].AdjustY( -(dy/2) ); + aXP1.SetFlags(3,PolyFlags::Symmetric); + aXP1.Remove(4,1); // because it's identical with aXP1[3] + } + } + } + return aXP1; +} + +/* +There could be a maximum of 64 different developments with 5 lines, a +maximum of 32 developments with 4 lines, a maximum of 16 developments with +3 lines, a maximum of 8 developments with 2 lines. +This gives us a total of 124 possibilities. +Normalized for the 1st exit angle to the right, there remain 31 possibilities. +Now, normalizing away the vertical mirroring, we get to a total of 16 +characteristic developments with 1 through 5 lines: + +1 line (type "I") -- + +2 lines (type "L") __| + +3 lines (type "U") __ (type "Z") _ + __| _| + _ _ +4 lines #1 _| #2 | | #3 |_ #4 | | + _| _| _| _| + Of these, #1 is implausible, #2 is a rotated version of #3. This leaves + #2 (from now on referred to as 4.1) and #4 (from now on referred to as 4.2). + _ _ +5 lines #1 _| #2 _| #3 ___ #4 _ + _| _| _| _| _| |_ + _ _ _ + #5 |_ #6 |_ #7 _| | #8 ____ + _| _| _| |_ _| + Of these, 5.1, 5.2, 5.4 and 5.5 are implausible, 5.7 is a reversed version + of 5.3. This leaves 5.3 (type "4"), 5.6 (type "S") and 5.8 (type "C"). + +We now have discerned the 9 basic types to cover all 400 possible constellations +of object positions and exit angles. 4 of the 9 types have got a center +line (CL). The number of object margins per object varies between 0 and 3: + + CL O1 O2 Note +"I": n 0 0 +"L": n 0 0 +"U": n 0-1 0-1 +"Z": y 0 0 +4.2: y 0 1 = U+1, respectively 1+U +4.4: n 0-2 0-2 = Z+1 +"4": y 0 2 = Z+2 +"S": y 1 1 = 1+Z+1 +"C": n 0-3 0-3 = 1+U+1 +*/ + +void SdrEdgeObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + const SfxHintId nId = rHint.GetId(); + bool bDataChg=nId==SfxHintId::DataChanged; + bool bDying=nId==SfxHintId::Dying; + bool bObj1=m_aCon1.m_pSdrObj!=nullptr && m_aCon1.m_pSdrObj->GetBroadcaster()==&rBC; + bool bObj2=m_aCon2.m_pSdrObj!=nullptr && m_aCon2.m_pSdrObj->GetBroadcaster()==&rBC; + if (bDying && (bObj1 || bObj2)) { + // catch Dying, so AttrObj doesn't start broadcasting + // about an alleged change of template + if (bObj1) m_aCon1.m_pSdrObj=nullptr; + if (bObj2) m_aCon2.m_pSdrObj=nullptr; + return; + } + if ( bObj1 || bObj2 ) + { + m_bEdgeTrackUserDefined = false; + } + SdrTextObj::Notify(rBC,rHint); + if (m_nNotifyingCount!=0)return; + +// a locking flag + m_nNotifyingCount++; + const SdrHint* pSdrHint = ( rHint.GetId() == SfxHintId::ThisIsAnSdrHint ? static_cast<const SdrHint*>(&rHint) : nullptr ); + + if (bDataChg) { // StyleSheet changed + ImpSetAttrToEdgeInfo(); // when changing templates, copy values from Pool to aEdgeInfo + } + if (bDataChg || + (bObj1 && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()) || + (bObj2 && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()) || + (pSdrHint && pSdrHint->GetKind()==SdrHintKind::ObjectRemoved)) + { + // broadcasting only, if on the same page + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetCurrentBoundRect(); + ImpDirtyEdgeTrack(); + + // only redraw here, object hasn't actually changed + ActionChanged(); + + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } + m_nNotifyingCount--; +} + +/** updates edges that are connected to the edges of this object + as if the connected objects sent a repaint broadcast +*/ +void SdrEdgeObj::Reformat() +{ + if( nullptr != m_aCon1.m_pSdrObj ) + { + SfxHint aHint( SfxHintId::DataChanged ); + Notify( *const_cast<SfxBroadcaster*>(m_aCon1.m_pSdrObj->GetBroadcaster()), aHint ); + } + + if( nullptr != m_aCon2.m_pSdrObj ) + { + SfxHint aHint( SfxHintId::DataChanged ); + Notify( *const_cast<SfxBroadcaster*>(m_aCon2.m_pSdrObj->GetBroadcaster()), aHint ); + } +} + +rtl::Reference<SdrObject> SdrEdgeObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrEdgeObj(rTargetModel, *this); +} + +OUString SdrEdgeObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulEDGE)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + return sName; +} + +OUString SdrEdgeObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralEDGE); +} + +basegfx::B2DPolyPolygon SdrEdgeObj::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aPolyPolygon; + + if (m_bEdgeTrackDirty) + { + const_cast<SdrEdgeObj*>(this)->ImpRecalcEdgeTrack(); + } + + if(m_pEdgeTrack) + { + aPolyPolygon.append(m_pEdgeTrack->getB2DPolygon()); + } + + return aPolyPolygon; +} + +void SdrEdgeObj::SetEdgeTrackPath( const basegfx::B2DPolyPolygon& rPoly ) +{ + if ( !rPoly.count() ) + { + m_bEdgeTrackDirty = true; + m_bEdgeTrackUserDefined = false; + } + else + { + *m_pEdgeTrack = XPolygon( rPoly.getB2DPolygon( 0 ) ); + m_bEdgeTrackDirty = false; + m_bEdgeTrackUserDefined = true; + + // #i110629# also set aRect and maSnapeRect depending on pEdgeTrack + const tools::Rectangle aPolygonBounds(m_pEdgeTrack->GetBoundRect()); + setRectangle(aPolygonBounds); + maSnapRect = aPolygonBounds; + } +} + +basegfx::B2DPolyPolygon SdrEdgeObj::GetEdgeTrackPath() const +{ + basegfx::B2DPolyPolygon aPolyPolygon; + + if (m_bEdgeTrackDirty) + const_cast<SdrEdgeObj*>(this)->ImpRecalcEdgeTrack(); + + aPolyPolygon.append( m_pEdgeTrack->getB2DPolygon() ); + + return aPolyPolygon; +} + +sal_uInt32 SdrEdgeObj::GetHdlCount() const +{ + SdrEdgeKind eKind=GetObjectItem(SDRATTR_EDGEKIND).GetValue(); + sal_uInt32 nHdlCnt(0); + sal_uInt32 nPointCount(m_pEdgeTrack->GetPointCount()); + + if(nPointCount) + { + nHdlCnt = 2; + if ((eKind==SdrEdgeKind::OrthoLines || eKind==SdrEdgeKind::Bezier) && nPointCount >= 4) + { + sal_uInt32 nO1(m_aEdgeInfo.m_nObj1Lines > 0 ? m_aEdgeInfo.m_nObj1Lines - 1 : 0); + sal_uInt32 nO2(m_aEdgeInfo.m_nObj2Lines > 0 ? m_aEdgeInfo.m_nObj2Lines - 1 : 0); + sal_uInt32 nM(m_aEdgeInfo.m_nMiddleLine != 0xFFFF ? 1 : 0); + nHdlCnt += nO1 + nO2 + nM; + } + else if (eKind==SdrEdgeKind::ThreeLines && nPointCount == 4) + { + if(GetConnectedNode(true)) + nHdlCnt++; + + if(GetConnectedNode(false)) + nHdlCnt++; + } + } + + return nHdlCnt; +} + +void SdrEdgeObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + sal_uInt32 nPointCount(m_pEdgeTrack->GetPointCount()); + if (nPointCount==0) + return; + + { + std::unique_ptr<SdrHdl> pHdl(new ImpEdgeHdl((*m_pEdgeTrack)[0],SdrHdlKind::Poly)); + if (m_aCon1.m_pSdrObj!=nullptr && m_aCon1.m_bBestVertex) pHdl->Set1PixMore(); + pHdl->SetPointNum(0); + rHdlList.AddHdl(std::move(pHdl)); + } + { + std::unique_ptr<SdrHdl> pHdl(new ImpEdgeHdl((*m_pEdgeTrack)[sal_uInt16(nPointCount-1)],SdrHdlKind::Poly)); + if (m_aCon2.m_pSdrObj!=nullptr && m_aCon2.m_bBestVertex) pHdl->Set1PixMore(); + pHdl->SetPointNum(1); + rHdlList.AddHdl(std::move(pHdl)); + } + { + SdrEdgeKind eKind=GetObjectItem(SDRATTR_EDGEKIND).GetValue(); + if ((eKind==SdrEdgeKind::OrthoLines || eKind==SdrEdgeKind::Bezier) && nPointCount >= 4) + { + sal_uInt32 nO1(m_aEdgeInfo.m_nObj1Lines > 0 ? m_aEdgeInfo.m_nObj1Lines - 1 : 0); + sal_uInt32 nO2(m_aEdgeInfo.m_nObj2Lines > 0 ? m_aEdgeInfo.m_nObj2Lines - 1 : 0); + sal_uInt32 nM(m_aEdgeInfo.m_nMiddleLine != 0xFFFF ? 1 : 0); + bool bOOXMLCurve = m_aEdgeInfo.m_bUseOOXMLCurve && eKind == SdrEdgeKind::Bezier; + for(sal_uInt32 i = 0; i < (nO1 + nO2 + nM); ++i) + { + sal_Int32 nPt(0); + sal_uInt32 nNum = i; + std::unique_ptr<ImpEdgeHdl> pHdl(new ImpEdgeHdl(Point(),SdrHdlKind::Poly)); + if (nNum<nO1) + { + nPt = bOOXMLCurve ? (nNum + 1) * 3 : nNum + 1; + if (nNum==0) pHdl->SetLineCode(SdrEdgeLineCode::Obj1Line2); + if (nNum==1) pHdl->SetLineCode(SdrEdgeLineCode::Obj1Line3); + } else { + nNum=nNum-nO1; + if (nNum<nO2) + { + nPt = bOOXMLCurve ? nPointCount - 4 - nNum * 3 : nPointCount - 3 - nNum; + if (nNum==0) pHdl->SetLineCode(SdrEdgeLineCode::Obj2Line2); + if (nNum==1) pHdl->SetLineCode(SdrEdgeLineCode::Obj2Line3); + } else { + nNum=nNum-nO2; + if (nNum<nM) { + nPt = bOOXMLCurve ? m_aEdgeInfo.m_nMiddleLine * 3 + : m_aEdgeInfo.m_nMiddleLine; + pHdl->SetLineCode(SdrEdgeLineCode::MiddleLine); + } + } + } + if (nPt>0) + { + if (bOOXMLCurve) + { + Point aPos((*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)]); + pHdl->SetPos(aPos); + } + else + { + Point aPos((*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)]); + aPos+=(*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)+1]; + aPos.setX( aPos.X() / 2 ); + aPos.setY( aPos.Y() / 2 ); + pHdl->SetPos(aPos); + } + pHdl->SetPointNum(i + 2); + rHdlList.AddHdl(std::move(pHdl)); + } + } + } + else if (eKind==SdrEdgeKind::ThreeLines && nPointCount == 4) + { + if(GetConnectedNode(true)) + { + Point aPos((*m_pEdgeTrack)[1]); + std::unique_ptr<ImpEdgeHdl> pHdl(new ImpEdgeHdl(aPos,SdrHdlKind::Poly)); + pHdl->SetLineCode(SdrEdgeLineCode::Obj1Line2); + pHdl->SetPointNum(2); + rHdlList.AddHdl(std::move(pHdl)); + } + if(GetConnectedNode(false)) + { + Point aPos((*m_pEdgeTrack)[2]); + std::unique_ptr<ImpEdgeHdl> pHdl(new ImpEdgeHdl(aPos,SdrHdlKind::Poly)); + pHdl->SetLineCode(SdrEdgeLineCode::Obj2Line2); + pHdl->SetPointNum(3); + rHdlList.AddHdl(std::move(pHdl)); + } + } + } +} + +bool SdrEdgeObj::hasSpecialDrag() const +{ + return true; +} + +rtl::Reference<SdrObject> SdrEdgeObj::getFullDragClone() const +{ + // use Clone operator + rtl::Reference<SdrEdgeObj> pRetval = SdrObject::Clone(*this, getSdrModelFromSdrObject()); + + // copy connections for clone, SdrEdgeObj::operator= does not do this + pRetval->ConnectToNode(true, GetConnectedNode(true)); + pRetval->ConnectToNode(false, GetConnectedNode(false)); + + return pRetval; +} + +bool SdrEdgeObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + if(!rDrag.GetHdl()) + return false; + + rDrag.SetEndDragChangesAttributes(true); + + if(rDrag.GetHdl()->GetPointNum() < 2) + { + rDrag.SetNoSnap(); + } + + return true; +} + +bool SdrEdgeObj::applySpecialDrag(SdrDragStat& rDragStat) +{ + SdrEdgeObj* pOriginalEdge = dynamic_cast< SdrEdgeObj* >(rDragStat.GetHdl()->GetObj()); + const bool bOriginalEdgeModified(pOriginalEdge == this); + + if(!bOriginalEdgeModified && pOriginalEdge) + { + // copy connections when clone is modified. This is needed because + // as preparation to this modification the data from the original object + // was copied to the clone using the operator=. As can be seen there, + // that operator does not copy the connections (for good reason) + ConnectToNode(true, pOriginalEdge->GetConnection(true).GetSdrObject()); + ConnectToNode(false, pOriginalEdge->GetConnection(false).GetSdrObject()); + } + + if(rDragStat.GetHdl()->GetPointNum() < 2) + { + // start or end point connector drag + const bool bDragA(0 == rDragStat.GetHdl()->GetPointNum()); + const Point aPointNow(rDragStat.GetNow()); + + rDragStat.SetEndDragChangesGeoAndAttributes(true); + + if(rDragStat.GetPageView()) + { + SdrObjConnection* pDraggedOne(bDragA ? &m_aCon1 : &m_aCon2); + + // clear connection + DisconnectFromNode(bDragA); + + // look for new connection + ImpFindConnector(aPointNow, *rDragStat.GetPageView(), *pDraggedOne, pOriginalEdge, nullptr, &rDragStat); + + if(pDraggedOne->m_pSdrObj) + { + // if found, officially connect to it; ImpFindConnector only + // sets pObj hard + SdrObject* pNewConnection = pDraggedOne->m_pSdrObj; + pDraggedOne->m_pSdrObj = nullptr; + ConnectToNode(bDragA, pNewConnection); + } + + if(rDragStat.GetView() && !bOriginalEdgeModified) + { + // show IA helper, but only do this during IA, so not when the original + // Edge gets modified in the last call + rDragStat.GetView()->SetConnectMarker(*pDraggedOne); + } + } + + if(m_pEdgeTrack) + { + // change pEdgeTrack to modified position + if(bDragA) + { + (*m_pEdgeTrack)[0] = aPointNow; + } + else + { + (*m_pEdgeTrack)[sal_uInt16(m_pEdgeTrack->GetPointCount()-1)] = aPointNow; + } + } + + // reset edge info's offsets, this is an end point drag + m_aEdgeInfo.m_aObj1Line2 = Point(); + m_aEdgeInfo.m_aObj1Line3 = Point(); + m_aEdgeInfo.m_aObj2Line2 = Point(); + m_aEdgeInfo.m_aObj2Line3 = Point(); + m_aEdgeInfo.m_aMiddleLine = Point(); + } + else + { + // control point connector drag + const ImpEdgeHdl* pEdgeHdl = static_cast<const ImpEdgeHdl*>(rDragStat.GetHdl()); + const SdrEdgeLineCode eLineCode = pEdgeHdl->GetLineCode(); + const Point aDist(rDragStat.GetNow() - rDragStat.GetStart()); + sal_Int32 nDist(pEdgeHdl->IsHorzDrag() ? aDist.X() : aDist.Y()); + + nDist += m_aEdgeInfo.ImpGetLineOffset(eLineCode, *m_pEdgeTrack); + m_aEdgeInfo.ImpSetLineOffset(eLineCode, *m_pEdgeTrack, nDist); + } + + // force recalculation of EdgeTrack + *m_pEdgeTrack = ImpCalcEdgeTrack(*m_pEdgeTrack, m_aCon1, m_aCon2, &m_aEdgeInfo); + m_bEdgeTrackDirty=false; + + // save EdgeInfos and mark object as user modified + ImpSetEdgeInfoToAttr(); + m_bEdgeTrackUserDefined = false; + + SetBoundAndSnapRectsDirty(); + + if(bOriginalEdgeModified && rDragStat.GetView()) + { + // hide connect marker helper again when original gets changed. + // This happens at the end of the interaction + rDragStat.GetView()->HideConnectMarker(); + } + + return true; +} + +OUString SdrEdgeObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment) + { + return OUString(); + } + else + { + return ImpGetDescriptionStr(STR_DragEdgeTail); + } +} + + +basegfx::B2DPolygon SdrEdgeObj::ImplAddConnectorOverlay(const SdrDragMethod& rDragMethod, bool bTail1, bool bTail2, bool bDetail) const +{ + basegfx::B2DPolygon aResult; + + if(bDetail) + { + SdrObjConnection aMyCon1(m_aCon1); + SdrObjConnection aMyCon2(m_aCon2); + + if (bTail1) + { + const basegfx::B2DPoint aTemp(rDragMethod.getCurrentTransformation() * basegfx::B2DPoint(aMyCon1.m_aObjOfs.X(), aMyCon1.m_aObjOfs.Y())); + aMyCon1.m_aObjOfs.setX( basegfx::fround(aTemp.getX()) ); + aMyCon1.m_aObjOfs.setY( basegfx::fround(aTemp.getY()) ); + } + + if (bTail2) + { + const basegfx::B2DPoint aTemp(rDragMethod.getCurrentTransformation() * basegfx::B2DPoint(aMyCon2.m_aObjOfs.X(), aMyCon2.m_aObjOfs.Y())); + aMyCon2.m_aObjOfs.setX( basegfx::fround(aTemp.getX()) ); + aMyCon2.m_aObjOfs.setY( basegfx::fround(aTemp.getY()) ); + } + + SdrEdgeInfoRec aInfo(m_aEdgeInfo); + XPolygon aXP(ImpCalcEdgeTrack(*m_pEdgeTrack, aMyCon1, aMyCon2, &aInfo)); + + if(aXP.GetPointCount()) + { + aResult = aXP.getB2DPolygon(); + } + } + else + { + Point aPt1((*m_pEdgeTrack)[0]); + Point aPt2((*m_pEdgeTrack)[sal_uInt16(m_pEdgeTrack->GetPointCount() - 1)]); + + if (m_aCon1.m_pSdrObj && (m_aCon1.m_bBestConn || m_aCon1.m_bBestVertex)) + aPt1 = m_aCon1.m_pSdrObj->GetSnapRect().Center(); + + if (m_aCon2.m_pSdrObj && (m_aCon2.m_bBestConn || m_aCon2.m_bBestVertex)) + aPt2 = m_aCon2.m_pSdrObj->GetSnapRect().Center(); + + if (bTail1) + { + const basegfx::B2DPoint aTemp(rDragMethod.getCurrentTransformation() * basegfx::B2DPoint(aPt1.X(), aPt1.Y())); + aPt1.setX( basegfx::fround(aTemp.getX()) ); + aPt1.setY( basegfx::fround(aTemp.getY()) ); + } + + if (bTail2) + { + const basegfx::B2DPoint aTemp(rDragMethod.getCurrentTransformation() * basegfx::B2DPoint(aPt2.X(), aPt2.Y())); + aPt2.setX( basegfx::fround(aTemp.getX()) ); + aPt2.setY( basegfx::fround(aTemp.getY()) ); + } + + aResult.append(basegfx::B2DPoint(aPt1.X(), aPt1.Y())); + aResult.append(basegfx::B2DPoint(aPt2.X(), aPt2.Y())); + } + + return aResult; +} + +bool SdrEdgeObj::BegCreate(SdrDragStat& rDragStat) +{ + rDragStat.SetNoSnap(); + m_pEdgeTrack->SetPointCount(2); + (*m_pEdgeTrack)[0]=rDragStat.GetStart(); + (*m_pEdgeTrack)[1]=rDragStat.GetNow(); + if (rDragStat.GetPageView()!=nullptr) { + ImpFindConnector(rDragStat.GetStart(),*rDragStat.GetPageView(),m_aCon1,this); + ConnectToNode(true,m_aCon1.m_pSdrObj); + } + *m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo); + return true; +} + +bool SdrEdgeObj::MovCreate(SdrDragStat& rDragStat) +{ + sal_uInt16 nMax=m_pEdgeTrack->GetPointCount(); + (*m_pEdgeTrack)[nMax-1]=rDragStat.GetNow(); + if (rDragStat.GetPageView()!=nullptr) { + ImpFindConnector(rDragStat.GetNow(),*rDragStat.GetPageView(),m_aCon2,this); + rDragStat.GetView()->SetConnectMarker(m_aCon2); + } + SetBoundRectDirty(); + m_bSnapRectDirty=true; + ConnectToNode(false,m_aCon2.m_pSdrObj); + *m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo); + m_bEdgeTrackDirty=false; + return true; +} + +bool SdrEdgeObj::EndCreate(SdrDragStat& rDragStat, SdrCreateCmd eCmd) +{ + bool bOk=(eCmd==SdrCreateCmd::ForceEnd || rDragStat.GetPointCount()>=2); + if (bOk) { + ConnectToNode(true,m_aCon1.m_pSdrObj); + ConnectToNode(false,m_aCon2.m_pSdrObj); + if (rDragStat.GetView()!=nullptr) { + rDragStat.GetView()->HideConnectMarker(); + } + ImpSetEdgeInfoToAttr(); // copy values from aEdgeInfo into the pool + } + SetBoundAndSnapRectsDirty(); + return bOk; +} + +bool SdrEdgeObj::BckCreate(SdrDragStat& rDragStat) +{ + if (rDragStat.GetView()!=nullptr) { + rDragStat.GetView()->HideConnectMarker(); + } + return false; +} + +void SdrEdgeObj::BrkCreate(SdrDragStat& rDragStat) +{ + if (rDragStat.GetView()!=nullptr) { + rDragStat.GetView()->HideConnectMarker(); + } +} + +basegfx::B2DPolyPolygon SdrEdgeObj::TakeCreatePoly(const SdrDragStat& /*rStatDrag*/) const +{ + basegfx::B2DPolyPolygon aRetval; + aRetval.append(m_pEdgeTrack->getB2DPolygon()); + return aRetval; +} + +PointerStyle SdrEdgeObj::GetCreatePointer() const +{ + return PointerStyle::DrawConnect; +} + +bool SdrEdgeObj::ImpFindConnector(const Point& rPt, const SdrPageView& rPV, SdrObjConnection& rCon, const SdrEdgeObj* pThis, OutputDevice* pOut, SdrDragStat* pDragStat) +{ + rCon.ResetVars(); + if (pOut==nullptr) pOut=rPV.GetView().GetFirstOutputDevice(); + if (pOut==nullptr) return false; + SdrObjList* pOL=rPV.GetObjList(); + const SdrLayerIDSet& rVisLayer=rPV.GetVisibleLayers(); + // sensitive area of connectors is twice as large as the one of the handles + sal_uInt16 nMarkHdSiz=rPV.GetView().GetMarkHdlSizePixel(); + Size aHalfConSiz(nMarkHdSiz,nMarkHdSiz); + if (comphelper::LibreOfficeKit::isActive() && pOut->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + aHalfConSiz=pOut->PixelToLogic(aHalfConSiz, MapMode(MapUnit::Map100thMM)); + else + aHalfConSiz=pOut->PixelToLogic(aHalfConSiz); + tools::Rectangle aMouseRect(rPt,rPt); + aMouseRect.AdjustLeft( -(aHalfConSiz.Width()) ); + aMouseRect.AdjustTop( -(aHalfConSiz.Height()) ); + aMouseRect.AdjustRight(aHalfConSiz.Width() ); + aMouseRect.AdjustBottom(aHalfConSiz.Height() ); + double fBoundHitTol=static_cast<double>(aHalfConSiz.Width())/2; if (fBoundHitTol==0.0) fBoundHitTol=1.0; + size_t no=pOL->GetObjCount(); + bool bFnd = false; + SdrObjConnection aTestCon; + bool bTiledRendering = comphelper::LibreOfficeKit::isActive(); + bool bHasRequestedOrdNum = false; + sal_Int32 requestedOrdNum = -1; + + if (bTiledRendering && pDragStat) + { + auto& glueOptions = pDragStat->GetGlueOptions(); + if (glueOptions.objectOrdNum != -1) + { + requestedOrdNum = glueOptions.objectOrdNum; + bHasRequestedOrdNum = true; + } + } + + while (no>0 && !bFnd) { + // issue: group objects on different layers return LayerID=0! + no--; + SdrObject* pObj=pOL->GetObj(no); + if (bHasRequestedOrdNum) + { + if (pObj->GetOrdNumDirect() != static_cast<sal_uInt32>(requestedOrdNum)) + continue; + } + if (rVisLayer.IsSet(pObj->GetLayer()) && pObj->IsVisible() && // only visible objects + (pThis==nullptr || pObj!=static_cast<SdrObject const *>(pThis))) // don't connect it to itself + { + tools::Rectangle aObjBound(pObj->GetCurrentBoundRect()); + if (aObjBound.Overlaps(aMouseRect)) { + aTestCon.ResetVars(); + bool bEdge=dynamic_cast<const SdrEdgeObj *>(pObj) != nullptr; // no BestCon for Edge + // User-defined connectors have absolute priority. + // After those come Vertex, Corner and center (Best), all prioritized equally. + // Finally, a HitTest for the object. + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + sal_uInt16 nGluePointCnt=pGPL==nullptr ? 0 : pGPL->GetCount(); + sal_uInt16 nGesAnz=nGluePointCnt+9; + bool bUserFnd = false; + sal_uIntPtr nBestDist=0xFFFFFFFF; + for (sal_uInt16 i=0; i<nGesAnz; i++) + { + bool bUser=i<nGluePointCnt; + bool bVertex=i>=nGluePointCnt+0 && i<nGluePointCnt+4; + bool bCorner=i>=nGluePointCnt+4 && i<nGluePointCnt+8; + bool bCenter=i==nGluePointCnt+8; + bool bOk = false; + Point aConPos; + sal_uInt16 nConNum=i; + if (bUser) { + const SdrGluePoint& rGP=(*pGPL)[nConNum]; + aConPos=rGP.GetAbsolutePos(*pObj); + nConNum=rGP.GetId(); + bOk = true; + } else if (bVertex && !bUserFnd) { + nConNum=nConNum-nGluePointCnt; + SdrGluePoint aPt(pObj->GetVertexGluePoint(nConNum)); + aConPos=aPt.GetAbsolutePos(*pObj); + bOk = true; + } else if (bCorner && !bUserFnd) { + nConNum-=nGluePointCnt+4; + i+=3; + } + else if (bCenter && !bUserFnd && !bEdge) + { + // Suppress default connect at object center + if(!pThis || !pThis->GetSuppressDefaultConnect()) + { + // not the edges! + nConNum=0; + aConPos=aObjBound.Center(); + bOk = true; + } + } + if (bOk && aMouseRect.Contains(aConPos)) { + if (bUser) bUserFnd = true; + bFnd = true; + sal_uIntPtr nDist=static_cast<sal_uIntPtr>(std::abs(aConPos.X()-rPt.X()))+static_cast<sal_uIntPtr>(std::abs(aConPos.Y()-rPt.Y())); + if (nDist<nBestDist) { + nBestDist=nDist; + aTestCon.m_pSdrObj=pObj; + aTestCon.m_nConId=nConNum; + aTestCon.m_bAutoCorner=bCorner; + aTestCon.m_bAutoVertex=bVertex; + aTestCon.m_bBestConn=false; // bCenter; + aTestCon.m_bBestVertex=bCenter; + } + } + } + // if no connector is hit, try HitTest again, for BestConnector (=bCenter) + if(!bFnd && + !bEdge && + SdrObjectPrimitiveHit(*pObj, rPt, {fBoundHitTol, fBoundHitTol}, rPV, &rVisLayer, false)) + { + // Suppress default connect at object inside bound + if(!pThis || !pThis->GetSuppressDefaultConnect()) + { + bFnd = true; + aTestCon.m_pSdrObj=pObj; + aTestCon.m_bBestConn=true; + } + } + if (bFnd) { + aMouseRect.AdjustLeft( -fBoundHitTol ); + aMouseRect.AdjustTop( -fBoundHitTol ); + aMouseRect.AdjustRight(fBoundHitTol ); + aMouseRect.AdjustBottom(fBoundHitTol ); + } + + } + } + } + rCon=aTestCon; + return bFnd; +} + +void SdrEdgeObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + const tools::Rectangle aOld(GetSnapRect()); + + if(aOld == rRect) + return; + + if (getRectangle().IsEmpty() && 0 == m_pEdgeTrack->GetPointCount()) + { + // #i110629# When initializing, do not scale on empty Rectangle; this + // will mirror the underlying text object (!) + setRectangle(rRect); + maSnapRect = rRect; + } + else + { + tools::Long nMulX = rRect.Right() - rRect.Left(); + tools::Long nDivX = aOld.Right() - aOld.Left(); + tools::Long nMulY = rRect.Bottom() - rRect.Top(); + tools::Long nDivY = aOld.Bottom() - aOld.Top(); + if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; } + if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; } + Fraction aX(nMulX, nDivX); + Fraction aY(nMulY, nDivY); + NbcResize(aOld.TopLeft(), aX, aY); + NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top())); + } +} + +void SdrEdgeObj::NbcMove(const Size& rSiz) +{ + SdrTextObj::NbcMove(rSiz); + MoveXPoly(*m_pEdgeTrack,rSiz); +} + +void SdrEdgeObj::NbcResize(const Point& rRefPnt, const Fraction& aXFact, const Fraction& aYFact) +{ + SdrTextObj::NbcResize(rRefPnt,aXFact,aXFact); + ResizeXPoly(*m_pEdgeTrack,rRefPnt,aXFact,aYFact); + + // if resize is not from paste, forget user distances + if (!getSdrModelFromSdrObject().IsPasteResize()) + { + m_aEdgeInfo.m_aObj1Line2 = Point(); + m_aEdgeInfo.m_aObj1Line3 = Point(); + m_aEdgeInfo.m_aObj2Line2 = Point(); + m_aEdgeInfo.m_aObj2Line3 = Point(); + m_aEdgeInfo.m_aMiddleLine = Point(); + } +} + +// #i54102# added rotation support +void SdrEdgeObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + if(m_bEdgeTrackUserDefined) + { + // #i120437# special handling when track is imported, apply + // transformation directly to imported track. + SdrTextObj::NbcRotate(rRef, nAngle, sn, cs); + RotateXPoly(*m_pEdgeTrack, rRef, sn, cs); + } + else + { + // handle start and end point if not connected + const bool bCon1(nullptr != m_aCon1.m_pSdrObj && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + const bool bCon2(nullptr != m_aCon2.m_pSdrObj && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + + if(!bCon1 && m_pEdgeTrack) + { + RotatePoint((*m_pEdgeTrack)[0],rRef,sn,cs); + ImpDirtyEdgeTrack(); + } + + if(!bCon2 && m_pEdgeTrack) + { + sal_uInt16 nPointCount = m_pEdgeTrack->GetPointCount(); + RotatePoint((*m_pEdgeTrack)[sal_uInt16(nPointCount-1)],rRef,sn,cs); + ImpDirtyEdgeTrack(); + } + } +} + +// #i54102# added mirror support +void SdrEdgeObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + if(m_bEdgeTrackUserDefined) + { + // #i120437# special handling when track is imported, apply + // transformation directly to imported track. + SdrTextObj::NbcMirror(rRef1, rRef2); + MirrorXPoly(*m_pEdgeTrack, rRef1, rRef2); + } + else + { + // handle start and end point if not connected + const bool bCon1(nullptr != m_aCon1.m_pSdrObj && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + const bool bCon2(nullptr != m_aCon2.m_pSdrObj && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + + if(!bCon1 && m_pEdgeTrack) + { + MirrorPoint((*m_pEdgeTrack)[0],rRef1,rRef2); + ImpDirtyEdgeTrack(); + } + + if(!bCon2 && m_pEdgeTrack) + { + sal_uInt16 nPointCount = m_pEdgeTrack->GetPointCount(); + MirrorPoint((*m_pEdgeTrack)[sal_uInt16(nPointCount-1)],rRef1,rRef2); + ImpDirtyEdgeTrack(); + } + } +} + +// #i54102# added shear support +void SdrEdgeObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + if(m_bEdgeTrackUserDefined) + { + // #i120437# special handling when track is imported, apply + // transformation directly to imported track. + SdrTextObj::NbcShear(rRef, nAngle, tn, bVShear); + ShearXPoly(*m_pEdgeTrack, rRef, tn, bVShear); + } + else + { + // handle start and end point if not connected + const bool bCon1(nullptr != m_aCon1.m_pSdrObj && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + const bool bCon2(nullptr != m_aCon2.m_pSdrObj && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); + + if(!bCon1 && m_pEdgeTrack) + { + ShearPoint((*m_pEdgeTrack)[0],rRef,tn,bVShear); + ImpDirtyEdgeTrack(); + } + + if(!bCon2 && m_pEdgeTrack) + { + sal_uInt16 nPointCount = m_pEdgeTrack->GetPointCount(); + ShearPoint((*m_pEdgeTrack)[sal_uInt16(nPointCount-1)],rRef,tn,bVShear); + ImpDirtyEdgeTrack(); + } + } +} + +rtl::Reference<SdrObject> SdrEdgeObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + basegfx::B2DPolyPolygon aPolyPolygon; + aPolyPolygon.append(m_pEdgeTrack->getB2DPolygon()); + rtl::Reference<SdrObject> pRet = ImpConvertMakeObj(aPolyPolygon, false, bBezier); + + if(bAddText) + { + pRet = ImpConvertAddText(std::move(pRet), bBezier); + } + + return pRet; +} + +sal_uInt32 SdrEdgeObj::GetSnapPointCount() const +{ + return 2; +} + +Point SdrEdgeObj::GetSnapPoint(sal_uInt32 i) const +{ + const_cast<SdrEdgeObj*>(this)->ImpUndirtyEdgeTrack(); + sal_uInt16 nCount=m_pEdgeTrack->GetPointCount(); + if (i==0) return (*m_pEdgeTrack)[0]; + else return (*m_pEdgeTrack)[nCount-1]; +} + +bool SdrEdgeObj::IsPolyObj() const +{ + return false; +} + +sal_uInt32 SdrEdgeObj::GetPointCount() const +{ + return 0; +} + +Point SdrEdgeObj::GetPoint(sal_uInt32 i) const +{ + const_cast<SdrEdgeObj*>(this)->ImpUndirtyEdgeTrack(); + sal_uInt16 nCount=m_pEdgeTrack->GetPointCount(); + if (0 == i) + return (*m_pEdgeTrack)[0]; + else + return (*m_pEdgeTrack)[nCount-1]; +} + +void SdrEdgeObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i) +{ + // TODO: Need an implementation to connect differently. + ImpUndirtyEdgeTrack(); + sal_uInt16 nCount=m_pEdgeTrack->GetPointCount(); + if (0 == i) + (*m_pEdgeTrack)[0]=rPnt; + if (1 == i) + (*m_pEdgeTrack)[nCount-1]=rPnt; + SetEdgeTrackDirty(); + SetBoundAndSnapRectsDirty(); +} + +SdrEdgeObjGeoData::SdrEdgeObjGeoData() + : m_pEdgeTrack(std::in_place) + , m_bEdgeTrackDirty(false) + , m_bEdgeTrackUserDefined(false) +{ +} + +SdrEdgeObjGeoData::~SdrEdgeObjGeoData() +{ +} + +std::unique_ptr<SdrObjGeoData> SdrEdgeObj::NewGeoData() const +{ + return std::make_unique<SdrEdgeObjGeoData>(); +} + +void SdrEdgeObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrTextObj::SaveGeoData(rGeo); + SdrEdgeObjGeoData& rEGeo=static_cast<SdrEdgeObjGeoData&>(rGeo); + rEGeo.m_aCon1 =m_aCon1; + rEGeo.m_aCon2 =m_aCon2; + *rEGeo.m_pEdgeTrack =*m_pEdgeTrack; + rEGeo.m_bEdgeTrackDirty=m_bEdgeTrackDirty; + rEGeo.m_bEdgeTrackUserDefined=m_bEdgeTrackUserDefined; + rEGeo.m_aEdgeInfo =m_aEdgeInfo; +} + +void SdrEdgeObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrTextObj::RestoreGeoData(rGeo); + const SdrEdgeObjGeoData& rEGeo=static_cast<const SdrEdgeObjGeoData&>(rGeo); + if (m_aCon1.m_pSdrObj!=rEGeo.m_aCon1.m_pSdrObj) { + if (m_aCon1.m_pSdrObj!=nullptr) m_aCon1.m_pSdrObj->RemoveListener(*this); + m_aCon1=rEGeo.m_aCon1; + if (m_aCon1.m_pSdrObj!=nullptr) m_aCon1.m_pSdrObj->AddListener(*this); + } + else + m_aCon1=rEGeo.m_aCon1; + + if (m_aCon2.m_pSdrObj!=rEGeo.m_aCon2.m_pSdrObj) { + if (m_aCon2.m_pSdrObj!=nullptr) m_aCon2.m_pSdrObj->RemoveListener(*this); + m_aCon2=rEGeo.m_aCon2; + if (m_aCon2.m_pSdrObj!=nullptr) m_aCon2.m_pSdrObj->AddListener(*this); + } + else + m_aCon2=rEGeo.m_aCon2; + + *m_pEdgeTrack =*rEGeo.m_pEdgeTrack; + m_bEdgeTrackDirty=rEGeo.m_bEdgeTrackDirty; + m_bEdgeTrackUserDefined=rEGeo.m_bEdgeTrackUserDefined; + m_aEdgeInfo =rEGeo.m_aEdgeInfo; +} + +Point SdrEdgeObj::GetTailPoint( bool bTail ) const +{ + if( m_pEdgeTrack && m_pEdgeTrack->GetPointCount()!=0) + { + const XPolygon& rTrack0 = *m_pEdgeTrack; + if(bTail) + { + return rTrack0[0]; + } + else + { + const sal_uInt16 nSiz = rTrack0.GetPointCount() - 1; + return rTrack0[nSiz]; + } + } + else + { + if(bTail) + return getOutRectangle().TopLeft(); + else + return getOutRectangle().BottomRight(); + } + +} + +void SdrEdgeObj::SetTailPoint( bool bTail, const Point& rPt ) +{ + ImpSetTailPoint( bTail, rPt ); + SetChanged(); +} + +/** this method is used by the api to set a gluepoint for a connection + nId == -1 : The best default point is automatically chosen + 0 <= nId <= 3 : One of the default points is chosen + nId >= 4 : A user defined gluepoint is chosen +*/ +void SdrEdgeObj::setGluePointIndex( bool bTail, sal_Int32 nIndex /* = -1 */ ) +{ + SdrObjConnection& rConn1 = GetConnection( bTail ); + + rConn1.SetAutoVertex( nIndex >= 0 && nIndex <= 3 ); + rConn1.SetBestConnection( nIndex < 0 ); + rConn1.SetBestVertex( nIndex < 0 ); + + if( nIndex > 3 ) + { + nIndex -= 3; // the start api index is 0, whereas the implementation in svx starts from 1 + + // for user defined gluepoints we have + // to get the id for this index first + const SdrGluePointList* pList = rConn1.GetSdrObject() ? rConn1.GetSdrObject()->GetGluePointList() : nullptr; + if( pList == nullptr || SDRGLUEPOINT_NOTFOUND == pList->FindGluePoint(static_cast<sal_uInt16>(nIndex)) ) + return; + } + else if( nIndex < 0 ) + { + nIndex = 0; + } + + rConn1.SetConnectorId( static_cast<sal_uInt16>(nIndex) ); + + SetChanged(); + SetBoundAndSnapRectsDirty(); + ImpRecalcEdgeTrack(); +} + +/** this method is used by the api to return a gluepoint id for a connection. + See setGluePointId for possible return values */ +sal_Int32 SdrEdgeObj::getGluePointIndex( bool bTail ) +{ + SdrObjConnection& rConn1 = GetConnection( bTail ); + sal_Int32 nId = -1; + if( !rConn1.IsBestConnection() ) + { + nId = rConn1.GetConnectorId(); + if( !rConn1.IsAutoVertex() ) + nId += 3; // the start api index is 0, whereas the implementation in svx starts from 1 + } + return nId; +} + +// Implementation was missing; edge track needs to be invalidated additionally. +void SdrEdgeObj::NbcSetAnchorPos(const Point& rPnt) +{ + // call parent functionality + SdrTextObj::NbcSetAnchorPos(rPnt); + + // Additionally, invalidate edge track + ImpDirtyEdgeTrack(); +} + +bool SdrEdgeObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const +{ + // use base method from SdrObject, it's not rotatable and + // a call to GetSnapRect() is used. That's what we need for Connector. + return SdrObject::TRGetBaseGeometry(rMatrix, rPolyPolygon); +} + +void SdrEdgeObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // where appropriate take care for existing connections. For now, just use the + // implementation from SdrObject. + SdrObject::TRSetBaseGeometry(rMatrix, rPolyPolygon); +} + +// for geometry access +::basegfx::B2DPolygon SdrEdgeObj::getEdgeTrack() const +{ + if(m_bEdgeTrackDirty) + { + const_cast< SdrEdgeObj* >(this)->ImpRecalcEdgeTrack(); + } + + if(m_pEdgeTrack) + { + return m_pEdgeTrack->getB2DPolygon(); + } + else + { + return ::basegfx::B2DPolygon(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdograf.cxx b/svx/source/svdraw/svdograf.cxx new file mode 100644 index 0000000000..0f7151e295 --- /dev/null +++ b/svx/source/svdraw/svdograf.cxx @@ -0,0 +1,1252 @@ +/* -*- 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 <unotools/streamwrap.hxx> + +#include <sfx2/lnkbase.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/svapp.hxx> + +#include <sfx2/linkmgr.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdhdl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdogrp.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflbmtit.hxx> +#include "svdfmtf.hxx" +#include <sdgcoitm.hxx> +#include <svx/sdgcpitm.hxx> +#include <svx/sdggaitm.hxx> +#include <sdginitm.hxx> +#include <svx/sdgluitm.hxx> +#include <svx/sdgmoitm.hxx> +#include <sdgtritm.hxx> +#include <sdr/properties/graphicproperties.hxx> +#include <sdr/contact/viewcontactofgraphic.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/processor2d/objectinfoextractor2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> +#include <memory> + +using namespace ::com::sun::star; + +class SdrGraphicLink : public sfx2::SvBaseLink +{ + SdrGrafObj& rGrafObj; + +public: + explicit SdrGraphicLink(SdrGrafObj& rObj); + + virtual void Closed() override; + + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) override; + + void Connect() { GetRealObject(); } +}; + +SdrGraphicLink::SdrGraphicLink(SdrGrafObj& rObj) +: ::sfx2::SvBaseLink( ::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SVXB ) +, rGrafObj( rObj ) +{ + SetSynchron( false ); +} + +::sfx2::SvBaseLink::UpdateResult SdrGraphicLink::DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) +{ + SdrModel& rModel(rGrafObj.getSdrModelFromSdrObject()); + sfx2::LinkManager* pLinkManager(rModel.GetLinkManager()); + + if( pLinkManager && rValue.hasValue() ) + { + sfx2::LinkManager::GetDisplayNames( this, nullptr, &rGrafObj.aFileName, nullptr, &rGrafObj.aFilterName ); + + Graphic aGraphic; + if (pLinkManager->GetGraphicFromAny(rMimeType, rValue, aGraphic, nullptr)) + { + rGrafObj.ImpSetLinkedGraphic(aGraphic); + } + else if( SotExchange::GetFormatIdFromMimeType( rMimeType ) != sfx2::LinkManager::RegisterStatusInfoId() ) + { + // broadcasting, to update slide sorter + rGrafObj.BroadcastObjectChange(); + } + } + return SUCCESS; +} + +void SdrGraphicLink::Closed() +{ + // close connection; set pLink of the object to NULL, as link instance is just about getting destructed. + rGrafObj.ForceSwapIn(); + rGrafObj.pGraphicLink=nullptr; + rGrafObj.ReleaseGraphicLink(); + SvBaseLink::Closed(); +} + +std::unique_ptr<sdr::properties::BaseProperties> SdrGrafObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::GraphicProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrGrafObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfGraphic>(*this); +} + + +// check if SVG and if try to get ObjectInfoPrimitive2D and extract info + +void SdrGrafObj::onGraphicChanged() +{ + if (!mpGraphicObject || !mpGraphicObject->GetGraphic().isAvailable()) + return; + + auto const & rVectorGraphicDataPtr = mpGraphicObject->GetGraphic().getVectorGraphicData(); + + if (!rVectorGraphicDataPtr) + return; + + // Skip for PDF as it is only a bitmap primitive in a sequence and + // doesn't contain metadata. However getting the primitive sequence + // will also trigger a premature rendering of the PDF. + if (rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Pdf) + return; + + const std::deque<uno::Reference<graphic::XPrimitive2D>>& rContainer(rVectorGraphicDataPtr->getPrimitive2DSequence()); + + if (rContainer.empty()) + return; + + drawinglayer::geometry::ViewInformation2D aViewInformation2D; + drawinglayer::processor2d::ObjectInfoPrimitiveExtractor2D aProcessor(aViewInformation2D); + + aProcessor.process(rContainer); + + const drawinglayer::primitive2d::ObjectInfoPrimitive2D* pResult = aProcessor.getResult(); + + if (!pResult) + return; + + OUString aName = pResult->getName(); + OUString aTitle = pResult->getTitle(); + OUString aDesc = pResult->getDesc(); + + if(!aName.isEmpty()) + { + SetName(aName); + } + + if(!aTitle.isEmpty()) + { + SetTitle(aTitle); + } + + if(!aDesc.isEmpty()) + { + SetDescription(aDesc); + } +} + +SdrGrafObj::SdrGrafObj(SdrModel& rSdrModel) +: SdrRectObj(rSdrModel) + ,mpGraphicObject(new GraphicObject) + ,pGraphicLink(nullptr) + ,bMirrored(false) + ,mbIsSignatureLine(false) + ,mbIsSignatureLineShowSignDate(true) + ,mbIsSignatureLineCanAddComment(false) + ,mbSignatureLineIsSigned(false) +{ + onGraphicChanged(); + + // #i118485# Shear allowed and possible now + mbNoShear = false; + + mbGrafAnimationAllowed = true; + + // #i25616# + mbLineIsOutsideGeometry = true; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; +} + +SdrGrafObj::SdrGrafObj(SdrModel& rSdrModel, SdrGrafObj const & rSource) +: SdrRectObj(rSdrModel, rSource) + ,mpGraphicObject(new GraphicObject) + ,pGraphicLink(nullptr) +{ + onGraphicChanged(); + + // #i118485# Shear allowed and possible now + mbNoShear = false; + + mbGrafAnimationAllowed = true; + + // #i25616# + mbLineIsOutsideGeometry = true; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; + + aFileName = rSource.aFileName; + bMirrored = rSource.bMirrored; + + mbIsSignatureLine = rSource.mbIsSignatureLine; + maSignatureLineId = rSource.maSignatureLineId; + maSignatureLineSuggestedSignerName = rSource.maSignatureLineSuggestedSignerName; + maSignatureLineSuggestedSignerTitle = rSource.maSignatureLineSuggestedSignerTitle; + maSignatureLineSuggestedSignerEmail = rSource.maSignatureLineSuggestedSignerEmail; + maSignatureLineSigningInstructions = rSource.maSignatureLineSigningInstructions; + mbIsSignatureLineShowSignDate = rSource.mbIsSignatureLineShowSignDate; + mbIsSignatureLineCanAddComment = rSource.mbIsSignatureLineCanAddComment; + mbSignatureLineIsSigned = false; + mpSignatureLineUnsignedGraphic = rSource.mpSignatureLineUnsignedGraphic; + + if(rSource.mpBarCode) + { + mpBarCode = std::make_unique<drawing::BarCode>(*rSource.mpBarCode); + } + else + { + mpBarCode.reset(); + } + + if (mbIsSignatureLine && rSource.mpSignatureLineUnsignedGraphic) + mpGraphicObject->SetGraphic(rSource.mpSignatureLineUnsignedGraphic); + else + mpGraphicObject->SetGraphic( rSource.GetGraphic() ); + + if( rSource.IsLinkedGraphic() ) + { + SetGraphicLink( aFileName ); + } + + ImpSetAttrToGrafInfo(); +} + +SdrGrafObj::SdrGrafObj( + SdrModel& rSdrModel, + const Graphic& rGraphic, + const tools::Rectangle& rRect) +: SdrRectObj(rSdrModel, rRect) + ,mpGraphicObject(new GraphicObject(rGraphic)) + ,pGraphicLink(nullptr) + ,bMirrored(false) + ,mbIsSignatureLine(false) + ,mbIsSignatureLineShowSignDate(true) + ,mbIsSignatureLineCanAddComment(false) + ,mbSignatureLineIsSigned(false) +{ + onGraphicChanged(); + + // #i118485# Shear allowed and possible now + mbNoShear = false; + + mbGrafAnimationAllowed = true; + + // #i25616# + mbLineIsOutsideGeometry = true; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; +} + +SdrGrafObj::SdrGrafObj( + SdrModel& rSdrModel, + const Graphic& rGraphic) +: SdrRectObj(rSdrModel) + ,mpGraphicObject(new GraphicObject(rGraphic)) + ,pGraphicLink(nullptr) + ,bMirrored(false) + ,mbIsSignatureLine(false) + ,mbIsSignatureLineShowSignDate(true) + ,mbIsSignatureLineCanAddComment(false) + ,mbSignatureLineIsSigned(false) +{ + onGraphicChanged(); + + // #i118485# Shear allowed and possible now + mbNoShear = false; + + mbGrafAnimationAllowed = true; + + // #i25616# + mbLineIsOutsideGeometry = true; + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; +} + +SdrGrafObj::~SdrGrafObj() +{ + ImpDeregisterLink(); +} + +void SdrGrafObj::SetGraphicObject(const GraphicObject& rGraphicObject) +{ + mpGraphicObject.reset(new GraphicObject(rGraphicObject)); + mpReplacementGraphicObject.reset(); + mpGraphicObject->SetUserData(); + SetChanged(); + BroadcastObjectChange(); + onGraphicChanged(); +} + +const GraphicObject& SdrGrafObj::GetGraphicObject(bool bForceSwapIn) const +{ + if (bForceSwapIn) + ForceSwapIn(); + return *mpGraphicObject; +} + +const GraphicObject* SdrGrafObj::GetReplacementGraphicObject() const +{ + if (!mpReplacementGraphicObject && mpGraphicObject) + { + auto const & rVectorGraphicDataPtr = mpGraphicObject->GetGraphic().getVectorGraphicData(); + + if (rVectorGraphicDataPtr) + { + const_cast< SdrGrafObj* >(this)->mpReplacementGraphicObject.reset(new GraphicObject(rVectorGraphicDataPtr->getReplacement())); + } + else if (mpGraphicObject->GetGraphic().GetType() == GraphicType::GdiMetafile) + { + // Replacement graphic for PDF and metafiles is just the bitmap. + const_cast<SdrGrafObj*>(this)->mpReplacementGraphicObject.reset(new GraphicObject(mpGraphicObject->GetGraphic().GetBitmapEx())); + } + } + + return mpReplacementGraphicObject.get(); +} + +void SdrGrafObj::NbcSetGraphic(const Graphic& rGraphic) +{ + mpGraphicObject->SetGraphic(rGraphic); + mpReplacementGraphicObject.reset(); + mpGraphicObject->SetUserData(); + onGraphicChanged(); +} + +void SdrGrafObj::SetGraphic( const Graphic& rGraphic ) +{ + if (!rGraphic.getOriginURL().isEmpty()) + { + ImpDeregisterLink(); + aFileName = rGraphic.getOriginURL(); + aFilterName = ""; + } + NbcSetGraphic(rGraphic); + if (!rGraphic.getOriginURL().isEmpty()) + { + ImpRegisterLink(); + mpGraphicObject->SetUserData(); + } + SetChanged(); + BroadcastObjectChange(); + ForceSwapIn(); +} + +const Graphic& SdrGrafObj::GetGraphic() const +{ + return mpGraphicObject->GetGraphic(); +} + +Graphic SdrGrafObj::GetTransformedGraphic( SdrGrafObjTransformsAttrs nTransformFlags ) const +{ + // Refactored most of the code to GraphicObject, where + // everybody can use e.g. the cropping functionality + MapMode aDestMap(getSdrModelFromSdrObject().GetScaleUnit()); + const Size aDestSize( GetLogicRect().GetSize() ); + GraphicAttr aActAttr = GetGraphicAttr(nTransformFlags); + + // Delegate to moved code in GraphicObject + return GetGraphicObject().GetTransformedGraphic( aDestSize, aDestMap, aActAttr ); +} + +GraphicType SdrGrafObj::GetGraphicType() const +{ + return mpGraphicObject->GetType(); +} + +GraphicAttr SdrGrafObj::GetGraphicAttr( SdrGrafObjTransformsAttrs nTransformFlags ) const +{ + GraphicAttr aActAttr; + + GraphicType eType = GetGraphicType(); + if( SdrGrafObjTransformsAttrs::NONE != nTransformFlags && + GraphicType::NONE != eType ) + { + const bool bMirror = bool( nTransformFlags & SdrGrafObjTransformsAttrs::MIRROR ); + const bool bRotate = bool( nTransformFlags & SdrGrafObjTransformsAttrs::ROTATE ) && + (maGeo.m_nRotationAngle && maGeo.m_nRotationAngle != 18000_deg100); + + // Need cropping info earlier + const_cast<SdrGrafObj*>(this)->ImpSetAttrToGrafInfo(); + + // Actually transform the graphic only in this case. + // Cropping always happens, though. + aActAttr = aGrafInfo; + + if( bMirror ) + { + sal_uInt16 nMirrorCase = ( maGeo.m_nRotationAngle == 18000_deg100 ) ? ( bMirrored ? 3 : 4 ) : ( bMirrored ? 2 : 1 ); + bool bHMirr = nMirrorCase == 2 || nMirrorCase == 4; + bool bVMirr = nMirrorCase == 3 || nMirrorCase == 4; + + aActAttr.SetMirrorFlags( ( bHMirr ? BmpMirrorFlags::Horizontal : BmpMirrorFlags::NONE ) | ( bVMirr ? BmpMirrorFlags::Vertical : BmpMirrorFlags::NONE ) ); + } + + if( bRotate ) + aActAttr.SetRotation( to<Degree10>(maGeo.m_nRotationAngle ) ); + } + + return aActAttr; +} + +bool SdrGrafObj::IsAnimated() const +{ + return mpGraphicObject->IsAnimated(); +} + +bool SdrGrafObj::IsEPS() const +{ + return mpGraphicObject->IsEPS(); +} + +MapMode SdrGrafObj::GetGrafPrefMapMode() const +{ + return mpGraphicObject->GetPrefMapMode(); +} + +Size SdrGrafObj::GetGrafPrefSize() const +{ + return mpGraphicObject->GetPrefSize(); +} + +void SdrGrafObj::SetGrafStreamURL( const OUString& rGraphicStreamURL ) +{ + if( rGraphicStreamURL.isEmpty() ) + { + mpGraphicObject->SetUserData(); + } + else if(getSdrModelFromSdrObject().IsSwapGraphics() ) + { + mpGraphicObject->SetUserData( rGraphicStreamURL ); + } +} + +OUString const & SdrGrafObj::GetGrafStreamURL() const +{ + return mpGraphicObject->GetUserData(); +} + +Size SdrGrafObj::getOriginalSize() const +{ + Size aSize = GetGrafPrefSize(); + + if (GetGrafPrefMapMode().GetMapUnit() == MapUnit::MapPixel) + aSize = Application::GetDefaultDevice()->PixelToLogic(aSize, MapMode(getSdrModelFromSdrObject().GetScaleUnit())); + else + aSize = OutputDevice::LogicToLogic(aSize, GetGrafPrefMapMode(), MapMode(getSdrModelFromSdrObject().GetScaleUnit())); + + if (aGrafInfo.IsCropped()) + { + const tools::Long aCroppedWidth(aSize.getWidth() - aGrafInfo.GetLeftCrop() + - aGrafInfo.GetRightCrop()); + const tools::Long aCroppedHeight(aSize.getHeight() - aGrafInfo.GetTopCrop() + - aGrafInfo.GetBottomCrop()); + + aSize = Size(aCroppedWidth, aCroppedHeight); + } + + return aSize; +} + +// TODO Remove +void SdrGrafObj::ForceSwapIn() const +{ + if (pGraphicLink && (mpGraphicObject->GetType() == GraphicType::NONE || + mpGraphicObject->GetType() == GraphicType::Default) ) + { + pGraphicLink->Update(); + } +} + +void SdrGrafObj::ImpRegisterLink() +{ + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + + if( pLinkManager != nullptr && pGraphicLink == nullptr ) + { + if (!aFileName.isEmpty()) + { + pGraphicLink = new SdrGraphicLink( *this ); + pLinkManager->InsertFileLink( + *pGraphicLink, sfx2::SvBaseLinkObjectType::ClientGraphic, aFileName, (aFilterName.isEmpty() ? nullptr : &aFilterName)); + pGraphicLink->Connect(); + } + } +} + +void SdrGrafObj::ImpDeregisterLink() +{ + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + + if( pLinkManager != nullptr && pGraphicLink!=nullptr) + { + // When using Remove, the *pGraphicLink is implicitly deleted + pLinkManager->Remove( pGraphicLink ); + pGraphicLink=nullptr; + } +} + +void SdrGrafObj::SetGraphicLink(const OUString& rFileName) +{ + Graphic aGraphic; + aGraphic.setOriginURL(rFileName); + SetGraphic(aGraphic); +} + +void SdrGrafObj::ReleaseGraphicLink() +{ + ImpDeregisterLink(); + aFileName.clear(); + aFilterName.clear(); + + auto aGraphic = mpGraphicObject->GetGraphic(); + aGraphic.setOriginURL(""); + SetGraphic(aGraphic); +} + +bool SdrGrafObj::IsLinkedGraphic() const +{ + return !mpGraphicObject->GetGraphic().getOriginURL().isEmpty(); +} + +void SdrGrafObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + bool bNoPresGrf = ( mpGraphicObject->GetType() != GraphicType::NONE ) && !m_bEmptyPresObj; + + rInfo.bResizeFreeAllowed = maGeo.m_nRotationAngle.get() % 9000 == 0 || + maGeo.m_nRotationAngle.get() % 18000 == 0 || + maGeo.m_nRotationAngle.get() % 27000 == 0; + + rInfo.bResizePropAllowed = true; + rInfo.bRotateFreeAllowed = bNoPresGrf; + rInfo.bRotate90Allowed = bNoPresGrf; + rInfo.bMirrorFreeAllowed = bNoPresGrf; + rInfo.bMirror45Allowed = bNoPresGrf; + rInfo.bMirror90Allowed = !m_bEmptyPresObj; + rInfo.bTransparenceAllowed = false; + + // #i118485# Shear allowed and possible now + rInfo.bShearAllowed = true; + + rInfo.bEdgeRadiusAllowed=false; + rInfo.bCanConvToPath = !IsEPS(); + rInfo.bCanConvToPathLineToArea = false; + rInfo.bCanConvToPolyLineToArea = false; + rInfo.bCanConvToPoly = !IsEPS(); + rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrGrafObj::GetObjIdentifier() const +{ + return SdrObjKind::Graphic; +} + +void SdrGrafObj::ImpSetLinkedGraphic( const Graphic& rGraphic ) +{ + const bool bIsChanged(getSdrModelFromSdrObject().IsChanged()); + NbcSetGraphic( rGraphic ); + ActionChanged(); + BroadcastObjectChange(); + getSdrModelFromSdrObject().SetChanged(bIsChanged); +} + +OUString SdrGrafObj::TakeObjNameSingul() const +{ + if (!mpGraphicObject) + return OUString(); + + auto const & rVectorGraphicDataPtr = mpGraphicObject->GetGraphic().getVectorGraphicData(); + + OUStringBuffer sName; + + if (rVectorGraphicDataPtr) + { + switch (rVectorGraphicDataPtr->getType()) + { + case VectorGraphicDataType::Svg: + { + sName.append(SvxResId(STR_ObjNameSingulGRAFSVG)); + break; + } + case VectorGraphicDataType::Wmf: + { + sName.append(SvxResId(STR_ObjNameSingulGRAFWMF)); + break; + } + case VectorGraphicDataType::Emf: + { + sName.append(SvxResId(STR_ObjNameSingulGRAFEMF)); + break; + } + case VectorGraphicDataType::Pdf: + { + sName.append(SvxResId(STR_ObjNameSingulGRAFPDF)); + break; + } + } //no default, see tdf#137813 + } + else + { + switch( mpGraphicObject->GetType() ) + { + case GraphicType::Bitmap: + { + TranslateId pId = ( ( mpGraphicObject->IsTransparent() || GetObjectItem( SDRATTR_GRAFTRANSPARENCE ).GetValue() ) ? + ( IsLinkedGraphic() ? STR_ObjNameSingulGRAFBMPTRANSLNK : STR_ObjNameSingulGRAFBMPTRANS ) : + ( IsLinkedGraphic() ? STR_ObjNameSingulGRAFBMPLNK : STR_ObjNameSingulGRAFBMP ) ); + + sName.append(SvxResId(pId)); + } + break; + + case GraphicType::GdiMetafile: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNameSingulGRAFMTFLNK : STR_ObjNameSingulGRAFMTF)); + break; + + case GraphicType::NONE: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNameSingulGRAFNONELNK : STR_ObjNameSingulGRAFNONE)); + break; + + default: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNameSingulGRAFLNK : STR_ObjNameSingulGRAF)); + break; + } + } + + const OUString aName(GetName()); + + if (!aName.isEmpty()) + { + sName.append(" '" + aName + "\'" ); + } + + return sName.makeStringAndClear(); +} + +OUString SdrGrafObj::TakeObjNamePlural() const +{ + if (!mpGraphicObject) + return OUString(); + + auto const & rVectorGraphicDataPtr = mpGraphicObject->GetGraphic().getVectorGraphicData(); + + OUStringBuffer sName; + + if (rVectorGraphicDataPtr) + { + switch (rVectorGraphicDataPtr->getType()) + { + case VectorGraphicDataType::Svg: + { + sName.append(SvxResId(STR_ObjNamePluralGRAFSVG)); + break; + } + case VectorGraphicDataType::Wmf: + { + sName.append(SvxResId(STR_ObjNamePluralGRAFWMF)); + break; + } + case VectorGraphicDataType::Emf: + { + sName.append(SvxResId(STR_ObjNamePluralGRAFEMF)); + break; + } + case VectorGraphicDataType::Pdf: + { + sName.append(SvxResId(STR_ObjNamePluralGRAFPDF)); + break; + } + } //no default, see tdf#137813 + } + else + { + switch(mpGraphicObject->GetType()) + { + case GraphicType::Bitmap: + { + TranslateId pId = ( ( mpGraphicObject->IsTransparent() || GetObjectItem( SDRATTR_GRAFTRANSPARENCE ).GetValue() ) ? + ( IsLinkedGraphic() ? STR_ObjNamePluralGRAFBMPTRANSLNK : STR_ObjNamePluralGRAFBMPTRANS ) : + ( IsLinkedGraphic() ? STR_ObjNamePluralGRAFBMPLNK : STR_ObjNamePluralGRAFBMP ) ); + + sName.append(SvxResId(pId)); + } + break; + + case GraphicType::GdiMetafile: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNamePluralGRAFMTFLNK : STR_ObjNamePluralGRAFMTF)); + break; + + case GraphicType::NONE: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNamePluralGRAFNONELNK : STR_ObjNamePluralGRAFNONE)); + break; + + default: + sName.append(SvxResId(IsLinkedGraphic() ? STR_ObjNamePluralGRAFLNK : STR_ObjNamePluralGRAF)); + break; + } + } + + const OUString aName(GetName()); + + if (!aName.isEmpty()) + { + sName.append(" '" + aName + "\'"); + } + + return sName.makeStringAndClear(); +} + +rtl::Reference<SdrObject> SdrGrafObj::getFullDragClone() const +{ + // call parent + rtl::Reference<SdrObject> pRetval = SdrRectObj::getFullDragClone(); + + // #i103116# the full drag clone leads to problems + // with linked graphics, so reset the link in this + // temporary interaction object and load graphic + if(pRetval && IsLinkedGraphic()) + { + static_cast< SdrGrafObj* >(pRetval.get())->ReleaseGraphicLink(); + } + + return pRetval; +} + +rtl::Reference<SdrObject> SdrGrafObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrGrafObj(rTargetModel, *this); +} + +sal_uInt32 SdrGrafObj::GetHdlCount() const +{ + return 8; +} + +void SdrGrafObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + SdrHdlList tempList(nullptr); + SdrRectObj::AddToHdlList( tempList ); + tempList.RemoveHdl(0); + tempList.MoveTo(rHdlList); +} + +void SdrGrafObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + SdrRectObj::NbcResize( rRef, xFact, yFact ); + + bool bMirrX = xFact.GetNumerator() < 0; + bool bMirrY = yFact.GetNumerator() < 0; + + if( bMirrX != bMirrY ) + bMirrored = !bMirrored; +} + +void SdrGrafObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SdrRectObj::NbcMirror(rRef1,rRef2); + bMirrored = !bMirrored; +} + +std::unique_ptr<SdrObjGeoData> SdrGrafObj::NewGeoData() const +{ + return std::make_unique<SdrGrafObjGeoData>(); +} + +void SdrGrafObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrRectObj::SaveGeoData(rGeo); + SdrGrafObjGeoData& rGGeo=static_cast<SdrGrafObjGeoData&>(rGeo); + rGGeo.bMirrored=bMirrored; +} + +void SdrGrafObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrRectObj::RestoreGeoData(rGeo); + const SdrGrafObjGeoData& rGGeo=static_cast<const SdrGrafObjGeoData&>(rGeo); + bMirrored=rGGeo.bMirrored; +} + +void SdrGrafObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + const bool bRemove(pNewPage == nullptr && pOldPage != nullptr); + const bool bInsert(pNewPage != nullptr && pOldPage == nullptr); + + if( bRemove ) + { + // No SwapIn necessary here, because if something's not loaded, it can't be animated either. + if( mpGraphicObject->IsAnimated()) + mpGraphicObject->StopAnimation(); + + if( pGraphicLink != nullptr ) + ImpDeregisterLink(); + } + + // call parent + SdrRectObj::handlePageChange(pOldPage, pNewPage); + + if (!aFileName.isEmpty() && bInsert) + { + ImpRegisterLink(); + } +} + +void SdrGrafObj::StartAnimation() +{ + SetGrafAnimationAllowed(true); +} + +bool SdrGrafObj::HasGDIMetaFile() const +{ + return( mpGraphicObject->GetType() == GraphicType::GdiMetafile ); +} + +bool SdrGrafObj::isEmbeddedVectorGraphicData() const +{ + return GraphicType::Bitmap == GetGraphicType() && GetGraphic().getVectorGraphicData(); +} + +GDIMetaFile SdrGrafObj::getMetafileFromEmbeddedVectorGraphicData() const +{ + GDIMetaFile aRetval; + + if(isEmbeddedVectorGraphicData()) + { + ScopedVclPtrInstance< VirtualDevice > pOut; + const tools::Rectangle aBoundRect(GetCurrentBoundRect()); + const MapMode aMap(getSdrModelFromSdrObject().GetScaleUnit()); + + pOut->EnableOutput(false); + pOut->SetMapMode(aMap); + aRetval.Record(pOut); + SingleObjectPainter(*pOut); + aRetval.Stop(); + aRetval.WindStart(); + aRetval.Move(-aBoundRect.Left(), -aBoundRect.Top()); + aRetval.SetPrefMapMode(aMap); + aRetval.SetPrefSize(aBoundRect.GetSize()); + } + + return aRetval; +} + +GDIMetaFile SdrGrafObj::GetMetaFile(GraphicType &rGraphicType) const +{ + if (isEmbeddedVectorGraphicData()) + { + // Embedded Vector Graphic Data + // There is currently no helper to create SdrObjects from primitives (even if I'm thinking + // about writing one for some time). To get the roundtrip to SdrObjects it is necessary to + // use the old converter path over the MetaFile mechanism. Create Metafile from Svg + // primitives here pretty directly + rGraphicType = GraphicType::GdiMetafile; + return getMetafileFromEmbeddedVectorGraphicData(); + } + else if (GraphicType::GdiMetafile == rGraphicType) + { + return GetTransformedGraphic(SdrGrafObjTransformsAttrs::MIRROR).GetGDIMetaFile(); + } + return GDIMetaFile(); +} + +rtl::Reference<SdrObject> SdrGrafObj::DoConvertToPolyObj(bool bBezier, bool bAddText ) const +{ + rtl::Reference<SdrObject> pRetval; + GraphicType aGraphicType(GetGraphicType()); + GDIMetaFile aMtf(GetMetaFile(aGraphicType)); + switch(aGraphicType) + { + case GraphicType::GdiMetafile: + { + // Sort into group and return ONLY those objects that can be created from the MetaFile. + ImpSdrGDIMetaFileImport aFilter( + getSdrModelFromSdrObject(), + GetLayer(), + getRectangle()); + rtl::Reference<SdrObjGroup> pGrp = new SdrObjGroup(getSdrModelFromSdrObject()); + + if(aFilter.DoImport(aMtf, *pGrp->GetSubList(), 0)) + { + { + // copy transformation + GeoStat aGeoStat(GetGeoStat()); + + if(aGeoStat.m_nShearAngle) + { + aGeoStat.RecalcTan(); + pGrp->NbcShear(getRectangle().TopLeft(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false); + } + + if(aGeoStat.m_nRotationAngle) + { + aGeoStat.RecalcSinCos(); + pGrp->NbcRotate(getRectangle().TopLeft(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle); + } + } + + pRetval = pGrp; + pGrp->NbcSetLayer(GetLayer()); + + if(bAddText) + { + pRetval = ImpConvertAddText(pRetval, bBezier); + } + + // convert all children + if( pRetval ) + { + pRetval = pRetval->DoConvertToPolyObj(bBezier, bAddText); + + if( pRetval ) + { + // flatten subgroups. As we call + // DoConvertToPolyObj() on the resulting group + // objects, subgroups can exist (e.g. text is + // a group object for every line). + SdrObjList* pList = pRetval->GetSubList(); + if( pList ) + pList->FlattenGroups(); + } + } + } + else + pGrp.clear(); + + // #i118485# convert line and fill + rtl::Reference<SdrObject> pLineFill = SdrRectObj::DoConvertToPolyObj(bBezier, false); + + if(pLineFill) + { + if(pRetval) + { + pGrp = dynamic_cast< SdrObjGroup* >(pRetval.get()); + + if(!pGrp) + { + pGrp = new SdrObjGroup(getSdrModelFromSdrObject()); + pGrp->NbcSetLayer(GetLayer()); + pGrp->GetSubList()->NbcInsertObject(pRetval.get()); + } + + pGrp->GetSubList()->NbcInsertObject(pLineFill.get(), 0); + } + else + { + pRetval = pLineFill; + } + } + + break; + } + case GraphicType::Bitmap: + { + // create basic object and add fill + pRetval = SdrRectObj::DoConvertToPolyObj(bBezier, bAddText); + + // save bitmap as an attribute + if(pRetval) + { + // retrieve bitmap for the fill + SfxItemSet aSet(GetObjectItemSet()); + + aSet.Put(XFillStyleItem(drawing::FillStyle_BITMAP)); + const BitmapEx aBitmapEx(GetTransformedGraphic().GetBitmapEx()); + aSet.Put(XFillBitmapItem(OUString(), Graphic(aBitmapEx))); + aSet.Put(XFillBmpTileItem(false)); + + pRetval->SetMergedItemSet(aSet); + } + break; + } + case GraphicType::NONE: + case GraphicType::Default: + { + pRetval = SdrRectObj::DoConvertToPolyObj(bBezier, bAddText); + break; + } + } + + return pRetval; +} + +void SdrGrafObj::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + SetXPolyDirty(); + SdrRectObj::Notify( rBC, rHint ); + ImpSetAttrToGrafInfo(); +} + + +void SdrGrafObj::SetMirrored( bool _bMirrored ) +{ + bMirrored = _bMirrored; +} + +void SdrGrafObj::ImpSetAttrToGrafInfo() +{ + const SfxItemSet& rSet = GetObjectItemSet(); + const sal_uInt16 nTrans = rSet.Get( SDRATTR_GRAFTRANSPARENCE ).GetValue(); + const SdrGrafCropItem& rCrop = rSet.Get( SDRATTR_GRAFCROP ); + + aGrafInfo.SetLuminance( rSet.Get( SDRATTR_GRAFLUMINANCE ).GetValue() ); + aGrafInfo.SetContrast( rSet.Get( SDRATTR_GRAFCONTRAST ).GetValue() ); + aGrafInfo.SetChannelR( rSet.Get( SDRATTR_GRAFRED ).GetValue() ); + aGrafInfo.SetChannelG( rSet.Get( SDRATTR_GRAFGREEN ).GetValue() ); + aGrafInfo.SetChannelB( rSet.Get( SDRATTR_GRAFBLUE ).GetValue() ); + aGrafInfo.SetGamma( rSet.Get( SDRATTR_GRAFGAMMA ).GetValue() * 0.01 ); + aGrafInfo.SetAlpha( 255 - static_cast<sal_uInt8>(FRound( std::min( nTrans, sal_uInt16(100) ) * 2.55 )) ); + aGrafInfo.SetInvert( rSet.Get( SDRATTR_GRAFINVERT ).GetValue() ); + aGrafInfo.SetDrawMode( rSet.Get( SDRATTR_GRAFMODE ).GetValue() ); + aGrafInfo.SetCrop( rCrop.GetLeft(), rCrop.GetTop(), rCrop.GetRight(), rCrop.GetBottom() ); + + SetXPolyDirty(); + SetBoundAndSnapRectsDirty(); +} + +void SdrGrafObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool bShrinkOnly ) +{ + Size aSize; + Size aMaxSize( rMaxRect.GetSize() ); + if (mpGraphicObject->GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel) + aSize = Application::GetDefaultDevice()->PixelToLogic(mpGraphicObject->GetPrefSize(), MapMode(MapUnit::Map100thMM)); + else + aSize = OutputDevice::LogicToLogic( mpGraphicObject->GetPrefSize(), + mpGraphicObject->GetPrefMapMode(), + MapMode( MapUnit::Map100thMM ) ); + + if( aSize.IsEmpty() ) + return; + + Point aPos( rMaxRect.TopLeft() ); + + // if the graphic is too large, fit it to page + if ( (!bShrinkOnly || + ( aSize.Height() > aMaxSize.Height() ) || + ( aSize.Width() > aMaxSize.Width() ) )&& + aSize.Height() && aMaxSize.Height() ) + { + float fGrfWH = static_cast<float>(aSize.Width()) / + static_cast<float>(aSize.Height()); + float fWinWH = static_cast<float>(aMaxSize.Width()) / + static_cast<float>(aMaxSize.Height()); + + // Scale graphic to page size + if ( fGrfWH < fWinWH ) + { + aSize.setWidth( static_cast<tools::Long>(aMaxSize.Height() * fGrfWH) ); + aSize.setHeight( aMaxSize.Height() ); + } + else if ( fGrfWH > 0.F ) + { + aSize.setWidth( aMaxSize.Width() ); + aSize.setHeight( static_cast<tools::Long>(aMaxSize.Width() / fGrfWH) ); + } + + aPos = rMaxRect.Center(); + } + + if( bShrinkOnly ) + aPos = getRectangle().TopLeft(); + + aPos.AdjustX( -(aSize.Width() / 2) ); + aPos.AdjustY( -(aSize.Height() / 2) ); + SetLogicRect( tools::Rectangle( aPos, aSize ) ); +} + +void SdrGrafObj::SetGrafAnimationAllowed(bool bNew) +{ + if(mbGrafAnimationAllowed != bNew) + { + mbGrafAnimationAllowed = bNew; + ActionChanged(); + } +} + +uno::Reference<io::XInputStream> SdrGrafObj::getInputStream() const +{ + uno::Reference<io::XInputStream> xStream; + + if (mpGraphicObject && GetGraphic().IsGfxLink()) + { + Graphic aGraphic( GetGraphic() ); + GfxLink aLink( aGraphic.GetGfxLink() ); + sal_uInt32 nSize = aLink.GetDataSize(); + const void* pSourceData = static_cast<const void*>(aLink.GetData()); + if( nSize && pSourceData ) + { + sal_uInt8 * pBuffer = new sal_uInt8[ nSize ]; + memcpy( pBuffer, pSourceData, nSize ); + + SvMemoryStream* pStream = new SvMemoryStream( static_cast<void*>(pBuffer), static_cast<std::size_t>(nSize), StreamMode::READ ); + pStream->ObjectOwnsMemory( true ); + xStream.set( new utl::OInputStreamWrapper( pStream, true ) ); + } + } + + if (!xStream.is() && !aFileName.isEmpty()) + { + SvFileStream* pStream = new SvFileStream( aFileName, StreamMode::READ ); + xStream.set( new utl::OInputStreamWrapper( pStream ) ); + } + + return xStream; +} + +// moved crop handle creation here; this is the object type using them +void SdrGrafObj::addCropHandles(SdrHdlList& rTarget) const +{ + basegfx::B2DHomMatrix aMatrix; + basegfx::B2DPolyPolygon aPolyPolygon; + + // get object transformation + TRGetBaseGeometry(aMatrix, aPolyPolygon); + + // part of object transformation correction, but used later, so defined outside next scope + double fShearX(0.0), fRotate(0.0); + + { // TTTT correct shear, it comes currently mirrored from TRGetBaseGeometry, can be removed with aw080 + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + + aMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + if(!basegfx::fTools::equalZero(fShearX)) + { + // shearX is used, correct it + fShearX = -fShearX; + } + + aMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + fShearX, + fRotate, + aTranslate); + } + + // get crop values + const SdrGrafCropItem& rCrop = GetMergedItem(SDRATTR_GRAFCROP); + + if(rCrop.GetLeft() || rCrop.GetTop() || rCrop.GetRight() ||rCrop.GetBottom()) + { + // decompose object transformation to have current translate and scale + basegfx::B2DVector aScale, aTranslate; + double fLclRotate, fLclShearX; + + aMatrix.decompose(aScale, aTranslate, fLclRotate, fLclShearX); + + if(!aScale.equalZero()) + { + // get crop scale + const basegfx::B2DVector aCropScaleFactor( + GetGraphicObject().calculateCropScaling( + aScale.getX(), + aScale.getY(), + rCrop.GetLeft(), + rCrop.GetTop(), + rCrop.GetRight(), + rCrop.GetBottom())); + + // apply crop scale + const double fCropLeft(rCrop.GetLeft() * aCropScaleFactor.getX()); + const double fCropTop(rCrop.GetTop() * aCropScaleFactor.getY()); + const double fCropRight(rCrop.GetRight() * aCropScaleFactor.getX()); + const double fCropBottom(rCrop.GetBottom() * aCropScaleFactor.getY()); + basegfx::B2DHomMatrix aMatrixForCropViewHdl(aMatrix); + + if(IsMirrored()) + { + // create corrected new matrix, TTTT can be removed with aw080 + // the old mirror only can mirror horizontally; the vertical mirror + // is faked by using the horizontal and 180 degree rotation. Since + // the object can be rotated differently from 180 degree, this is + // not safe to detect. Just correct horizontal mirror (which is + // in IsMirrored()) and keep the rotation angle + // caution: Do not modify aMatrix, it is used below to calculate + // the exact handle positions + basegfx::B2DHomMatrix aPreMultiply; + + // mirrored X, apply + aPreMultiply.translate(-0.5, 0.0); + aPreMultiply.scale(-1.0, 1.0); + aPreMultiply.translate(0.5, 0.0); + + aMatrixForCropViewHdl = aMatrixForCropViewHdl * aPreMultiply; + } + + rTarget.AddHdl( + std::make_unique<SdrCropViewHdl>( + aMatrixForCropViewHdl, + GetGraphicObject().GetGraphic(), + fCropLeft, + fCropTop, + fCropRight, + fCropBottom)); + } + } + + basegfx::B2DPoint aPos; + + aPos = aMatrix * basegfx::B2DPoint(0.0, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperLeft, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(0.5, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Upper, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(1.0, 0.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperRight, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(0.0, 0.5); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Left , fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(1.0, 0.5); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Right, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(0.0, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerLeft, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(0.5, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Lower, fShearX, fRotate)); + aPos = aMatrix * basegfx::B2DPoint(1.0, 1.0); + rTarget.AddHdl(std::make_unique<SdrCropHdl>(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerRight, fShearX, fRotate)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdogrp.cxx b/svx/source/svdraw/svdogrp.cxx new file mode 100644 index 0000000000..5878c8cd99 --- /dev/null +++ b/svx/source/svdraw/svdogrp.cxx @@ -0,0 +1,794 @@ +/* -*- 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/svdogrp.hxx> + +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdtrans.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <sdr/properties/groupproperties.hxx> +#include <sdr/contact/viewcontactofgroup.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <libxml/xmlwriter.h> +#include <vcl/canvastools.hxx> +#include <svx/diagram/IDiagramHelper.hxx> + +const std::shared_ptr< svx::diagram::IDiagramHelper >& SdrObjGroup::getDiagramHelper() const +{ + return mp_DiagramHelper; +} + +// BaseProperties section +std::unique_ptr<sdr::properties::BaseProperties> SdrObjGroup::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::GroupProperties>(*this); +} + +// DrawContact section +std::unique_ptr<sdr::contact::ViewContact> SdrObjGroup::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfGroup>(*this); +} + +SdrObjGroup::SdrObjGroup(SdrModel& rSdrModel) +: SdrObject(rSdrModel) +, SdrObjList() +, maRefPoint(0, 0) +, mp_DiagramHelper() +{ + m_bClosedObj=false; +} + +SdrObjGroup::SdrObjGroup(SdrModel& rSdrModel, SdrObjGroup const & rSource) +: SdrObject(rSdrModel, rSource) +, SdrObjList() +, maRefPoint(0, 0) +, mp_DiagramHelper() +{ + m_bClosedObj=false; + + // copy child SdrObjects + if(nullptr != rSource.GetSubList()) + { + // #i36404# Copy SubList, init model and page first + const SdrObjList& rSourceSubList(*rSource.GetSubList()); + + CopyObjects(rSourceSubList); + + // tdf#116979: needed here, we need bSnapRectDirty to be true + // which it is after using SdrObject::operator= (see above), + // but set to false again using CopyObjects + SetBoundAndSnapRectsDirty(); + } + + // copy local parameters + maRefPoint = rSource.maRefPoint; +} + +void SdrObjGroup::AddToHdlList(SdrHdlList& rHdlList) const +{ + // only for diagram, so do nothing for just groups + if(!isDiagram()) + return; + + svx::diagram::IDiagramHelper::AddAdditionalVisualization(*this, rHdlList); +} + +SdrObjGroup::~SdrObjGroup() +{ +} + +SdrPage* SdrObjGroup::getSdrPageFromSdrObjList() const +{ + return getSdrPageFromSdrObject(); +} + +SdrObject* SdrObjGroup::getSdrObjectFromSdrObjList() const +{ + return const_cast< SdrObjGroup* >(this); +} + +SdrObjList* SdrObjGroup::getChildrenOfSdrObject() const +{ + return const_cast< SdrObjGroup* >(this); +} + +void SdrObjGroup::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bNoContortion=false; + const size_t nObjCount(GetObjCount()); + for (const rtl::Reference<SdrObject>& pObj : *this) { + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo(aInfo); + if (!aInfo.bMoveAllowed ) rInfo.bMoveAllowed =false; + if (!aInfo.bResizeFreeAllowed ) rInfo.bResizeFreeAllowed =false; + if (!aInfo.bResizePropAllowed ) rInfo.bResizePropAllowed =false; + if (!aInfo.bRotateFreeAllowed ) rInfo.bRotateFreeAllowed =false; + if (!aInfo.bRotate90Allowed ) rInfo.bRotate90Allowed =false; + if (!aInfo.bMirrorFreeAllowed ) rInfo.bMirrorFreeAllowed =false; + if (!aInfo.bMirror45Allowed ) rInfo.bMirror45Allowed =false; + if (!aInfo.bMirror90Allowed ) rInfo.bMirror90Allowed =false; + if (!aInfo.bShearAllowed ) rInfo.bShearAllowed =false; + if (!aInfo.bEdgeRadiusAllowed ) rInfo.bEdgeRadiusAllowed =false; + if (!aInfo.bNoOrthoDesired ) rInfo.bNoOrthoDesired =false; + if (aInfo.bNoContortion ) rInfo.bNoContortion =true; + if (!aInfo.bCanConvToPath ) rInfo.bCanConvToPath =false; + + if(!aInfo.bCanConvToContour) + rInfo.bCanConvToContour = false; + + if (!aInfo.bCanConvToPoly ) rInfo.bCanConvToPoly =false; + if (!aInfo.bCanConvToPathLineToArea) rInfo.bCanConvToPathLineToArea=false; + if (!aInfo.bCanConvToPolyLineToArea) rInfo.bCanConvToPolyLineToArea=false; + } + if (nObjCount==0) { + rInfo.bRotateFreeAllowed=false; + rInfo.bRotate90Allowed =false; + rInfo.bMirrorFreeAllowed=false; + rInfo.bMirror45Allowed =false; + rInfo.bMirror90Allowed =false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =false; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bNoContortion =true; + } + if(nObjCount != 1) + { + // only allowed if single object selected + rInfo.bTransparenceAllowed = false; + } +} + +void SdrObjGroup::SetBoundRectDirty() +{ + // avoid resetting aOutRect which in case of this object is model data, + // not re-creatable view data +} + +SdrObjKind SdrObjGroup::GetObjIdentifier() const +{ + return SdrObjKind::Group; +} + +SdrLayerID SdrObjGroup::GetLayer() const +{ + SdrLayerID nLay = SdrObject::GetLayer(); + bool b1st = true; + for (const rtl::Reference<SdrObject>& pObject : *this) + { + SdrLayerID nLay1(pObject->GetLayer()); + if (b1st) { nLay=nLay1; b1st = false; } + else if (nLay1!=nLay) return SdrLayerID(0); + } + return nLay; +} + +void SdrObjGroup::NbcSetLayer(SdrLayerID nLayer) +{ + SdrObject::NbcSetLayer(nLayer); + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcSetLayer(nLayer); +} + +void SdrObjGroup::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + // call parent + SdrObject::handlePageChange(pOldPage, pNewPage); + + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->handlePageChange(pOldPage, pNewPage); +} + +SdrObjList* SdrObjGroup::GetSubList() const +{ + return const_cast< SdrObjGroup* >(this); +} + +static bool containsOOXData(const css::uno::Any& rVal) +{ + const css::uno::Sequence<css::beans::PropertyValue>& propList(rVal.get< css::uno::Sequence<css::beans::PropertyValue> >()); + for (const auto& rProp : std::as_const(propList)) + { + if(rProp.Name.startsWith("OOX")) + { + return true; + } + } + + return false; +} + +void SdrObjGroup::SetGrabBagItem(const css::uno::Any& rVal) +{ + // detect if the intention is to disable Diagram functionality + if(isDiagram() && !containsOOXData(rVal)) + { + css::uno::Any aOld; + GetGrabBagItem(aOld); + + if(containsOOXData(aOld)) + { + mp_DiagramHelper.reset(); + } + } + + // call parent + SdrObject::SetGrabBagItem(rVal); +} + +const tools::Rectangle& SdrObjGroup::GetCurrentBoundRect() const +{ + // <aOutRect> has to contain the bounding rectangle + if(0 != GetObjCount()) + { + setOutRectangleConst(GetAllObjBoundRect()); + } + + return getOutRectangle(); +} + +const tools::Rectangle& SdrObjGroup::GetSnapRect() const +{ + // <aOutRect> has to contain the bounding rectangle + if(0 != GetObjCount()) + { + return GetAllObjSnapRect(); + } + else + { + return getOutRectangle(); + } +} + +rtl::Reference<SdrObject> SdrObjGroup::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrObjGroup(rTargetModel, *this); +} + +OUString SdrObjGroup::TakeObjNameSingul() const +{ + OUString sName; + + if(0 == GetObjCount()) + { + sName = SvxResId(STR_ObjNameSingulGRUPEMPTY); + } + else + { + if(isDiagram()) + sName = SvxResId(STR_ObjNameSingulDIAGRAM); + else + sName = SvxResId(STR_ObjNameSingulGRUP); + } + + const OUString aName(GetName()); + + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + + +OUString SdrObjGroup::TakeObjNamePlural() const +{ + if(0 == GetObjCount()) + return SvxResId(STR_ObjNamePluralGRUPEMPTY); + if(isDiagram()) + return SvxResId(RID_GALLERYSTR_THEME_DIAGRAMS); + return SvxResId(STR_ObjNamePluralGRUP); +} + + +void SdrObjGroup::RecalcSnapRect() +{ + // TODO: unnecessary, because we use the Rects from the SubList +} + +basegfx::B2DPolyPolygon SdrObjGroup::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aRetval; + + for (const rtl::Reference<SdrObject>& pObj : *this) + aRetval.append(pObj->TakeXorPoly()); + + if(!aRetval.count()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(getOutRectangle()); + aRetval.append(basegfx::utils::createPolygonFromRect(aRange)); + } + + return aRetval; +} + +bool SdrObjGroup::beginSpecialDrag(SdrDragStat& /*rDrag*/) const +{ + return false; +} + + +bool SdrObjGroup::BegCreate(SdrDragStat& /*rStat*/) +{ + return false; +} + + +Degree100 SdrObjGroup::GetRotateAngle() const +{ + Degree100 nRetval(0); + + if(0 != GetObjCount()) + { + SdrObject* pObj(GetObj(0)); + + nRetval = pObj->GetRotateAngle(); + } + + return nRetval; +} + + +Degree100 SdrObjGroup::GetShearAngle(bool /*bVertical*/) const +{ + Degree100 nRetval(0); + + if(0 != GetObjCount()) + { + SdrObject* pObj(GetObj(0)); + + nRetval = pObj->GetShearAngle(); + } + + return nRetval; +} + + +void SdrObjGroup::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aOld(GetSnapRect()); + tools::Long nMulX=rRect.Right()-rRect.Left(); + tools::Long nDivX=aOld.Right()-aOld.Left(); + tools::Long nMulY=rRect.Bottom()-rRect.Top(); + tools::Long nDivY=aOld.Bottom()-aOld.Top(); + if (nDivX==0) { nMulX=1; nDivX=1; } + if (nDivY==0) { nMulY=1; nDivY=1; } + if (nMulX!=nDivX || nMulY!=nDivY) { + Fraction aX(nMulX,nDivX); + Fraction aY(nMulY,nDivY); + NbcResize(aOld.TopLeft(),aX,aY); + } + if (rRect.Left()!=aOld.Left() || rRect.Top()!=aOld.Top()) { + NbcMove(Size(rRect.Left()-aOld.Left(),rRect.Top()-aOld.Top())); + } +} + + +void SdrObjGroup::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + NbcSetSnapRect(rRect); +} + + +void SdrObjGroup::NbcMove(const Size& rSize) +{ + maRefPoint.Move(rSize); + const size_t nObjCount(GetObjCount()); + + if(0 != nObjCount) + { + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcMove(rSize); + } + else + { + moveOutRectangle(rSize.Width(), rSize.Height()); + SetBoundAndSnapRectsDirty(); + } +} + + +void SdrObjGroup::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0); + bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0); + if (bXMirr || bYMirr) { + Point aRef1(GetSnapRect().Center()); + if (bXMirr) { + Point aRef2(aRef1); + aRef2.AdjustY( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + if (bYMirr) { + Point aRef2(aRef1); + aRef2.AdjustX( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + } + + ResizePoint(maRefPoint, rRef, xFact, yFact); + + const size_t nObjCount(GetObjCount()); + if(0 != nObjCount) + { + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcResize(rRef,xFact,yFact); + } + else + { + auto aRectangle = getOutRectangle(); + ResizeRect(aRectangle, rRef, xFact, yFact); + setOutRectangle(aRectangle); + + SetBoundAndSnapRectsDirty(); + } +} + + +void SdrObjGroup::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + SetGlueReallyAbsolute(true); + RotatePoint(maRefPoint, rRef, sn, cs); + + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcRotate(rRef,nAngle,sn,cs); + + NbcRotateGluePoints(rRef,nAngle,sn,cs); + SetGlueReallyAbsolute(false); +} + + +void SdrObjGroup::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SetGlueReallyAbsolute(true); + MirrorPoint(maRefPoint, rRef1, rRef2); // implementation missing in SvdEtc! + + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcMirror(rRef1,rRef2); + + NbcMirrorGluePoints(rRef1,rRef2); + SetGlueReallyAbsolute(false); +} + + +void SdrObjGroup::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + SetGlueReallyAbsolute(true); + ShearPoint(maRefPoint, rRef, tn); + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + pObj->NbcShear(rRef,nAngle,tn,bVShear); + } + + NbcShearGluePoints(rRef,tn,bVShear); + SetGlueReallyAbsolute(false); +} + + +void SdrObjGroup::NbcSetAnchorPos(const Point& rPnt) +{ + m_aAnchor=rPnt; + Size aSiz(rPnt.X()-m_aAnchor.X(),rPnt.Y()-m_aAnchor.Y()); + maRefPoint.Move(aSiz); + + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->NbcSetAnchorPos(rPnt); +} + + +void SdrObjGroup::SetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + tools::Rectangle aOld(GetSnapRect()); + if (aOld.IsEmpty()) + { + Fraction aX(1,1); + Fraction aY(1,1); + Resize(aOld.TopLeft(),aX,aY); + } + else + { + tools::Long nMulX=rRect.Right()-rRect.Left(); + tools::Long nDivX=aOld.Right()-aOld.Left(); + tools::Long nMulY=rRect.Bottom()-rRect.Top(); + tools::Long nDivY=aOld.Bottom()-aOld.Top(); + if (nDivX==0) { nMulX=1; nDivX=1; } + if (nDivY==0) { nMulY=1; nDivY=1; } + if (nMulX!=nDivX || nMulY!=nDivY) { + Fraction aX(nMulX,nDivX); + Fraction aY(nMulY,nDivY); + Resize(aOld.TopLeft(),aX,aY); + } + } + if (rRect.Left()!=aOld.Left() || rRect.Top()!=aOld.Top()) { + Move(Size(rRect.Left()-aOld.Left(),rRect.Top()-aOld.Top())); + } + + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +void SdrObjGroup::SetLogicRect(const tools::Rectangle& rRect) +{ + SetSnapRect(rRect); +} + + +void SdrObjGroup::Move(const Size& rSiz) +{ + if (rSiz.Width()==0 && rSiz.Height()==0) + return; + + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + maRefPoint.Move(rSiz); + const size_t nObjCount(GetObjCount()); + + if(0 != nObjCount) + { + // first move the connectors, then everything else + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->Move(rSiz); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->Move(rSiz); + } + } + else + { + moveOutRectangle(rSiz.Width(), rSiz.Height()); + SetBoundAndSnapRectsDirty(); + } + + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); +} + + +void SdrObjGroup::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bUnsetRelative) +{ + if (xFact.GetNumerator()==xFact.GetDenominator() && yFact.GetNumerator()==yFact.GetDenominator()) + return; + + bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0); + bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0); + if (bXMirr || bYMirr) { + Point aRef1(GetSnapRect().Center()); + if (bXMirr) { + Point aRef2(aRef1); + aRef2.AdjustY( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + if (bYMirr) { + Point aRef2(aRef1); + aRef2.AdjustX( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + } + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + ResizePoint(maRefPoint, rRef, xFact, yFact); + const size_t nObjCount(GetObjCount()); + + if(0 != nObjCount) + { + // move the connectors first, everything else afterwards + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->Resize(rRef,xFact,yFact,bUnsetRelative); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->Resize(rRef,xFact,yFact,bUnsetRelative); + } + } + else + { + auto aRectangle = getOutRectangle(); + ResizeRect(aRectangle, rRef, xFact, yFact); + setOutRectangle(aRectangle); + + SetBoundAndSnapRectsDirty(); + } + + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +void SdrObjGroup::Rotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + if (nAngle==0_deg100) + return; + + SetGlueReallyAbsolute(true); + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + RotatePoint(maRefPoint, rRef, sn, cs); + // move the connectors first, everything else afterwards + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->Rotate(rRef,nAngle,sn,cs); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->Rotate(rRef,nAngle,sn,cs); + } + + NbcRotateGluePoints(rRef,nAngle,sn,cs); + SetGlueReallyAbsolute(false); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +void SdrObjGroup::Mirror(const Point& rRef1, const Point& rRef2) +{ + SetGlueReallyAbsolute(true); + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + MirrorPoint(maRefPoint, rRef1, rRef2); // implementation missing in SvdEtc! + // move the connectors first, everything else afterwards + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->Mirror(rRef1,rRef2); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->Mirror(rRef1,rRef2); + } + + NbcMirrorGluePoints(rRef1,rRef2); + SetGlueReallyAbsolute(false); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +void SdrObjGroup::Shear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + if (nAngle==0_deg100) + return; + + SetGlueReallyAbsolute(true); + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + ShearPoint(maRefPoint, rRef, tn); + // move the connectors first, everything else afterwards + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->Shear(rRef,nAngle,tn,bVShear); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->Shear(rRef,nAngle,tn,bVShear); + } + + NbcShearGluePoints(rRef,tn,bVShear); + SetGlueReallyAbsolute(false); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + +} + + +void SdrObjGroup::SetAnchorPos(const Point& rPnt) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + bool bChg=m_aAnchor!=rPnt; + m_aAnchor=rPnt; + Size aSiz(rPnt.X()-m_aAnchor.X(),rPnt.Y()-m_aAnchor.Y()); + maRefPoint.Move(aSiz); + // move the connectors first, everything else afterwards + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->IsEdgeObj()) + pObj->SetAnchorPos(rPnt); + } + + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->IsEdgeObj()) + pObj->SetAnchorPos(rPnt); + } + + if (bChg) + { + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} + + +void SdrObjGroup::NbcSetRelativePos(const Point& rPnt) +{ + Point aRelPos0(GetSnapRect().TopLeft()-m_aAnchor); + Size aSiz(rPnt.X()-aRelPos0.X(),rPnt.Y()-aRelPos0.Y()); + NbcMove(aSiz); // this also calls SetRectsDirty() +} + +void SdrObjGroup::SetRelativePos(const Point& rPnt) +{ + Point aRelPos0(GetSnapRect().TopLeft()-m_aAnchor); + Size aSiz(rPnt.X()-aRelPos0.X(),rPnt.Y()-aRelPos0.Y()); + if (aSiz.Width()!=0 || aSiz.Height()!=0) Move(aSiz); // this also calls SetRectsDirty() and Broadcast, ... +} + +void SdrObjGroup::NbcReformatText() +{ + NbcReformatAllTextObjects(); +} + +rtl::Reference<SdrObject> SdrObjGroup::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + rtl::Reference<SdrObject> pGroup( new SdrObjGroup(getSdrModelFromSdrObject()) ); + + for (const rtl::Reference<SdrObject>& pIterObj : *this) + { + rtl::Reference<SdrObject> pResult(pIterObj->DoConvertToPolyObj(bBezier, bAddText)); + + // pResult can be NULL e.g. for empty objects + if( pResult ) + pGroup->GetSubList()->NbcInsertObject(pResult.get()); + } + + return pGroup; +} + +void SdrObjGroup::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrObjGroup")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + SdrObject::dumpAsXml(pWriter); + SdrObjList::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdomeas.cxx b/svx/source/svdraw/svdomeas.cxx new file mode 100644 index 0000000000..9831cf9124 --- /dev/null +++ b/svx/source/svdraw/svdomeas.cxx @@ -0,0 +1,1429 @@ +/* -*- 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/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editobj.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/measfld.hxx> +#include <editeng/outlobj.hxx> +#include <math.h> +#include <svl/style.hxx> + +#include <sdr/contact/viewcontactofsdrmeasureobj.hxx> +#include <sdr/properties/measureproperties.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdomeas.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdview.hxx> +#include <svx/sxmbritm.hxx> +#include <svx/sxmlhitm.hxx> +#include <sxmsitm.hxx> +#include <sxmtaitm.hxx> +#include <svx/sxmtfitm.hxx> +#include <svx/sxmtpitm.hxx> +#include <svx/sxmtritm.hxx> +#include <svx/sxmuitm.hxx> +#include <svx/xlnedcit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstcit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xpoly.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/localedatawrapper.hxx> +#include <vcl/ptrstyle.hxx> + + +SdrMeasureObjGeoData::SdrMeasureObjGeoData() {} +SdrMeasureObjGeoData::~SdrMeasureObjGeoData() {} + +OUString SdrMeasureObj::TakeRepresentation(SdrMeasureFieldKind eMeasureFieldKind) const +{ + OUString aStr; + Fraction aMeasureScale(1, 1); + bool bTextRota90(false); + bool bShowUnit(false); + FieldUnit eMeasureUnit(FieldUnit::NONE); + FieldUnit eModUIUnit(FieldUnit::NONE); + + const SfxItemSet& rSet = GetMergedItemSet(); + bTextRota90 = rSet.Get(SDRATTR_MEASURETEXTROTA90).GetValue(); + eMeasureUnit = rSet.Get(SDRATTR_MEASUREUNIT).GetValue(); + aMeasureScale = rSet.Get(SDRATTR_MEASURESCALE).GetValue(); + bShowUnit = rSet.Get(SDRATTR_MEASURESHOWUNIT).GetValue(); + sal_Int16 nNumDigits = rSet.Get(SDRATTR_MEASUREDECIMALPLACES).GetValue(); + + switch(eMeasureFieldKind) + { + case SdrMeasureFieldKind::Value: + { + eModUIUnit = getSdrModelFromSdrObject().GetUIUnit(); + + if(eMeasureUnit == FieldUnit::NONE) + eMeasureUnit = eModUIUnit; + + sal_Int32 nLen(GetLen(aPt2 - aPt1)); + Fraction aFact(1,1); + + if(eMeasureUnit != eModUIUnit) + { + // for the unit conversion + aFact *= GetMapFactor(eModUIUnit, eMeasureUnit).X(); + } + + if(aMeasureScale.GetNumerator() != aMeasureScale.GetDenominator()) + { + aFact *= aMeasureScale; + } + + if(aFact.GetNumerator() != aFact.GetDenominator()) + { + // scale via BigInt, to avoid overruns + nLen = BigMulDiv(nLen, aFact.GetNumerator(), aFact.GetDenominator()); + } + + if(!aFact.IsValid()) + { + aStr = "?"; + } + else + { + aStr = getSdrModelFromSdrObject().GetMetricString(nLen, true, nNumDigits); + } + + SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleDataWrapper = aSysLocale.GetLocaleData(); + sal_Unicode cDec(rLocaleDataWrapper.getNumDecimalSep()[0]); + sal_Unicode cDecAlt(rLocaleDataWrapper.getNumDecimalSepAlt().toChar()); + + if(aStr.indexOf(cDec) != -1 || (cDecAlt && aStr.indexOf(cDecAlt) != -1)) + { + sal_Int32 nLen2(aStr.getLength() - 1); + + while(aStr[nLen2] == '0') + { + aStr = aStr.copy(0, nLen2); + nLen2--; + } + + if(aStr[nLen2] == cDec || (cDecAlt && aStr[nLen2] == cDecAlt)) + { + aStr = aStr.copy(0, nLen2); + nLen2--; + } + + if(aStr.isEmpty()) + aStr += "0"; + } + + break; + } + case SdrMeasureFieldKind::Unit: + { + if(bShowUnit) + { + eModUIUnit = getSdrModelFromSdrObject().GetUIUnit(); + + if(eMeasureUnit == FieldUnit::NONE) + eMeasureUnit = eModUIUnit; + + aStr = SdrModel::GetUnitString(eMeasureUnit); + } + + break; + } + case SdrMeasureFieldKind::Rotate90Blanks: + { + if(bTextRota90) + { + aStr = " "; + } + + break; + } + } + return aStr; +} + + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrMeasureObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::MeasureProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrMeasureObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrMeasureObj>(*this); +} + + +SdrMeasureObj::SdrMeasureObj(SdrModel& rSdrModel) +: SdrTextObj(rSdrModel), + bTextDirty(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; +} + +SdrMeasureObj::SdrMeasureObj(SdrModel& rSdrModel, SdrMeasureObj const & rSource) +: SdrTextObj(rSdrModel, rSource), + bTextDirty(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; + + aPt1 = rSource.aPt1; + aPt2 = rSource.aPt2; + bTextDirty = rSource.bTextDirty; +} + +SdrMeasureObj::SdrMeasureObj( + SdrModel& rSdrModel, + const Point& rPt1, + const Point& rPt2) +: SdrTextObj(rSdrModel), + aPt1(rPt1), + aPt2(rPt2), + bTextDirty(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = false; +} + +SdrMeasureObj::~SdrMeasureObj() +{ +} + +void SdrMeasureObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bMoveAllowed =true; + rInfo.bResizeFreeAllowed=true; + rInfo.bResizePropAllowed=true; + rInfo.bRotateFreeAllowed=true; + rInfo.bRotate90Allowed =true; + rInfo.bMirrorFreeAllowed=true; + rInfo.bMirror45Allowed =true; + rInfo.bMirror90Allowed =true; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =true; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bNoOrthoDesired =true; + rInfo.bNoContortion =false; + rInfo.bCanConvToPath =false; + rInfo.bCanConvToPoly =true; + rInfo.bCanConvToPathLineToArea=false; + rInfo.bCanConvToPolyLineToArea=false; + rInfo.bCanConvToContour = LineGeometryUsageIsNecessary(); +} + +SdrObjKind SdrMeasureObj::GetObjIdentifier() const +{ + return SdrObjKind::Measure; +} + +struct ImpMeasureRec : public SdrDragStatUserData +{ + Point aPt1; + Point aPt2; + css::drawing::MeasureTextHorzPos eWantTextHPos; + css::drawing::MeasureTextVertPos eWantTextVPos; + tools::Long nLineDist; + tools::Long nHelplineOverhang; + tools::Long nHelplineDist; + tools::Long nHelpline1Len; + tools::Long nHelpline2Len; + bool bBelowRefEdge; + bool bTextRota90; + bool bTextUpsideDown; + bool bTextAutoAngle; + Degree100 nTextAutoAngleView; +}; + +namespace { + +struct ImpLineRec +{ + Point aP1; + Point aP2; +}; + +} + +struct ImpMeasurePoly +{ + ImpLineRec aMainline1; // those with the 1st arrowhead + ImpLineRec aMainline2; // those with the 2nd arrowhead + ImpLineRec aMainline3; // those in between + ImpLineRec aHelpline1; + ImpLineRec aHelpline2; + Size aTextSize; + tools::Long nLineLen; + Degree100 nLineAngle; + Degree100 nTextAngle; + Degree100 nHlpAngle; + double nLineSin; + double nLineCos; + sal_uInt16 nMainlineCnt; + css::drawing::MeasureTextHorzPos eUsedTextHPos; + css::drawing::MeasureTextVertPos eUsedTextVPos; + tools::Long nLineWdt2; // half the line width + tools::Long nArrow1Len; // length of 1st arrowhead; for Center, use only half + tools::Long nArrow2Len; // length of 2nd arrowhead; for Center, use only half + tools::Long nArrow1Wdt; // width of 1st arrow + tools::Long nArrow2Wdt; // width of 2nd arrow + tools::Long nShortLineLen; // line length, if PfeileAussen (arrowheads on the outside) + bool bAutoUpsideDown; // UpsideDown via automation + bool bBreakedLine; +}; + +void SdrMeasureObj::ImpTakeAttr(ImpMeasureRec& rRec) const +{ + rRec.aPt1 = aPt1; + rRec.aPt2 = aPt2; + + const SfxItemSet& rSet = GetObjectItemSet(); + rRec.eWantTextHPos =rSet.Get(SDRATTR_MEASURETEXTHPOS ).GetValue(); + rRec.eWantTextVPos =rSet.Get(SDRATTR_MEASURETEXTVPOS ).GetValue(); + rRec.nLineDist =rSet.Get(SDRATTR_MEASURELINEDIST ).GetValue(); + rRec.nHelplineOverhang =rSet.Get(SDRATTR_MEASUREHELPLINEOVERHANG).GetValue(); + rRec.nHelplineDist =rSet.Get(SDRATTR_MEASUREHELPLINEDIST ).GetValue(); + rRec.nHelpline1Len =rSet.Get(SDRATTR_MEASUREHELPLINE1LEN ).GetValue(); + rRec.nHelpline2Len =rSet.Get(SDRATTR_MEASUREHELPLINE2LEN ).GetValue(); + rRec.bBelowRefEdge =rSet.Get(SDRATTR_MEASUREBELOWREFEDGE ).GetValue(); + rRec.bTextRota90 =rSet.Get(SDRATTR_MEASURETEXTROTA90 ).GetValue(); + rRec.bTextUpsideDown =static_cast<const SdrMeasureTextUpsideDownItem& >(rSet.Get(SDRATTR_MEASURETEXTUPSIDEDOWN )).GetValue(); + rRec.bTextAutoAngle =rSet.Get(SDRATTR_MEASURETEXTAUTOANGLE ).GetValue(); + rRec.nTextAutoAngleView=static_cast<const SdrMeasureTextAutoAngleViewItem&>(rSet.Get(SDRATTR_MEASURETEXTAUTOANGLEVIEW)).GetValue(); +} + +static tools::Long impGetLineStartEndDistance(const basegfx::B2DPolyPolygon& rPolyPolygon, tools::Long nNewWidth, bool bCenter) +{ + const basegfx::B2DRange aPolygonRange(rPolyPolygon.getB2DRange()); + const double fOldWidth(std::max(aPolygonRange.getWidth(), 1.0)); + const double fScale(static_cast<double>(nNewWidth) / fOldWidth); + tools::Long nHeight(basegfx::fround(aPolygonRange.getHeight() * fScale)); + + if(bCenter) + { + nHeight /= 2; + } + + return nHeight; +} + +void SdrMeasureObj::ImpCalcGeometrics(const ImpMeasureRec& rRec, ImpMeasurePoly& rPol) const +{ + Point aP1(rRec.aPt1); + Point aP2(rRec.aPt2); + Point aDelt(aP2); aDelt-=aP1; + + rPol.aTextSize=GetTextSize(); + rPol.nLineLen=GetLen(aDelt); + + rPol.nLineWdt2=0; + tools::Long nArrow1Len=0; bool bArrow1Center=false; + tools::Long nArrow2Len=0; bool bArrow2Center=false; + tools::Long nArrow1Wdt=0; + tools::Long nArrow2Wdt=0; + rPol.nArrow1Wdt=0; + rPol.nArrow2Wdt=0; + tools::Long nArrowNeed=0; + tools::Long nShortLen=0; + bool bPfeileAussen = false; + + const SfxItemSet& rSet = GetObjectItemSet(); + sal_Int32 nLineWdt = rSet.Get(XATTR_LINEWIDTH).GetValue(); // line width + rPol.nLineWdt2 = (nLineWdt + 1) / 2; + + nArrow1Wdt = rSet.Get(XATTR_LINESTARTWIDTH).GetValue(); + if(nArrow1Wdt < 0) + nArrow1Wdt = -nLineWdt * nArrow1Wdt / 100; // <0 = relative + + nArrow2Wdt = rSet.Get(XATTR_LINEENDWIDTH).GetValue(); + if(nArrow2Wdt < 0) + nArrow2Wdt = -nLineWdt * nArrow2Wdt / 100; // <0 = relative + + basegfx::B2DPolyPolygon aPol1(rSet.Get(XATTR_LINESTART).GetLineStartValue()); + basegfx::B2DPolyPolygon aPol2(rSet.Get(XATTR_LINEEND).GetLineEndValue()); + bArrow1Center = rSet.Get(XATTR_LINESTARTCENTER).GetValue(); + bArrow2Center = rSet.Get(XATTR_LINEENDCENTER).GetValue(); + nArrow1Len = impGetLineStartEndDistance(aPol1, nArrow1Wdt, bArrow1Center) - 1; + nArrow2Len = impGetLineStartEndDistance(aPol2, nArrow2Wdt, bArrow2Center) - 1; + + // nArrowLen is already halved at bCenter. + // In the case of 2 arrowheads each 4mm long, we can't go below 10mm. + nArrowNeed=nArrow1Len+nArrow2Len+(nArrow1Wdt+nArrow2Wdt)/2; + if (rPol.nLineLen<nArrowNeed) bPfeileAussen = true; + nShortLen=(nArrow1Len+nArrow1Wdt + nArrow2Len+nArrow2Wdt) /2; + + rPol.eUsedTextHPos=rRec.eWantTextHPos; + rPol.eUsedTextVPos=rRec.eWantTextVPos; + if (rPol.eUsedTextVPos == css::drawing::MeasureTextVertPos_AUTO) + rPol.eUsedTextVPos = css::drawing::MeasureTextVertPos_EAST; + bool bBrkLine=false; + if (rPol.eUsedTextVPos == css::drawing::MeasureTextVertPos_CENTERED) + { + OutlinerParaObject* pOutlinerParaObject = SdrTextObj::GetOutlinerParaObject(); + if (pOutlinerParaObject!=nullptr && pOutlinerParaObject->GetTextObject().GetParagraphCount()==1) + { + bBrkLine=true; // dashed line if there's only on paragraph. + } + } + rPol.bBreakedLine=bBrkLine; + if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_AUTO) { // if text is too wide, push it outside + bool bOutside = false; + tools::Long nNeedSiz=!rRec.bTextRota90 ? rPol.aTextSize.Width() : rPol.aTextSize.Height(); + if (nNeedSiz>rPol.nLineLen) bOutside = true; // text doesn't fit in between + if (bBrkLine) { + if (nNeedSiz+nArrowNeed>rPol.nLineLen) bPfeileAussen = true; // text fits in between, if arrowheads are on the outside + } else { + tools::Long nSmallNeed=nArrow1Len+nArrow2Len+(nArrow1Wdt+nArrow2Wdt)/2/4; + if (nNeedSiz+nSmallNeed>rPol.nLineLen) bPfeileAussen = true; // text fits in between, if arrowheads are on the outside + } + rPol.eUsedTextHPos=bOutside ? css::drawing::MeasureTextHorzPos_LEFTOUTSIDE : css::drawing::MeasureTextHorzPos_INSIDE; + } + if (rPol.eUsedTextHPos != css::drawing::MeasureTextHorzPos_INSIDE) bPfeileAussen = true; + rPol.nArrow1Wdt=nArrow1Wdt; + rPol.nArrow2Wdt=nArrow2Wdt; + rPol.nShortLineLen=nShortLen; + rPol.nArrow1Len=nArrow1Len; + rPol.nArrow2Len=nArrow2Len; + + rPol.nLineAngle=GetAngle(aDelt); + double a = toRadians(rPol.nLineAngle); + double nLineSin=sin(a); + double nLineCos=cos(a); + rPol.nLineSin=nLineSin; + rPol.nLineCos=nLineCos; + + rPol.nTextAngle=rPol.nLineAngle; + if (rRec.bTextRota90) rPol.nTextAngle+=9000_deg100; + + rPol.bAutoUpsideDown=false; + if (rRec.bTextAutoAngle) { + Degree100 nTmpAngle=NormAngle36000(rPol.nTextAngle-rRec.nTextAutoAngleView); + if (nTmpAngle>=18000_deg100) { + rPol.nTextAngle+=18000_deg100; + rPol.bAutoUpsideDown=true; + } + } + + if (rRec.bTextUpsideDown) rPol.nTextAngle+=18000_deg100; + rPol.nTextAngle=NormAngle36000(rPol.nTextAngle); + rPol.nHlpAngle=rPol.nLineAngle+9000_deg100; + if (rRec.bBelowRefEdge) rPol.nHlpAngle+=18000_deg100; + rPol.nHlpAngle=NormAngle36000(rPol.nHlpAngle); + double nHlpSin=nLineCos; + double nHlpCos=-nLineSin; + if (rRec.bBelowRefEdge) { + nHlpSin=-nHlpSin; + nHlpCos=-nHlpCos; + } + + tools::Long nLineDist=rRec.nLineDist; + tools::Long nOverhang=rRec.nHelplineOverhang; + tools::Long nHelplineDist=rRec.nHelplineDist; + + tools::Long dx= FRound(nLineDist*nHlpCos); + tools::Long dy=-FRound(nLineDist*nHlpSin); + tools::Long dxh1a= FRound((nHelplineDist-rRec.nHelpline1Len)*nHlpCos); + tools::Long dyh1a=-FRound((nHelplineDist-rRec.nHelpline1Len)*nHlpSin); + tools::Long dxh1b= FRound((nHelplineDist-rRec.nHelpline2Len)*nHlpCos); + tools::Long dyh1b=-FRound((nHelplineDist-rRec.nHelpline2Len)*nHlpSin); + tools::Long dxh2= FRound((nLineDist+nOverhang)*nHlpCos); + tools::Long dyh2=-FRound((nLineDist+nOverhang)*nHlpSin); + + // extension line 1 + rPol.aHelpline1.aP1=Point(aP1.X()+dxh1a,aP1.Y()+dyh1a); + rPol.aHelpline1.aP2=Point(aP1.X()+dxh2,aP1.Y()+dyh2); + + // extension line 2 + rPol.aHelpline2.aP1=Point(aP2.X()+dxh1b,aP2.Y()+dyh1b); + rPol.aHelpline2.aP2=Point(aP2.X()+dxh2,aP2.Y()+dyh2); + + // dimension line + Point aMainlinePt1(aP1.X()+dx,aP1.Y()+dy); + Point aMainlinePt2(aP2.X()+dx,aP2.Y()+dy); + if (!bPfeileAussen) { + rPol.aMainline1.aP1=aMainlinePt1; + rPol.aMainline1.aP2=aMainlinePt2; + rPol.aMainline2=rPol.aMainline1; + rPol.aMainline3=rPol.aMainline1; + rPol.nMainlineCnt=1; + if (bBrkLine) { + tools::Long nNeedSiz=!rRec.bTextRota90 ? rPol.aTextSize.Width() : rPol.aTextSize.Height(); + tools::Long nHalfLen=(rPol.nLineLen-nNeedSiz-nArrow1Wdt/4-nArrow2Wdt/4) /2; + rPol.nMainlineCnt=2; + rPol.aMainline1.aP2=aMainlinePt1; + rPol.aMainline1.aP2.AdjustX(nHalfLen ); + RotatePoint(rPol.aMainline1.aP2,rPol.aMainline1.aP1,nLineSin,nLineCos); + rPol.aMainline2.aP1=aMainlinePt2; + rPol.aMainline2.aP1.AdjustX( -nHalfLen ); + RotatePoint(rPol.aMainline2.aP1,rPol.aMainline2.aP2,nLineSin,nLineCos); + } + } else { + tools::Long nLen1=nShortLen; // arrowhead's width as line length outside of the arrowhead + tools::Long nLen2=nShortLen; + tools::Long nTextWdt=rRec.bTextRota90 ? rPol.aTextSize.Height() : rPol.aTextSize.Width(); + if (!bBrkLine) { + if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) nLen1=nArrow1Len+nTextWdt; + if (rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) nLen2=nArrow2Len+nTextWdt; + } + rPol.aMainline1.aP1=aMainlinePt1; + rPol.aMainline1.aP2=aMainlinePt1; rPol.aMainline1.aP2.AdjustX( -nLen1 ); RotatePoint(rPol.aMainline1.aP2,aMainlinePt1,nLineSin,nLineCos); + rPol.aMainline2.aP1=aMainlinePt2; rPol.aMainline2.aP1.AdjustX(nLen2 ); RotatePoint(rPol.aMainline2.aP1,aMainlinePt2,nLineSin,nLineCos); + rPol.aMainline2.aP2=aMainlinePt2; + rPol.aMainline3.aP1=aMainlinePt1; + rPol.aMainline3.aP2=aMainlinePt2; + rPol.nMainlineCnt=3; + if (bBrkLine && rPol.eUsedTextHPos==css::drawing::MeasureTextHorzPos_INSIDE) rPol.nMainlineCnt=2; + } +} + +basegfx::B2DPolyPolygon SdrMeasureObj::ImpCalcXPoly(const ImpMeasurePoly& rPol) +{ + basegfx::B2DPolyPolygon aRetval; + basegfx::B2DPolygon aPartPolyA; + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline1.aP1.X(), rPol.aMainline1.aP1.Y())); + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline1.aP2.X(), rPol.aMainline1.aP2.Y())); + aRetval.append(aPartPolyA); + + if(rPol.nMainlineCnt > 1) + { + aPartPolyA.clear(); + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline2.aP1.X(), rPol.aMainline2.aP1.Y())); + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline2.aP2.X(), rPol.aMainline2.aP2.Y())); + aRetval.append(aPartPolyA); + } + + if(rPol.nMainlineCnt > 2) + { + aPartPolyA.clear(); + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline3.aP1.X(), rPol.aMainline3.aP1.Y())); + aPartPolyA.append(basegfx::B2DPoint(rPol.aMainline3.aP2.X(), rPol.aMainline3.aP2.Y())); + aRetval.append(aPartPolyA); + } + + aPartPolyA.clear(); + aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline1.aP1.X(), rPol.aHelpline1.aP1.Y())); + aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline1.aP2.X(), rPol.aHelpline1.aP2.Y())); + aRetval.append(aPartPolyA); + + aPartPolyA.clear(); + aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline2.aP1.X(), rPol.aHelpline2.aP1.Y())); + aPartPolyA.append(basegfx::B2DPoint(rPol.aHelpline2.aP2.X(), rPol.aHelpline2.aP2.Y())); + aRetval.append(aPartPolyA); + + return aRetval; +} + +bool SdrMeasureObj::CalcFieldValue(const SvxFieldItem& rField, sal_Int32 nPara, sal_uInt16 nPos, + bool bEdit, + std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle, OUString& rRet) const +{ + const SvxFieldData* pField=rField.GetField(); + const SdrMeasureField* pMeasureField=dynamic_cast<const SdrMeasureField*>( pField ); + if (pMeasureField!=nullptr) { + rRet = TakeRepresentation(pMeasureField->GetMeasureFieldKind()); + if (rpFldColor && !bEdit) + { + rpFldColor.reset(); + } + return true; + } else { + return SdrTextObj::CalcFieldValue(rField,nPara,nPos,bEdit,rpTxtColor,rpFldColor,rpFldLineStyle,rRet); + } +} + +void SdrMeasureObj::UndirtyText() const +{ + if (!bTextDirty) + return; + + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + OutlinerParaObject* pOutlinerParaObject = SdrTextObj::GetOutlinerParaObject(); + if(pOutlinerParaObject==nullptr) + { + rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Rotate90Blanks), EE_FEATURE_FIELD), ESelection(0,0)); + rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Value), EE_FEATURE_FIELD),ESelection(0,1)); + rOutliner.QuickInsertText(" ", ESelection(0,2)); + rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Unit), EE_FEATURE_FIELD),ESelection(0,3)); + rOutliner.QuickInsertField(SvxFieldItem(SdrMeasureField(SdrMeasureFieldKind::Rotate90Blanks), EE_FEATURE_FIELD),ESelection(0,4)); + + if(GetStyleSheet()) + rOutliner.SetStyleSheet(0, GetStyleSheet()); + + rOutliner.SetParaAttribs(0, GetObjectItemSet()); + + // cast to nonconst + const_cast<SdrMeasureObj*>(this)->NbcSetOutlinerParaObject( rOutliner.CreateParaObject() ); + } + else + { + rOutliner.SetText(*pOutlinerParaObject); + } + + rOutliner.SetUpdateLayout(true); + rOutliner.UpdateFields(); + Size aSiz(rOutliner.CalcTextSize()); + rOutliner.Clear(); + // cast to nonconst three times + const_cast<SdrMeasureObj*>(this)->maTextSize = aSiz; + const_cast<SdrMeasureObj*>(this)->mbTextSizeDirty = false; + const_cast<SdrMeasureObj*>(this)->bTextDirty = false; +} + +void SdrMeasureObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + if (bTextDirty) UndirtyText(); + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + ImpTakeAttr(aRec); + ImpCalcGeometrics(aRec,aMPol); + + // determine TextSize including text frame margins + Size aTextSize2(aMPol.aTextSize); + if (aTextSize2.Width()<1) aTextSize2.setWidth(1 ); + if (aTextSize2.Height()<1) aTextSize2.setHeight(1 ); + aTextSize2.AdjustWidth(GetTextLeftDistance()+GetTextRightDistance() ); + aTextSize2.AdjustHeight(GetTextUpperDistance()+GetTextLowerDistance() ); + + Point aPt1b(aMPol.aMainline1.aP1); + tools::Long nLen=aMPol.nLineLen; + tools::Long nLWdt=aMPol.nLineWdt2; + tools::Long nArr1Len=aMPol.nArrow1Len; + tools::Long nArr2Len=aMPol.nArrow2Len; + if (aMPol.bBreakedLine) { + // In the case of a dashed line and Outside, the text should be + // placed next to the line at the arrowhead instead of directly + // at the arrowhead. + nArr1Len=aMPol.nShortLineLen+aMPol.nArrow1Wdt/4; + nArr2Len=aMPol.nShortLineLen+aMPol.nArrow2Wdt/4; + } + + Point aTextPos; + bool bRota90=aRec.bTextRota90; + bool bUpsideDown=aRec.bTextUpsideDown!=aMPol.bAutoUpsideDown; + bool bBelowRefEdge=aRec.bBelowRefEdge; + css::drawing::MeasureTextHorzPos eMH=aMPol.eUsedTextHPos; + css::drawing::MeasureTextVertPos eMV=aMPol.eUsedTextVPos; + if (!bRota90) { + switch (eMH) { + case css::drawing::MeasureTextHorzPos_LEFTOUTSIDE: aTextPos.setX(aPt1b.X()-aTextSize2.Width()-nArr1Len-nLWdt ); break; + case css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE: aTextPos.setX(aPt1b.X()+nLen+nArr2Len+nLWdt ); break; + default: aTextPos.setX(aPt1b.X() ); aTextSize2.setWidth(nLen ); + } + switch (eMV) { + case css::drawing::MeasureTextVertPos_CENTERED: + aTextPos.setY(aPt1b.Y()-aTextSize2.Height()/2 ); break; + case css::drawing::MeasureTextVertPos_WEST: { + if (!bUpsideDown) aTextPos.setY(aPt1b.Y()+nLWdt ); + else aTextPos.setY(aPt1b.Y()-aTextSize2.Height()-nLWdt ); + } break; + default: { + if (!bUpsideDown) aTextPos.setY(aPt1b.Y()-aTextSize2.Height()-nLWdt ); + else aTextPos.setY(aPt1b.Y()+nLWdt ); + } + } + if (bUpsideDown) { + aTextPos.AdjustX(aTextSize2.Width() ); + aTextPos.AdjustY(aTextSize2.Height() ); + } + } else { // also if bTextRota90==TRUE + switch (eMH) { + case css::drawing::MeasureTextHorzPos_LEFTOUTSIDE: aTextPos.setX(aPt1b.X()-aTextSize2.Height()-nArr1Len ); break; + case css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE: aTextPos.setX(aPt1b.X()+nLen+nArr2Len ); break; + default: aTextPos.setX(aPt1b.X() ); aTextSize2.setHeight(nLen ); + } + switch (eMV) { + case css::drawing::MeasureTextVertPos_CENTERED: + aTextPos.setY(aPt1b.Y()+aTextSize2.Width()/2 ); break; + case css::drawing::MeasureTextVertPos_WEST: { + if (!bBelowRefEdge) aTextPos.setY(aPt1b.Y()+aTextSize2.Width()+nLWdt ); + else aTextPos.setY(aPt1b.Y()-nLWdt ); + } break; + default: { + if (!bBelowRefEdge) aTextPos.setY(aPt1b.Y()-nLWdt ); + else aTextPos.setY(aPt1b.Y()+aTextSize2.Width()+nLWdt ); + } + } + if (bUpsideDown) { + aTextPos.AdjustX(aTextSize2.Height() ); + aTextPos.AdjustY( -(aTextSize2.Width()) ); + } + } + if (aMPol.nTextAngle != maGeo.m_nRotationAngle) { + const_cast<SdrMeasureObj*>(this)->maGeo.m_nRotationAngle=aMPol.nTextAngle; + const_cast<SdrMeasureObj*>(this)->maGeo.RecalcSinCos(); + } + RotatePoint(aTextPos,aPt1b,aMPol.nLineSin,aMPol.nLineCos); + aTextSize2.AdjustWidth( 1 ); aTextSize2.AdjustHeight( 1 ); // because of the Rect-Ctor's odd behavior + rRect=tools::Rectangle(aTextPos,aTextSize2); + rRect.Normalize(); + const_cast<SdrMeasureObj*>(this)->setRectangle(rRect); + + if (aMPol.nTextAngle != maGeo.m_nRotationAngle) { + const_cast<SdrMeasureObj*>(this)->maGeo.m_nRotationAngle=aMPol.nTextAngle; + const_cast<SdrMeasureObj*>(this)->maGeo.RecalcSinCos(); + } +} + +rtl::Reference<SdrObject> SdrMeasureObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrMeasureObj(rTargetModel, *this); +} + +OUString SdrMeasureObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulMEASURE)); + + OUString aName( GetName() ); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrMeasureObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralMEASURE); +} + +basegfx::B2DPolyPolygon SdrMeasureObj::TakeXorPoly() const +{ + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + ImpTakeAttr(aRec); + ImpCalcGeometrics(aRec,aMPol); + return ImpCalcXPoly(aMPol); +} + +sal_uInt32 SdrMeasureObj::GetHdlCount() const +{ + return 6; +} + +void SdrMeasureObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + ImpTakeAttr(aRec); + aRec.nHelplineDist=0; + ImpCalcGeometrics(aRec,aMPol); + + for (sal_uInt32 nHdlNum=0; nHdlNum<6; ++nHdlNum) + { + Point aPt; + switch (nHdlNum) { + case 0: aPt=aMPol.aHelpline1.aP1; break; + case 1: aPt=aMPol.aHelpline2.aP1; break; + case 2: aPt=aPt1; break; + case 3: aPt=aPt2; break; + case 4: aPt=aMPol.aHelpline1.aP2; break; + case 5: aPt=aMPol.aHelpline2.aP2; break; + } // switch + std::unique_ptr<SdrHdl> pHdl(new ImpMeasureHdl(aPt,SdrHdlKind::User)); + pHdl->SetObjHdlNum(nHdlNum); + pHdl->SetRotationAngle(aMPol.nLineAngle); + rHdlList.AddHdl(std::move(pHdl)); + } +} + + +bool SdrMeasureObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrMeasureObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + + if(pHdl) + { + const sal_uInt32 nHdlNum(pHdl->GetObjHdlNum()); + + if(nHdlNum != 2 && nHdlNum != 3) + { + rDrag.SetEndDragChangesAttributes(true); + } + + return true; + } + + return false; +} + +bool SdrMeasureObj::applySpecialDrag(SdrDragStat& rDrag) +{ + ImpMeasureRec aMeasureRec; + const SdrHdl* pHdl = rDrag.GetHdl(); + const sal_uInt32 nHdlNum(pHdl->GetObjHdlNum()); + + ImpTakeAttr(aMeasureRec); + ImpEvalDrag(aMeasureRec, rDrag); + + switch (nHdlNum) + { + case 2: + { + aPt1 = aMeasureRec.aPt1; + SetTextDirty(); + break; + } + case 3: + { + aPt2 = aMeasureRec.aPt2; + SetTextDirty(); + break; + } + default: + { + switch(nHdlNum) + { + case 0: + case 1: + { + ImpMeasureRec aOrigMeasureRec; + ImpTakeAttr(aOrigMeasureRec); + + if(aMeasureRec.nHelpline1Len != aOrigMeasureRec.nHelpline1Len) + { + SetObjectItem(makeSdrMeasureHelpline1LenItem(aMeasureRec.nHelpline1Len)); + } + + if(aMeasureRec.nHelpline2Len != aOrigMeasureRec.nHelpline2Len) + { + SetObjectItem(makeSdrMeasureHelpline2LenItem(aMeasureRec.nHelpline2Len)); + } + + break; + } + + case 4: + case 5: + { + ImpMeasureRec aOrigMeasureRec; + ImpTakeAttr(aOrigMeasureRec); + + if(aMeasureRec.nLineDist != aOrigMeasureRec.nLineDist) + { + SetObjectItem(makeSdrMeasureLineDistItem(aMeasureRec.nLineDist)); + } + + if(aMeasureRec.bBelowRefEdge != aOrigMeasureRec.bBelowRefEdge) + { + SetObjectItem(SdrMeasureBelowRefEdgeItem(aMeasureRec.bBelowRefEdge)); + } + } + } + } + } // switch + + SetBoundAndSnapRectsDirty(); + SetChanged(); + + return true; +} + +OUString SdrMeasureObj::getSpecialDragComment(const SdrDragStat& /*rDrag*/) const +{ + return OUString(); +} + +void SdrMeasureObj::ImpEvalDrag(ImpMeasureRec& rRec, const SdrDragStat& rDrag) const +{ + Degree100 nLineAngle=GetAngle(rRec.aPt2-rRec.aPt1); + double a = toRadians(nLineAngle); + double nSin=sin(a); + double nCos=cos(a); + + const SdrHdl* pHdl=rDrag.GetHdl(); + sal_uInt32 nHdlNum(pHdl->GetObjHdlNum()); + bool bOrtho=rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho(); + bool bBigOrtho=bOrtho && rDrag.GetView()->IsBigOrtho(); + bool bBelow=rRec.bBelowRefEdge; + Point aPt(rDrag.GetNow()); + + switch (nHdlNum) { + case 0: { + RotatePoint(aPt,aPt1,nSin,-nCos); + rRec.nHelpline1Len=aPt1.Y()-aPt.Y(); + if (bBelow) rRec.nHelpline1Len=-rRec.nHelpline1Len; + if (bOrtho) rRec.nHelpline2Len=rRec.nHelpline1Len; + } break; + case 1: { + RotatePoint(aPt,aPt2,nSin,-nCos); + rRec.nHelpline2Len=aPt2.Y()-aPt.Y(); + if (bBelow) rRec.nHelpline2Len=-rRec.nHelpline2Len; + if (bOrtho) rRec.nHelpline1Len=rRec.nHelpline2Len; + } break; + case 2: case 3: { + bool bAnf=nHdlNum==2; + Point& rMov=bAnf ? rRec.aPt1 : rRec.aPt2; + Point aMov(rMov); + Point aFix(bAnf ? rRec.aPt2 : rRec.aPt1); + if (bOrtho) { + tools::Long ndx0=aMov.X()-aFix.X(); + tools::Long ndy0=aMov.Y()-aFix.Y(); + bool bHLin=ndy0==0; + bool bVLin=ndx0==0; + if (!bHLin || !bVLin) { // else aPt1==aPt2 + tools::Long ndx=aPt.X()-aFix.X(); + tools::Long ndy=aPt.Y()-aFix.Y(); + double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0); + double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0); + bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho); + bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho); + if (bHor) ndy=tools::Long(ndy0*nXFact); + if (bVer) ndx=tools::Long(ndx0*nYFact); + aPt=aFix; + aPt.AdjustX(ndx ); + aPt.AdjustY(ndy ); + } // else Ortho8 + } + rMov=aPt; + } break; + case 4: case 5: { + tools::Long nVal0=rRec.nLineDist; + RotatePoint(aPt,(nHdlNum==4 ? aPt1 : aPt2),nSin,-nCos); + rRec.nLineDist=aPt.Y()- (nHdlNum==4 ? aPt1.Y() : aPt2.Y()); + if (bBelow) rRec.nLineDist=-rRec.nLineDist; + if (rRec.nLineDist<0) { + rRec.nLineDist=-rRec.nLineDist; + rRec.bBelowRefEdge=!bBelow; + } + rRec.nLineDist-=rRec.nHelplineOverhang; + if (bOrtho) rRec.nLineDist=nVal0; + } break; + } // switch +} + + +bool SdrMeasureObj::BegCreate(SdrDragStat& rStat) +{ + rStat.SetOrtho8Possible(); + aPt1=rStat.GetStart(); + aPt2=rStat.GetNow(); + SetTextDirty(); + return true; +} + +bool SdrMeasureObj::MovCreate(SdrDragStat& rStat) +{ + SdrView* pView=rStat.GetView(); + aPt1=rStat.GetStart(); + aPt2=rStat.GetNow(); + if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) { + aPt1+=aPt1; + aPt1-=rStat.GetNow(); + } + SetTextDirty(); + SetBoundRectDirty(); + m_bSnapRectDirty=true; + return true; +} + +bool SdrMeasureObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + SetTextDirty(); + SetBoundAndSnapRectsDirty(); + return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2); +} + +bool SdrMeasureObj::BckCreate(SdrDragStat& /*rStat*/) +{ + return false; +} + +void SdrMeasureObj::BrkCreate(SdrDragStat& /*rStat*/) +{ +} + +basegfx::B2DPolyPolygon SdrMeasureObj::TakeCreatePoly(const SdrDragStat& /*rDrag*/) const +{ + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + + ImpTakeAttr(aRec); + ImpCalcGeometrics(aRec, aMPol); + + return ImpCalcXPoly(aMPol); +} + +PointerStyle SdrMeasureObj::GetCreatePointer() const +{ + return PointerStyle::Cross; +} + +void SdrMeasureObj::NbcMove(const Size& rSiz) +{ + SdrTextObj::NbcMove(rSiz); + aPt1.Move(rSiz); + aPt2.Move(rSiz); +} + +void SdrMeasureObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + SdrTextObj::NbcResize(rRef,xFact,yFact); + ResizePoint(aPt1,rRef,xFact,yFact); + ResizePoint(aPt2,rRef,xFact,yFact); + SetTextDirty(); +} + +void SdrMeasureObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + SdrTextObj::NbcRotate(rRef,nAngle,sn,cs); + tools::Long nLen0=GetLen(aPt2-aPt1); + RotatePoint(aPt1,rRef,sn,cs); + RotatePoint(aPt2,rRef,sn,cs); + tools::Long nLen1=GetLen(aPt2-aPt1); + if (nLen1!=nLen0) { // rounding error! + tools::Long dx=aPt2.X()-aPt1.X(); + tools::Long dy=aPt2.Y()-aPt1.Y(); + dx=BigMulDiv(dx,nLen0,nLen1); + dy=BigMulDiv(dy,nLen0,nLen1); + if (rRef==aPt2) { + aPt1.setX(aPt2.X()-dx ); + aPt1.setY(aPt2.Y()-dy ); + } else { + aPt2.setX(aPt1.X()+dx ); + aPt2.setY(aPt1.Y()+dy ); + } + } + SetBoundAndSnapRectsDirty(); +} + +void SdrMeasureObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SdrTextObj::NbcMirror(rRef1,rRef2); + MirrorPoint(aPt1,rRef1,rRef2); + MirrorPoint(aPt2,rRef1,rRef2); + SetBoundAndSnapRectsDirty(); +} + +void SdrMeasureObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear); + ShearPoint(aPt1,rRef,tn,bVShear); + ShearPoint(aPt2,rRef,tn,bVShear); + SetBoundAndSnapRectsDirty(); + SetTextDirty(); +} + +Degree100 SdrMeasureObj::GetRotateAngle() const +{ + return GetAngle(aPt2-aPt1); +} + +void SdrMeasureObj::RecalcSnapRect() +{ + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + XPolyPolygon aXPP; + + ImpTakeAttr(aRec); + ImpCalcGeometrics(aRec, aMPol); + aXPP = XPolyPolygon(ImpCalcXPoly(aMPol)); + maSnapRect = aXPP.GetBoundRect(); +} + +sal_uInt32 SdrMeasureObj::GetSnapPointCount() const +{ + return 2; +} + +Point SdrMeasureObj::GetSnapPoint(sal_uInt32 i) const +{ + if (i==0) return aPt1; + else return aPt2; +} + +bool SdrMeasureObj::IsPolyObj() const +{ + return true; +} + +sal_uInt32 SdrMeasureObj::GetPointCount() const +{ + return 2; +} + +Point SdrMeasureObj::GetPoint(sal_uInt32 i) const +{ + return (0 == i) ? aPt1 : aPt2; +} + +void SdrMeasureObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i) +{ + if (0 == i) + aPt1=rPnt; + if (1 == i) + aPt2=rPnt; + SetBoundAndSnapRectsDirty(); + SetTextDirty(); +} + +std::unique_ptr<SdrObjGeoData> SdrMeasureObj::NewGeoData() const +{ + return std::make_unique<SdrMeasureObjGeoData>(); +} + +void SdrMeasureObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrTextObj::SaveGeoData(rGeo); + SdrMeasureObjGeoData& rMGeo=static_cast<SdrMeasureObjGeoData&>(rGeo); + rMGeo.aPt1=aPt1; + rMGeo.aPt2=aPt2; +} + +void SdrMeasureObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrTextObj::RestoreGeoData(rGeo); + const SdrMeasureObjGeoData& rMGeo=static_cast<const SdrMeasureObjGeoData&>(rGeo); + aPt1=rMGeo.aPt1; + aPt2=rMGeo.aPt2; + SetTextDirty(); +} + +rtl::Reference<SdrObject> SdrMeasureObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + // get XOR Poly as base + XPolyPolygon aTmpPolyPolygon(TakeXorPoly()); + + // get local ItemSet and StyleSheet + SfxItemSet aSet(GetObjectItemSet()); + SfxStyleSheet* pStyleSheet = GetStyleSheet(); + + // prepare group + rtl::Reference<SdrObjGroup> pGroup(new SdrObjGroup(getSdrModelFromSdrObject())); + + // prepare parameters + basegfx::B2DPolyPolygon aPolyPoly; + rtl::Reference<SdrPathObj> pPath; + sal_uInt16 nCount(aTmpPolyPolygon.Count()); + sal_uInt16 nLoopStart(0); + + if(nCount == 3) + { + // three lines, first one is the middle one + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon()); + + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + aSet.Put(XLineStartWidthItem(0)); + aSet.Put(XLineEndWidthItem(0)); + nLoopStart = 1; + } + else if(nCount == 4) + { + // four lines, middle line with gap, so there are two lines used + // which have one arrow each + sal_Int32 nEndWidth = aSet.Get(XATTR_LINEENDWIDTH).GetValue(); + aSet.Put(XLineEndWidthItem(0)); + + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon()); + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + + aSet.Put(XLineEndWidthItem(nEndWidth)); + aSet.Put(XLineStartWidthItem(0)); + + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[1].getB2DPolygon()); + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + + aSet.Put(XLineEndWidthItem(0)); + nLoopStart = 2; + } + else if(nCount == 5) + { + // five lines, first two are the outer ones + sal_Int32 nEndWidth = aSet.Get(XATTR_LINEENDWIDTH).GetValue(); + + aSet.Put(XLineEndWidthItem(0)); + + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[0].getB2DPolygon()); + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + + aSet.Put(XLineEndWidthItem(nEndWidth)); + aSet.Put(XLineStartWidthItem(0)); + + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[1].getB2DPolygon()); + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + + aSet.Put(XLineEndWidthItem(0)); + nLoopStart = 2; + } + + for(;nLoopStart<nCount;nLoopStart++) + { + aPolyPoly.clear(); + aPolyPoly.append(aTmpPolyPolygon[nLoopStart].getB2DPolygon()); + pPath = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + aPolyPoly); + + pPath->SetMergedItemSet(aSet); + pPath->SetStyleSheet(pStyleSheet, true); + + pGroup->GetSubList()->NbcInsertObject(pPath.get()); + } + + if(bAddText) + { + return ImpConvertAddText(std::move(pGroup), bBezier); + } + else + { + return pGroup; + } +} + +bool SdrMeasureObj::BegTextEdit(SdrOutliner& rOutl) +{ + UndirtyText(); + return SdrTextObj::BegTextEdit(rOutl); +} + +const Size& SdrMeasureObj::GetTextSize() const +{ + if (bTextDirty) UndirtyText(); + return SdrTextObj::GetTextSize(); +} + +OutlinerParaObject* SdrMeasureObj::GetOutlinerParaObject() const +{ + if(bTextDirty) + UndirtyText(); + return SdrTextObj::GetOutlinerParaObject(); +} + +void SdrMeasureObj::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject) +{ + SdrTextObj::NbcSetOutlinerParaObject(std::move(pTextObject)); + if(SdrTextObj::GetOutlinerParaObject()) + SetTextDirty(); // recalculate text +} + +void SdrMeasureObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, + tools::Rectangle* pAnchorRect, bool bLineWidth ) const +{ + if (bTextDirty) UndirtyText(); + SdrTextObj::TakeTextRect( rOutliner, rTextRect, bNoEditText, pAnchorRect, bLineWidth ); +} + +void SdrMeasureObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const +{ + if (bTextDirty) UndirtyText(); + SdrTextObj::TakeTextAnchorRect(rAnchorRect); +} + +void SdrMeasureObj::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const +{ + if (bTextDirty) UndirtyText(); + SdrTextObj::TakeTextEditArea(pPaperMin,pPaperMax,pViewInit,pViewMin); +} + +EEAnchorMode SdrMeasureObj::GetOutlinerViewAnchorMode() const +{ + if (bTextDirty) UndirtyText(); + ImpMeasureRec aRec; + ImpMeasurePoly aMPol; + ImpTakeAttr(aRec); + ImpCalcGeometrics(aRec,aMPol); + + SdrTextHorzAdjust eTH=GetTextHorizontalAdjust(); + SdrTextVertAdjust eTV=GetTextVerticalAdjust(); + css::drawing::MeasureTextHorzPos eMH = aMPol.eUsedTextHPos; + css::drawing::MeasureTextVertPos eMV = aMPol.eUsedTextVPos; + bool bTextRota90=aRec.bTextRota90; + bool bBelowRefEdge=aRec.bBelowRefEdge; + + // TODO: bTextUpsideDown should be interpreted here! + if (!bTextRota90) { + if (eMH==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) eTH=SDRTEXTHORZADJUST_RIGHT; + if (eMH==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) eTH=SDRTEXTHORZADJUST_LEFT; + // at eMH==css::drawing::MeasureTextHorzPos_INSIDE we can anchor horizontally + if (eMV==css::drawing::MeasureTextVertPos_EAST) eTV=SDRTEXTVERTADJUST_BOTTOM; + if (eMV==css::drawing::MeasureTextVertPos_WEST) eTV=SDRTEXTVERTADJUST_TOP; + if (eMV==css::drawing::MeasureTextVertPos_CENTERED) eTV=SDRTEXTVERTADJUST_CENTER; + } else { + if (eMH==css::drawing::MeasureTextHorzPos_LEFTOUTSIDE) eTV=SDRTEXTVERTADJUST_BOTTOM; + if (eMH==css::drawing::MeasureTextHorzPos_RIGHTOUTSIDE) eTV=SDRTEXTVERTADJUST_TOP; + // at eMH==css::drawing::MeasureTextHorzPos_INSIDE we can anchor vertically + if (!bBelowRefEdge) { + if (eMV==css::drawing::MeasureTextVertPos_EAST) eTH=SDRTEXTHORZADJUST_LEFT; + if (eMV==css::drawing::MeasureTextVertPos_WEST) eTH=SDRTEXTHORZADJUST_RIGHT; + } else { + if (eMV==css::drawing::MeasureTextVertPos_EAST) eTH=SDRTEXTHORZADJUST_RIGHT; + if (eMV==css::drawing::MeasureTextVertPos_WEST) eTH=SDRTEXTHORZADJUST_LEFT; + } + if (eMV==css::drawing::MeasureTextVertPos_CENTERED) eTH=SDRTEXTHORZADJUST_CENTER; + } + + EEAnchorMode eRet=EEAnchorMode::BottomHCenter; + if (eTH==SDRTEXTHORZADJUST_LEFT) { + if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopLeft; + else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomLeft; + else eRet=EEAnchorMode::VCenterLeft; + } else if (eTH==SDRTEXTHORZADJUST_RIGHT) { + if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopRight; + else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomRight; + else eRet=EEAnchorMode::VCenterRight; + } else { + if (eTV==SDRTEXTVERTADJUST_TOP) eRet=EEAnchorMode::TopHCenter; + else if (eTV==SDRTEXTVERTADJUST_BOTTOM) eRet=EEAnchorMode::BottomHCenter; + else eRet=EEAnchorMode::VCenterHCenter; + } + return eRet; +} + + +// #i97878# +// TRGetBaseGeometry/TRSetBaseGeometry needs to be based on two positions, +// same as line geometry in SdrPathObj. Thus needs to be overridden and +// implemented since currently it is derived from SdrTextObj which uses +// a functionality based on SnapRect which is not useful here + +bool SdrMeasureObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const +{ + // handle the same as a simple line since the definition is based on two points + const basegfx::B2DRange aRange(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y()); + basegfx::B2DTuple aScale(aRange.getRange()); + basegfx::B2DTuple aTranslate(aRange.getMinimum()); + + // position maybe relative to anchor position, convert + if( getSdrModelFromSdrObject().IsWriter() ) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build return value matrix + rMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(aScale, aTranslate); + + return true; +} + +void SdrMeasureObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/) +{ + // use given transformation to derive the two defining points from unit line + basegfx::B2DPoint aPosA(rMatrix * basegfx::B2DPoint(0.0, 0.0)); + basegfx::B2DPoint aPosB(rMatrix * basegfx::B2DPoint(1.0, 0.0)); + + if( getSdrModelFromSdrObject().IsWriter() ) + { + // if anchor is used, make position relative to it + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + const basegfx::B2DVector aAnchorOffset(GetAnchorPos().X(), GetAnchorPos().Y()); + + aPosA += aAnchorOffset; + aPosB += aAnchorOffset; + } + } + + // derive new model data + const Point aNewPt1(basegfx::fround(aPosA.getX()), basegfx::fround(aPosA.getY())); + const Point aNewPt2(basegfx::fround(aPosB.getX()), basegfx::fround(aPosB.getY())); + + if(aNewPt1 == aPt1 && aNewPt2 == aPt2) + return; + + // set model values and broadcast + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + + aPt1 = aNewPt1; + aPt2 = aNewPt2; + + SetTextDirty(); + ActionChanged(); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdomedia.cxx b/svx/source/svdraw/svdomedia.cxx new file mode 100644 index 0000000000..125b69312c --- /dev/null +++ b/svx/source/svdraw/svdomedia.cxx @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column:100 -*- */ +/* + * 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 <config_features.h> + +#include <svx/svdomedia.hxx> + +#include <com/sun/star/text/GraphicCrop.hpp> + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <ucbhelper/content.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> +#include <boost/property_tree/json_parser.hpp> + +#include <vcl/svapp.hxx> + +#include <svx/svdmodel.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/sdr/contact/viewcontactofsdrmediaobj.hxx> +#include <avmedia/mediawindow.hxx> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; + + +struct SdrMediaObj::Impl +{ + ::avmedia::MediaItem m_MediaProperties; +#if HAVE_FEATURE_AVMEDIA + // Note: the temp file is read only, until it is deleted! + // It may be shared between multiple documents in case of copy/paste, + // hence the shared_ptr. + std::shared_ptr< ::avmedia::MediaTempFile > m_pTempFile; +#endif + uno::Reference< graphic::XGraphic > m_xCachedSnapshot; + rtl::Reference<avmedia::PlayerListener> m_xPlayerListener; + OUString m_LastFailedPkgURL; +}; + +SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel) +: SdrRectObj(rSdrModel) + ,m_xImpl( new Impl ) +{ +} + +SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel, SdrMediaObj const & rSource) +: SdrRectObj(rSdrModel, rSource) + ,m_xImpl( new Impl ) +{ +#if HAVE_FEATURE_AVMEDIA + m_xImpl->m_pTempFile = rSource.m_xImpl->m_pTempFile; // before props +#endif + setMediaProperties( rSource.getMediaProperties() ); + m_xImpl->m_xCachedSnapshot = rSource.m_xImpl->m_xCachedSnapshot; +} + +SdrMediaObj::SdrMediaObj( + SdrModel& rSdrModel, + const tools::Rectangle& rRect) +: SdrRectObj(rSdrModel, rRect) + ,m_xImpl( new Impl ) +{ + osl_atomic_increment(&m_refCount); + + const bool bUndo(rSdrModel.IsUndoEnabled()); + rSdrModel.EnableUndo(false); + MakeNameUnique(); + rSdrModel.EnableUndo(bUndo); + + osl_atomic_decrement(&m_refCount); +} + +SdrMediaObj::~SdrMediaObj() +{ +} + +bool SdrMediaObj::HasTextEdit() const +{ + return false; +} + +std::unique_ptr<sdr::contact::ViewContact> SdrMediaObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrMediaObj>( *this ); +} + +void SdrMediaObj::TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const +{ + rInfo.bMoveAllowed = true; + rInfo.bResizeFreeAllowed = true; + rInfo.bResizePropAllowed = true; + rInfo.bRotateFreeAllowed = false; + rInfo.bRotate90Allowed = false; + rInfo.bMirrorFreeAllowed = false; + rInfo.bMirror45Allowed = false; + rInfo.bMirror90Allowed = false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed = false; + rInfo.bEdgeRadiusAllowed = false; + rInfo.bNoOrthoDesired = false; + rInfo.bNoContortion = false; + rInfo.bCanConvToPath = false; + rInfo.bCanConvToPoly = false; + rInfo.bCanConvToContour = false; + rInfo.bCanConvToPathLineToArea = false; + rInfo.bCanConvToPolyLineToArea = false; +} + +SdrObjKind SdrMediaObj::GetObjIdentifier() const +{ + return SdrObjKind::Media; +} + +OUString SdrMediaObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulMEDIA)); + + OUString aName(GetName()); + + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrMediaObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralMEDIA); +} + +rtl::Reference<SdrObject> SdrMediaObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrMediaObj(rTargetModel, *this); +} + +uno::Reference< graphic::XGraphic > const & SdrMediaObj::getSnapshot() const +{ +#if HAVE_FEATURE_AVMEDIA + if( !m_xImpl->m_xCachedSnapshot.is() ) + { + Graphic aGraphic = m_xImpl->m_MediaProperties.getGraphic(); + if (!aGraphic.IsNone()) + { + Size aPref = aGraphic.GetPrefSize(); + Size aPixel = aGraphic.GetSizePixel(); + const text::GraphicCrop& rCrop = m_xImpl->m_MediaProperties.getCrop(); + if (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0) + { + tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth(); + tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight(); + tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth(); + tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight(); + BitmapEx aBitmapEx = aGraphic.GetBitmapEx(); + aBitmapEx.Crop({nLeft, nTop, aPixel.getWidth() - nRight, aPixel.getHeight() - nBottom}); + aGraphic = aBitmapEx; + } + + // We have an explicit graphic for this media object, then go with that instead of + // generating our own one. + m_xImpl->m_xCachedSnapshot = aGraphic.GetXGraphic(); + return m_xImpl->m_xCachedSnapshot; + } + + OUString aRealURL = m_xImpl->m_MediaProperties.getTempURL(); + if( aRealURL.isEmpty() ) + aRealURL = m_xImpl->m_MediaProperties.getURL(); + OUString sReferer = m_xImpl->m_MediaProperties.getReferer(); + OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType(); + uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot; + + m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener( + [this, xCachedSnapshot, aRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){ + SolarMutexGuard g; + uno::Reference<graphic::XGraphic> xGraphic + = m_xImpl->m_MediaProperties.getGraphic().GetXGraphic(); + m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic); + ActionChanged(); + })); + + avmedia::MediaWindow::grabFrame(aRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener); + } +#endif + return m_xImpl->m_xCachedSnapshot; +} + +void SdrMediaObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */ ) +{ + Size aSize( Application::GetDefaultDevice()->PixelToLogic( + static_cast< sdr::contact::ViewContactOfSdrMediaObj& >( GetViewContact() ).getPreferredSize(), + MapMode(MapUnit::Map100thMM)) ); + Size aMaxSize( rMaxRect.GetSize() ); + + if( aSize.IsEmpty() ) + return; + + Point aPos( rMaxRect.TopLeft() ); + + // if graphic is too large, fit it to the page + if ( (!bShrinkOnly || + ( aSize.Height() > aMaxSize.Height() ) || + ( aSize.Width() > aMaxSize.Width() ) )&& + aSize.Height() && aMaxSize.Height() ) + { + float fGrfWH = static_cast<float>(aSize.Width()) / + static_cast<float>(aSize.Height()); + float fWinWH = static_cast<float>(aMaxSize.Width()) / + static_cast<float>(aMaxSize.Height()); + + // scale graphic to page size + if ( fGrfWH < fWinWH ) + { + aSize.setWidth( static_cast<tools::Long>(aMaxSize.Height() * fGrfWH) ); + aSize.setHeight( aMaxSize.Height() ); + } + else if ( fGrfWH > 0.F ) + { + aSize.setWidth( aMaxSize.Width() ); + aSize.setHeight( static_cast<tools::Long>(aMaxSize.Width() / fGrfWH) ); + } + + aPos = rMaxRect.Center(); + } + + if( bShrinkOnly ) + aPos = getRectangle().TopLeft(); + + aPos.AdjustX( -(aSize.Width() / 2) ); + aPos.AdjustY( -(aSize.Height() / 2) ); + SetLogicRect( tools::Rectangle( aPos, aSize ) ); +} + +void SdrMediaObj::setURL(const OUString& rURL, const OUString& rReferer) +{ + ::avmedia::MediaItem aURLItem; +#if HAVE_FEATURE_AVMEDIA + aURLItem.setURL( rURL, "", rReferer ); +#else + (void) rURL; + (void) rReferer; +#endif + setMediaProperties( aURLItem ); +} + +const OUString& SdrMediaObj::getURL() const +{ +#if HAVE_FEATURE_AVMEDIA + return m_xImpl->m_MediaProperties.getURL(); +#else + static OUString ret; + return ret; +#endif +} + +const OUString& SdrMediaObj::getTempURL() const +{ +#if HAVE_FEATURE_AVMEDIA + return m_xImpl->m_MediaProperties.getTempURL(); +#else +static OUString ret; + return ret; +#endif +} + +void SdrMediaObj::setMediaProperties( const ::avmedia::MediaItem& rState ) +{ + mediaPropertiesChanged( rState ); + static_cast< sdr::contact::ViewContactOfSdrMediaObj& >( GetViewContact() ).executeMediaItem( getMediaProperties() ); +} + +const ::avmedia::MediaItem& SdrMediaObj::getMediaProperties() const +{ + return m_xImpl->m_MediaProperties; +} + +uno::Reference<io::XInputStream> SdrMediaObj::GetInputStream() const +{ +#if HAVE_FEATURE_AVMEDIA + if (!m_xImpl->m_pTempFile) + { + SAL_WARN("svx", "this is only intended for embedded media"); + return nullptr; + } + ucbhelper::Content tempFile(m_xImpl->m_pTempFile->m_TempFileURL, + uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + return tempFile.openStream(); +#else + return nullptr; +#endif +} + +void SdrMediaObj::SetInputStream(uno::Reference<io::XInputStream> const& xStream) +{ +#if !HAVE_FEATURE_AVMEDIA + (void) xStream; +#else + if (m_xImpl->m_pTempFile || m_xImpl->m_LastFailedPkgURL.isEmpty()) + { + SAL_WARN("svx", "this is only intended for embedded media"); + return; + } + + OUString tempFileURL; + const bool bSuccess( + ::avmedia::CreateMediaTempFile( + xStream, + tempFileURL, + u"")); + + if (bSuccess) + { + m_xImpl->m_pTempFile = std::make_shared<::avmedia::MediaTempFile>(tempFileURL); + m_xImpl->m_MediaProperties.setURL( + m_xImpl->m_LastFailedPkgURL, tempFileURL, ""); + } + m_xImpl->m_LastFailedPkgURL.clear(); // once only +#endif +} + +/// copy a stream from XStorage to temp file +#if HAVE_FEATURE_AVMEDIA +static bool lcl_HandlePackageURL( + OUString const & rURL, + const SdrModel& rModel, + OUString & o_rTempFileURL) +{ + ::comphelper::LifecycleProxy sourceProxy; + uno::Reference<io::XInputStream> xInStream; + try { + xInStream = rModel.GetDocumentStream(rURL, sourceProxy); + } + catch (container::NoSuchElementException const&) + { + SAL_INFO("svx", "not found: '" << rURL << "'"); + return false; + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("svx", ""); + return false; + } + if (!xInStream.is()) + { + SAL_WARN("svx", "no stream?"); + return false; + } + // Make sure the temporary copy has the same file name extension as the original media file + // (like .mp4). That seems to be important for some AVFoundation APIs. For random extension-less + // file names, they don't seem to even bother looking inside the file. + sal_Int32 nLastDot = rURL.lastIndexOf('.'); + sal_Int32 nLastSlash = rURL.lastIndexOf('/'); + OUString sDesiredExtension; + if (nLastDot > nLastSlash && nLastDot+1 < rURL.getLength()) + sDesiredExtension = rURL.copy(nLastDot); + return ::avmedia::CreateMediaTempFile(xInStream, o_rTempFileURL, sDesiredExtension); +} +#endif + +void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProperties ) +{ + bool bBroadcastChanged = false; +#if HAVE_FEATURE_AVMEDIA + const AVMediaSetMask nMaskSet = rNewProperties.getMaskSet(); + + // use only a subset of MediaItem properties for own properties + if( AVMediaSetMask::MIME_TYPE & nMaskSet ) + m_xImpl->m_MediaProperties.setMimeType( rNewProperties.getMimeType() ); + + if (nMaskSet & AVMediaSetMask::GRAPHIC) + { + m_xImpl->m_MediaProperties.setGraphic(rNewProperties.getGraphic()); + } + + if (nMaskSet & AVMediaSetMask::CROP) + { + m_xImpl->m_MediaProperties.setCrop(rNewProperties.getCrop()); + } + + if( ( AVMediaSetMask::URL & nMaskSet ) && + ( rNewProperties.getURL() != getURL() )) + { + m_xImpl->m_xCachedSnapshot.clear(); + m_xImpl->m_xPlayerListener.clear(); + OUString const& url(rNewProperties.getURL()); + if (url.startsWithIgnoreAsciiCase("vnd.sun.star.Package:")) + { + if ( !m_xImpl->m_pTempFile + || (m_xImpl->m_pTempFile->m_TempFileURL != + rNewProperties.getTempURL())) + { + OUString tempFileURL; + const bool bSuccess( + lcl_HandlePackageURL( + url, + getSdrModelFromSdrObject(), + tempFileURL)); + + if (bSuccess) + { + m_xImpl->m_pTempFile = + std::make_shared<::avmedia::MediaTempFile>(tempFileURL); + m_xImpl->m_MediaProperties.setURL(url, tempFileURL, ""); + } + else // this case is for Clone via operator= + { + m_xImpl->m_pTempFile.reset(); + m_xImpl->m_MediaProperties.setURL("", "", ""); + // UGLY: oox import also gets here, because unlike ODF + // getDocumentStorage() is not the imported file... + m_xImpl->m_LastFailedPkgURL = url; + } + } + else + { + m_xImpl->m_MediaProperties.setURL(url, + rNewProperties.getTempURL(), ""); + } + } + else + { + m_xImpl->m_pTempFile.reset(); + m_xImpl->m_MediaProperties.setURL(url, "", rNewProperties.getReferer()); + } + bBroadcastChanged = true; + } + + if( AVMediaSetMask::LOOP & nMaskSet ) + m_xImpl->m_MediaProperties.setLoop( rNewProperties.isLoop() ); + + if( AVMediaSetMask::MUTE & nMaskSet ) + m_xImpl->m_MediaProperties.setMute( rNewProperties.isMute() ); + + if( AVMediaSetMask::VOLUMEDB & nMaskSet ) + m_xImpl->m_MediaProperties.setVolumeDB( rNewProperties.getVolumeDB() ); + + if( AVMediaSetMask::ZOOM & nMaskSet ) + m_xImpl->m_MediaProperties.setZoom( rNewProperties.getZoom() ); +#else + (void) rNewProperties; +#endif + + if( bBroadcastChanged ) + { + SetChanged(); + BroadcastObjectChange(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoole2.cxx b/svx/source/svdraw/svdoole2.cxx new file mode 100644 index 0000000000..bd7db1a62c --- /dev/null +++ b/svx/source/svdraw/svdoole2.cxx @@ -0,0 +1,1996 @@ +/* -*- 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/svdoole2.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/ObjectSaveVetoException.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist2.hpp> +#include <com/sun/star/embed/XInplaceClient.hpp> +#include <com/sun/star/embed/XInplaceObject.hpp> +#include <com/sun/star/embed/XLinkageSupport.hpp> +#include <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/embed/XWindowSupplier.hpp> +#include <com/sun/star/document/XEventListener.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> + +#include <cppuhelper/exc_hlp.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <toolkit/helper/convert.hxx> + +#include <svtools/colorcfg.hxx> +#include <svtools/embedhlp.hxx> + +#include <sfx2/objsh.hxx> +#include <sfx2/ipclient.hxx> +#include <sfx2/lnkbase.hxx> +#include <sfx2/linkmgr.hxx> +#include <tools/debug.hxx> +#include <tools/globname.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <sot/formats.hxx> +#include <cppuhelper/implbase.hxx> + +#include <vcl/svapp.hxx> + +#include <svx/svdmodel.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdetc.hxx> +#include <unomlstr.hxx> +#include <sdr/contact/viewcontactofsdrole2obj.hxx> +#include <svx/svdograf.hxx> +#include <sdr/properties/oleproperties.hxx> +#include <svx/unoshape.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbstit.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <editeng/outlobj.hxx> +#include <svx/svdpage.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <bitmaps.hlst> + +using namespace ::com::sun::star; + +static uno::Reference < beans::XPropertySet > lcl_getFrame_throw(const SdrOle2Obj* _pObject) +{ + uno::Reference < beans::XPropertySet > xFrame; + if ( _pObject ) + { + uno::Reference< frame::XController> xController = _pObject->GetParentXModel()->getCurrentController(); + if ( xController.is() ) + { + xFrame.set( xController->getFrame(),uno::UNO_QUERY_THROW); + } + } // if ( _pObject ) + return xFrame; +} + +namespace { + +class SdrLightEmbeddedClient_Impl : public ::cppu::WeakImplHelper + < embed::XStateChangeListener + , document::XEventListener + , embed::XInplaceClient + , embed::XEmbeddedClient + , embed::XWindowSupplier + > +{ + uno::Reference< awt::XWindow > m_xWindow; + SdrOle2Obj* mpObj; + + Fraction m_aScaleWidth; + Fraction m_aScaleHeight; + + +public: + explicit SdrLightEmbeddedClient_Impl( SdrOle2Obj* pObj ); + virtual ~SdrLightEmbeddedClient_Impl() override; + + void SetSizeScale( const Fraction& aScaleWidth, const Fraction& aScaleHeight ) + { + m_aScaleWidth = aScaleWidth; + m_aScaleHeight = aScaleHeight; + } + + const Fraction& GetScaleWidth() const { return m_aScaleWidth; } + const Fraction& GetScaleHeight() const { return m_aScaleHeight; } + + void setWindow(const uno::Reference< awt::XWindow >& _xWindow); + + void disconnect(); +private: + + tools::Rectangle impl_getScaledRect_nothrow() const; + // XStateChangeListener + virtual void SAL_CALL changingState( const css::lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL stateChanged( const css::lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; + + // document::XEventListener + virtual void SAL_CALL notifyEvent( const document::EventObject& aEvent ) override; + + // XEmbeddedClient + virtual void SAL_CALL saveObject() override; + virtual void SAL_CALL visibilityChanged( sal_Bool bVisible ) override; + + // XComponentSupplier + virtual uno::Reference< util::XCloseable > SAL_CALL getComponent() override; + + // XInplaceClient + virtual sal_Bool SAL_CALL canInplaceActivate() override; + virtual void SAL_CALL activatingInplace() override; + virtual void SAL_CALL activatingUI() override; + virtual void SAL_CALL deactivatedInplace() override; + virtual void SAL_CALL deactivatedUI() override; + virtual uno::Reference< css::frame::XLayoutManager > SAL_CALL getLayoutManager() override; + virtual uno::Reference< frame::XDispatchProvider > SAL_CALL getInplaceDispatchProvider() override; + virtual awt::Rectangle SAL_CALL getPlacement() override; + virtual awt::Rectangle SAL_CALL getClipRectangle() override; + virtual void SAL_CALL translateAccelerators( const uno::Sequence< awt::KeyEvent >& aKeys ) override; + virtual void SAL_CALL scrollObject( const awt::Size& aOffset ) override; + virtual void SAL_CALL changedPlacement( const awt::Rectangle& aPosRect ) override; + + // XWindowSupplier + virtual uno::Reference< awt::XWindow > SAL_CALL getWindow() override; +}; + +} + +SdrLightEmbeddedClient_Impl::SdrLightEmbeddedClient_Impl( SdrOle2Obj* pObj ) +: mpObj( pObj ) +{ +} +SdrLightEmbeddedClient_Impl::~SdrLightEmbeddedClient_Impl() +{ + assert(!mpObj); +} +tools::Rectangle SdrLightEmbeddedClient_Impl::impl_getScaledRect_nothrow() const +{ + tools::Rectangle aLogicRect( mpObj->GetLogicRect() ); + // apply scaling to object area and convert to pixels + aLogicRect.SetSize( Size( tools::Long( aLogicRect.GetWidth() * m_aScaleWidth), + tools::Long( aLogicRect.GetHeight() * m_aScaleHeight) ) ); + return aLogicRect; +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::changingState( const css::lang::EventObject& /*aEvent*/, ::sal_Int32 /*nOldState*/, ::sal_Int32 /*nNewState*/ ) +{ +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::stateChanged( const css::lang::EventObject& /*aEvent*/, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) +{ + SolarMutexGuard aGuard; + + if ( mpObj && nOldState == embed::EmbedStates::LOADED && nNewState == embed::EmbedStates::RUNNING ) + { + mpObj->ObjectLoaded(); + GetSdrGlobalData().GetOLEObjCache().InsertObj(mpObj); + } + else if ( mpObj && nNewState == embed::EmbedStates::LOADED && nOldState == embed::EmbedStates::RUNNING ) + { + GetSdrGlobalData().GetOLEObjCache().RemoveObj(mpObj); + } +} + +void SdrLightEmbeddedClient_Impl::disconnect() +{ + SolarMutexGuard aGuard; + if (!mpObj) + return; + GetSdrGlobalData().GetOLEObjCache().RemoveObj(mpObj); + mpObj = nullptr; +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::disposing( const css::lang::EventObject& /*aEvent*/ ) +{ + disconnect(); +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::notifyEvent( const document::EventObject& aEvent ) +{ + // TODO/LATER: when writer uses this implementation the code could be shared with SfxInPlaceClient_Impl + + SolarMutexGuard aGuard; + + // the code currently makes sense only in case there is no other client + if ( !(mpObj && mpObj->GetAspect() != embed::Aspects::MSOLE_ICON && aEvent.EventName == "OnVisAreaChanged" + && mpObj->GetObjRef().is() && mpObj->GetObjRef()->getClientSite() == uno::Reference< embed::XEmbeddedClient >( this )) ) + return; + + try + { + MapUnit aContainerMapUnit( MapUnit::Map100thMM ); + uno::Reference< embed::XVisualObject > xParentVis( mpObj->GetParentXModel(), uno::UNO_QUERY ); + if ( xParentVis.is() ) + aContainerMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xParentVis->getMapUnit( mpObj->GetAspect() ) ); + + MapUnit aObjMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( mpObj->GetObjRef()->getMapUnit( mpObj->GetAspect() ) ); + + tools::Rectangle aVisArea; + awt::Size aSz; + try + { + aSz = mpObj->GetObjRef()->getVisualAreaSize( mpObj->GetAspect() ); + } + catch( embed::NoVisualAreaSizeException& ) + { + TOOLS_WARN_EXCEPTION("svx.svdraw", "No visual area size!"); + aSz.Width = 5000; + aSz.Height = 5000; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.svdraw", ""); + aSz.Width = 5000; + aSz.Height = 5000; + } + + aVisArea.SetSize( Size( aSz.Width, aSz.Height ) ); + aVisArea = OutputDevice::LogicToLogic(aVisArea, MapMode(aObjMapUnit), MapMode(aContainerMapUnit)); + Size aScaledSize( static_cast< tools::Long >( m_aScaleWidth * Fraction( aVisArea.GetWidth() ) ), + static_cast< tools::Long >( m_aScaleHeight * Fraction( aVisArea.GetHeight() ) ) ); + tools::Rectangle aLogicRect( mpObj->GetLogicRect() ); + + // react to the change if the difference is bigger than one pixel + Size aPixelDiff = + Application::GetDefaultDevice()->LogicToPixel( + Size( aLogicRect.GetWidth() - aScaledSize.Width(), + aLogicRect.GetHeight() - aScaledSize.Height() ), + MapMode(aContainerMapUnit)); + if( aPixelDiff.Width() || aPixelDiff.Height() ) + { + mpObj->SetLogicRect( tools::Rectangle( aLogicRect.TopLeft(), aScaledSize ) ); + mpObj->BroadcastObjectChange(); + } + else + mpObj->ActionChanged(); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.svdraw", ""); + } +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::saveObject() +{ + // TODO/LATER: when writer uses this implementation the code could be shared with SfxInPlaceClient_Impl + uno::Reference< embed::XCommonEmbedPersist > xPersist; + uno::Reference< util::XModifiable > xModifiable; + + { + SolarMutexGuard aGuard; + + if ( !mpObj ) + throw embed::ObjectSaveVetoException(); + + // the common persistence is supported by objects and links + xPersist.set( mpObj->GetObjRef(), uno::UNO_QUERY_THROW ); + xModifiable.set( mpObj->GetParentXModel(), uno::UNO_QUERY ); + } + + xPersist->storeOwn(); + + if ( xModifiable.is() ) + xModifiable->setModified( true ); +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::visibilityChanged( sal_Bool /*bVisible*/ ) +{ + // nothing to do currently + // TODO/LATER: when writer uses this implementation the code could be shared with SfxInPlaceClient_Impl + if ( mpObj ) + { + tools::Rectangle aLogicRect( mpObj->GetLogicRect() ); + Size aLogicSize( aLogicRect.GetWidth(), aLogicRect.GetHeight() ); + + if( mpObj->IsChart() ) + { + //charts never should be stretched see #i84323# for example + mpObj->SetLogicRect( tools::Rectangle( aLogicRect.TopLeft(), aLogicSize ) ); + mpObj->BroadcastObjectChange(); + } // if( mpObj->IsChart() ) + } +} + +uno::Reference< util::XCloseable > SAL_CALL SdrLightEmbeddedClient_Impl::getComponent() +{ + uno::Reference< util::XCloseable > xResult; + + SolarMutexGuard aGuard; + if ( mpObj ) + xResult.set( mpObj->GetParentXModel(), uno::UNO_QUERY ); + + return xResult; +} +// XInplaceClient + +sal_Bool SAL_CALL SdrLightEmbeddedClient_Impl::canInplaceActivate() +{ + bool bRet = false; + SolarMutexGuard aGuard; + if ( mpObj ) + { + uno::Reference< embed::XEmbeddedObject > xObject = mpObj->GetObjRef(); + if ( !xObject.is() ) + throw uno::RuntimeException(); + // we don't want to switch directly from outplace to inplace mode + bRet = ( xObject->getCurrentState() != embed::EmbedStates::ACTIVE && mpObj->GetAspect() != embed::Aspects::MSOLE_ICON ); + } // if ( mpObj ) + return bRet; +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::activatingInplace() +{ +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::activatingUI() +{ + SolarMutexGuard aGuard; + + uno::Reference < beans::XPropertySet > xFrame( lcl_getFrame_throw(mpObj)); + uno::Reference < frame::XFrame > xOwnFrame( xFrame,uno::UNO_QUERY); + uno::Reference < frame::XFramesSupplier > xParentFrame = xOwnFrame->getCreator(); + if ( xParentFrame.is() ) + xParentFrame->setActiveFrame( xOwnFrame ); + + OLEObjCache& rObjCache = GetSdrGlobalData().GetOLEObjCache(); + const size_t nCount = rObjCache.size(); + for(sal_Int32 i = nCount-1 ; i >= 0;--i) + { + SdrOle2Obj* pObj = rObjCache[i]; + if ( pObj != mpObj ) + { + // only deactivate ole objects which belongs to the same frame + if ( xFrame == lcl_getFrame_throw(pObj) ) + { + const uno::Reference< embed::XEmbeddedObject >& xObject = pObj->GetObjRef(); + try + { + if ( xObject->getStatus( pObj->GetAspect() ) & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE ) + xObject->changeState( embed::EmbedStates::INPLACE_ACTIVE ); + else + { + // the links should not stay in running state for long time because of locking + uno::Reference< embed::XLinkageSupport > xLink( xObject, uno::UNO_QUERY ); + if ( xLink.is() && xLink->isLink() ) + xObject->changeState( embed::EmbedStates::LOADED ); + else + xObject->changeState( embed::EmbedStates::RUNNING ); + } + } + catch (css::uno::Exception& ) + {} + } + } + } // for(sal_Int32 i = nCount-1 ; i >= 0;--i) +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::deactivatedInplace() +{ +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::deactivatedUI() +{ + SolarMutexGuard aGuard; + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager(getLayoutManager()); + if ( xLayoutManager.is() ) + { + static constexpr OUString aMenuBarURL = u"private:resource/menubar/menubar"_ustr; + if ( !xLayoutManager->isElementVisible( aMenuBarURL ) ) + xLayoutManager->createElement( aMenuBarURL ); + } +} + +uno::Reference< css::frame::XLayoutManager > SAL_CALL SdrLightEmbeddedClient_Impl::getLayoutManager() +{ + uno::Reference< css::frame::XLayoutManager > xMan; + SolarMutexGuard aGuard; + uno::Reference < beans::XPropertySet > xFrame( lcl_getFrame_throw(mpObj)); + try + { + xMan.set(xFrame->getPropertyValue("LayoutManager"),uno::UNO_QUERY); + } + catch ( uno::Exception& ex ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( ex.Message, + nullptr, anyEx ); + } + + return xMan; +} + +uno::Reference< frame::XDispatchProvider > SAL_CALL SdrLightEmbeddedClient_Impl::getInplaceDispatchProvider() +{ + SolarMutexGuard aGuard; + return uno::Reference < frame::XDispatchProvider >( lcl_getFrame_throw(mpObj), uno::UNO_QUERY_THROW ); +} + +awt::Rectangle SAL_CALL SdrLightEmbeddedClient_Impl::getPlacement() +{ + SolarMutexGuard aGuard; + if ( !mpObj ) + throw uno::RuntimeException(); + + tools::Rectangle aLogicRect = impl_getScaledRect_nothrow(); + MapUnit aContainerMapUnit( MapUnit::Map100thMM ); + uno::Reference< embed::XVisualObject > xParentVis( mpObj->GetParentXModel(), uno::UNO_QUERY ); + if ( xParentVis.is() ) + aContainerMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xParentVis->getMapUnit( mpObj->GetAspect() ) ); + + aLogicRect = Application::GetDefaultDevice()->LogicToPixel(aLogicRect, MapMode(aContainerMapUnit)); + return AWTRectangle( aLogicRect ); +} + +awt::Rectangle SAL_CALL SdrLightEmbeddedClient_Impl::getClipRectangle() +{ + return getPlacement(); +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::translateAccelerators( const uno::Sequence< awt::KeyEvent >& /*aKeys*/ ) +{ +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::scrollObject( const awt::Size& /*aOffset*/ ) +{ +} + +void SAL_CALL SdrLightEmbeddedClient_Impl::changedPlacement( const awt::Rectangle& aPosRect ) +{ + SolarMutexGuard aGuard; + if ( !mpObj ) + throw uno::RuntimeException(); + + uno::Reference< embed::XInplaceObject > xInplace( mpObj->GetObjRef(), uno::UNO_QUERY_THROW ); + + // check if the change is at least one pixel in size + awt::Rectangle aOldRect = getPlacement(); + tools::Rectangle aNewPixelRect = VCLRectangle( aPosRect ); + tools::Rectangle aOldPixelRect = VCLRectangle( aOldRect ); + if ( aOldPixelRect == aNewPixelRect ) + // nothing has changed + return; + + // new scaled object area + MapUnit aContainerMapUnit( MapUnit::Map100thMM ); + uno::Reference< embed::XVisualObject > xParentVis( mpObj->GetParentXModel(), uno::UNO_QUERY ); + if ( xParentVis.is() ) + aContainerMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xParentVis->getMapUnit( mpObj->GetAspect() ) ); + + tools::Rectangle aNewLogicRect = Application::GetDefaultDevice()->PixelToLogic(aNewPixelRect, MapMode(aContainerMapUnit)); + tools::Rectangle aLogicRect = impl_getScaledRect_nothrow(); + + if ( aNewLogicRect == aLogicRect ) + return; + + // the calculation of the object area has not changed the object size + // it should be done here then + //SfxBooleanFlagGuard aGuard( m_bResizeNoScale, true ); + + // new size of the object area without scaling + Size aNewObjSize( tools::Long( aNewLogicRect.GetWidth() / m_aScaleWidth ), + tools::Long( aNewLogicRect.GetHeight() / m_aScaleHeight ) ); + + // now remove scaling from new placement and keep this at the new object area + aNewLogicRect.SetSize( aNewObjSize ); + // react to the change if the difference is bigger than one pixel + Size aPixelDiff = + Application::GetDefaultDevice()->LogicToPixel( + Size( aLogicRect.GetWidth() - aNewObjSize.Width(), + aLogicRect.GetHeight() - aNewObjSize.Height() ), + MapMode(aContainerMapUnit)); + if( aPixelDiff.Width() || aPixelDiff.Height() ) + { + mpObj->SetLogicRect( tools::Rectangle( aLogicRect.TopLeft(), aNewObjSize ) ); + mpObj->BroadcastObjectChange(); + } + else + mpObj->ActionChanged(); +} +// XWindowSupplier + +uno::Reference< awt::XWindow > SAL_CALL SdrLightEmbeddedClient_Impl::getWindow() +{ + SolarMutexGuard aGuard; + uno::Reference< awt::XWindow > xCurrent = m_xWindow; + if ( !xCurrent.is() ) + { + if ( !mpObj ) + throw uno::RuntimeException(); + uno::Reference< frame::XFrame> xFrame(lcl_getFrame_throw(mpObj),uno::UNO_QUERY_THROW); + xCurrent = xFrame->getComponentWindow(); + } // if ( !xCurrent.is() ) + return xCurrent; +} +void SdrLightEmbeddedClient_Impl::setWindow(const uno::Reference< awt::XWindow >& _xWindow) +{ + m_xWindow = _xWindow; +} + +SdrEmbedObjectLink::SdrEmbedObjectLink(SdrOle2Obj* pObject): + ::sfx2::SvBaseLink( ::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SVXB ), + pObj(pObject) +{ + SetSynchron( false ); +} + +SdrEmbedObjectLink::~SdrEmbedObjectLink() +{ +} + +::sfx2::SvBaseLink::UpdateResult SdrEmbedObjectLink::DataChanged( + const OUString& /*rMimeType*/, const css::uno::Any & /*rValue*/ ) +{ + if ( !pObj->UpdateLinkURL_Impl() ) + { + // the link URL was not changed + uno::Reference< embed::XEmbeddedObject > xObject = pObj->GetObjRef(); + OSL_ENSURE( xObject.is(), "The object must exist always!" ); + if ( xObject.is() ) + { + // let the object reload the link + // TODO/LATER: reload call could be used for this case + + try + { + sal_Int32 nState = xObject->getCurrentState(); + if ( nState != embed::EmbedStates::LOADED ) + { + // in some cases the linked file probably is not locked so it could be changed + xObject->changeState( embed::EmbedStates::LOADED ); + xObject->changeState( nState ); + } + } + catch ( uno::Exception& ) + { + } + } + } + + pObj->GetNewReplacement(); + pObj->SetChanged(); + + return SUCCESS; +} + +void SdrEmbedObjectLink::Closed() +{ + pObj->BreakFileLink_Impl(); + SvBaseLink::Closed(); +} + +SdrIFrameLink::SdrIFrameLink(SdrOle2Obj* pObject) + : ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SVXB) + , m_pObject(pObject) +{ + SetSynchron( false ); +} + +::sfx2::SvBaseLink::UpdateResult SdrIFrameLink::DataChanged( + const OUString&, const uno::Any& ) +{ + uno::Reference<embed::XEmbeddedObject> xObject = m_pObject->GetObjRef(); + uno::Reference<embed::XCommonEmbedPersist> xPersObj(xObject, uno::UNO_QUERY); + if (xPersObj.is()) + { + // let the IFrameObject reload the link + try + { + xPersObj->reload(uno::Sequence<beans::PropertyValue>(), uno::Sequence<beans::PropertyValue>()); + } + catch (const uno::Exception&) + { + } + + m_pObject->SetChanged(); + } + + return SUCCESS; +} + +class SdrOle2ObjImpl +{ +public: + svt::EmbeddedObjectRef mxObjRef; + + std::optional<Graphic> moGraphic; + OUString maProgName; + OUString aPersistName; // name of object in persist + rtl::Reference<SdrLightEmbeddedClient_Impl> mxLightClient; // must be registered as client only using AddOwnLightClient() call + + bool mbFrame:1; // Due to compatibility at SdrTextObj for now + bool mbSuppressSetVisAreaSize:1; // #i118524# + mutable bool mbTypeAsked:1; + mutable bool mbIsChart:1; + bool mbLoadingOLEObjectFailed:1; // New local var to avoid repeated loading if load of OLE2 fails + bool mbConnected:1; + + sfx2::SvBaseLink* mpObjectLink; + OUString maLinkURL; + + rtl::Reference<SvxUnoShapeModifyListener> mxModifyListener; + + explicit SdrOle2ObjImpl( bool bFrame ) : + mbFrame(bFrame), + mbSuppressSetVisAreaSize(false), + mbTypeAsked(false), + mbIsChart(false), + mbLoadingOLEObjectFailed(false), + mbConnected(false), + mpObjectLink(nullptr) + { + mxObjRef.Lock(); + } + + SdrOle2ObjImpl( bool bFrame, const svt::EmbeddedObjectRef& rObjRef ) : + mxObjRef(rObjRef), + mbFrame(bFrame), + mbSuppressSetVisAreaSize(false), + mbTypeAsked(false), + mbIsChart(false), + mbLoadingOLEObjectFailed(false), + mbConnected(false), + mpObjectLink(nullptr) + { + mxObjRef.Lock(); + } + + ~SdrOle2ObjImpl() + { + moGraphic.reset(); + + if (mxModifyListener.is()) + { + mxModifyListener->invalidate(); + } + } +}; + +// Predicate determining whether the given OLE is an internal math +// object +static bool ImplIsMathObj( const uno::Reference < embed::XEmbeddedObject >& rObjRef ) +{ + if ( !rObjRef.is() ) + return false; + + SvGlobalName aClassName( rObjRef->getClassID() ); + return aClassName == SvGlobalName(SO3_SM_CLASSID_30) || + aClassName == SvGlobalName(SO3_SM_CLASSID_40) || + aClassName == SvGlobalName(SO3_SM_CLASSID_50) || + aClassName == SvGlobalName(SO3_SM_CLASSID_60) || + aClassName == SvGlobalName(SO3_SM_CLASSID); +} + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrOle2Obj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::OleProperties>(*this); +} + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrOle2Obj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrOle2Obj>(*this); +} + +void SdrOle2Obj::Init() +{ + // Stuff that was done from old SetModel: + // #i43086# #i85304 redo the change for charts for the above bugfix, as #i43086# does not occur anymore + // so maybe the ImpSetVisAreaSize call can be removed here completely + // Nevertheless I leave it in for other objects as I am not sure about the side effects when removing now + if(!getSdrModelFromSdrObject().isLocked() && !IsChart()) + { + ImpSetVisAreaSize(); + } + + ::comphelper::IEmbeddedHelper* pDestPers(getSdrModelFromSdrObject().GetPersist()); + if(pDestPers && !IsEmptyPresObj()) + { + // object wasn't connected, now it should be + Connect_Impl(); + } + + AddListeners_Impl(); +} + +SdrOle2Obj::SdrOle2Obj( + SdrModel& rSdrModel, + bool bFrame_) +: SdrRectObj(rSdrModel), + mpImpl(new SdrOle2ObjImpl(bFrame_)) +{ + Init(); +} + +SdrOle2Obj::SdrOle2Obj(SdrModel& rSdrModel, SdrOle2Obj const & rSource) +: SdrRectObj(rSdrModel, rSource), + mpImpl(new SdrOle2ObjImpl(/*bFrame*/false)) +{ + Init(); + + // Manually copying bClosedObj attribute + SetClosedObj( rSource.IsClosedObj() ); + + mpImpl->aPersistName = rSource.mpImpl->aPersistName; + mpImpl->maProgName = rSource.mpImpl->maProgName; + mpImpl->mbFrame = rSource.mpImpl->mbFrame; + + if (rSource.mpImpl->moGraphic) + { + mpImpl->moGraphic.emplace(*rSource.mpImpl->moGraphic); + } + + if( IsEmptyPresObj() ) + return; + + ::comphelper::IEmbeddedHelper* pDestPers(getSdrModelFromSdrObject().GetPersist()); + ::comphelper::IEmbeddedHelper* pSrcPers(rSource.getSdrModelFromSdrObject().GetPersist()); + if( !(pDestPers && pSrcPers) ) + return; + + DBG_ASSERT( !mpImpl->mxObjRef.is(), "Object already existing!" ); + comphelper::EmbeddedObjectContainer& rContainer = pSrcPers->getEmbeddedObjectContainer(); + uno::Reference < embed::XEmbeddedObject > xObj = rContainer.GetEmbeddedObject( mpImpl->aPersistName ); + if ( xObj.is() ) + { + OUString aTmp; + mpImpl->mxObjRef.Assign( pDestPers->getEmbeddedObjectContainer().CopyAndGetEmbeddedObject( + rContainer, xObj, aTmp, pSrcPers->getDocumentBaseURL(), pDestPers->getDocumentBaseURL()), rSource.GetAspect()); + mpImpl->mbTypeAsked = false; + mpImpl->aPersistName = aTmp; + CheckFileLink_Impl(); + } + + Connect(); +} + +SdrOle2Obj::SdrOle2Obj( + SdrModel& rSdrModel, + const svt::EmbeddedObjectRef& rNewObjRef, + const OUString& rNewObjName, + const tools::Rectangle& rNewRect) +: SdrRectObj(rSdrModel, rNewRect), + mpImpl(new SdrOle2ObjImpl(false/*bFrame_*/, rNewObjRef)) +{ + osl_atomic_increment(&m_refCount); + + mpImpl->aPersistName = rNewObjName; + + if (mpImpl->mxObjRef.is() && (mpImpl->mxObjRef->getStatus( GetAspect() ) & embed::EmbedMisc::EMBED_NEVERRESIZE ) ) + m_bSizProt = true; + + // For math objects, set closed state to transparent + SetClosedObj(!ImplIsMathObj( mpImpl->mxObjRef.GetObject() )); + + Init(); + + osl_atomic_decrement(&m_refCount); +} + +OUString SdrOle2Obj::GetStyleString() +{ + OUString strStyle; + if (mpImpl->mxObjRef.is() && mpImpl->mxObjRef.IsChart()) + { + strStyle = mpImpl->mxObjRef.GetChartType(); + } + return strStyle; +} + +SdrOle2Obj::~SdrOle2Obj() +{ + if ( mpImpl->mbConnected ) + Disconnect(); + + DisconnectFileLink_Impl(); + + if (mpImpl->mxLightClient) + { + mpImpl->mxLightClient->disconnect(); + mpImpl->mxLightClient.clear(); + } +} + +void SdrOle2Obj::SetAspect( sal_Int64 nAspect ) +{ + mpImpl->mxObjRef.SetViewAspect( nAspect ); +} + +const svt::EmbeddedObjectRef& SdrOle2Obj::getEmbeddedObjectRef() const +{ + return mpImpl->mxObjRef; +} + +sal_Int64 SdrOle2Obj::GetAspect() const +{ + return mpImpl->mxObjRef.GetViewAspect(); +} + +bool SdrOle2Obj::isInplaceActive() const +{ + return mpImpl->mxObjRef.is() && embed::EmbedStates::INPLACE_ACTIVE == mpImpl->mxObjRef->getCurrentState(); +} + +bool SdrOle2Obj::isUiActive() const +{ + return mpImpl->mxObjRef.is() && embed::EmbedStates::UI_ACTIVE == mpImpl->mxObjRef->getCurrentState(); +} + +void SdrOle2Obj::SetGraphic(const Graphic& rGrf) +{ + // only for setting a preview graphic + mpImpl->moGraphic.emplace(rGrf); + + SetChanged(); + BroadcastObjectChange(); +} + +void SdrOle2Obj::ClearGraphic() +{ + mpImpl->moGraphic.reset(); + + SetChanged(); + BroadcastObjectChange(); +} + +void SdrOle2Obj::SetProgName( const OUString& rName ) +{ + mpImpl->maProgName = rName; +} + +const OUString& SdrOle2Obj::GetProgName() const +{ + return mpImpl->maProgName; +} + +bool SdrOle2Obj::IsEmpty() const +{ + return !mpImpl->mxObjRef.is(); +} + +void SdrOle2Obj::Connect(SvxOle2Shape* pCreator) +{ + if( IsEmptyPresObj() ) + return; + + if( mpImpl->mbConnected ) + { + // currently there are situations where it seems to be unavoidable to have multiple connects + // changing this would need a larger code rewrite, so for now I remove the assertion + // OSL_FAIL("Connect() called on connected object!"); + return; + } + + Connect_Impl(pCreator); + AddListeners_Impl(); +} + +bool SdrOle2Obj::UpdateLinkURL_Impl() +{ + bool bResult = false; + + if ( mpImpl->mpObjectLink ) + { + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + + if ( pLinkManager ) + { + OUString aNewLinkURL; + sfx2::LinkManager::GetDisplayNames( mpImpl->mpObjectLink, nullptr, &aNewLinkURL ); + if ( !aNewLinkURL.equalsIgnoreAsciiCase( mpImpl->maLinkURL ) ) + { + GetObjRef_Impl(); + uno::Reference<embed::XCommonEmbedPersist> xPersObj( mpImpl->mxObjRef.GetObject(), uno::UNO_QUERY ); + OSL_ENSURE( xPersObj.is(), "The object must exist!" ); + if ( xPersObj.is() ) + { + try + { + sal_Int32 nCurState = mpImpl->mxObjRef->getCurrentState(); + if ( nCurState != embed::EmbedStates::LOADED ) + mpImpl->mxObjRef->changeState(embed::EmbedStates::LOADED); + + // TODO/LATER: there should be possible to get current mediadescriptor settings from the object + uno::Sequence< beans::PropertyValue > aArgs{ comphelper::makePropertyValue( + "URL", aNewLinkURL) }; + xPersObj->reload( aArgs, uno::Sequence< beans::PropertyValue >() ); + + mpImpl->maLinkURL = aNewLinkURL; + bResult = true; + + if ( nCurState != embed::EmbedStates::LOADED ) + mpImpl->mxObjRef->changeState(nCurState); + } + catch( css::uno::Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::UpdateLinkURL_Impl()" ); + } + } + + if ( !bResult ) + { + // TODO/LATER: return the old name to the link manager, is it possible? + } + } + } + } + + return bResult; +} + +void SdrOle2Obj::BreakFileLink_Impl() +{ + uno::Reference<document::XStorageBasedDocument> xDoc(getSdrModelFromSdrObject().getUnoModel(), uno::UNO_QUERY); + + if ( !xDoc.is() ) + return; + + uno::Reference< embed::XStorage > xStorage = xDoc->getDocumentStorage(); + if ( !xStorage.is() ) + return; + + try + { + uno::Reference< embed::XLinkageSupport > xLinkSupport( mpImpl->mxObjRef.GetObject(), uno::UNO_QUERY_THROW ); + xLinkSupport->breakLink( xStorage, mpImpl->aPersistName ); + DisconnectFileLink_Impl(); + mpImpl->maLinkURL.clear(); + } + catch( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::BreakFileLink_Impl()" ); + } +} + +void SdrOle2Obj::DisconnectFileLink_Impl() +{ + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + + if ( pLinkManager && mpImpl->mpObjectLink ) + { + pLinkManager->Remove( mpImpl->mpObjectLink ); + mpImpl->mpObjectLink = nullptr; + } +} + +void SdrOle2Obj::CheckFileLink_Impl() +{ + if (!mpImpl->mxObjRef.GetObject().is() || mpImpl->mpObjectLink) + return; + + try + { + uno::Reference<embed::XEmbeddedObject> xObject = mpImpl->mxObjRef.GetObject(); + if (!xObject) + return; + + bool bIFrame = false; + + OUString aLinkURL; + uno::Reference<embed::XLinkageSupport> xLinkSupport(xObject, uno::UNO_QUERY); + if (xLinkSupport) + { + if (xLinkSupport->isLink()) + aLinkURL = xLinkSupport->getLinkURL(); + } + else + { + // get IFrame (Floating Frames) listed and updatable from the + // manage links dialog + SvGlobalName aClassId(xObject->getClassID()); + if (aClassId == SvGlobalName(SO3_IFRAME_CLASSID)) + { + uno::Reference<beans::XPropertySet> xSet(xObject->getComponent(), uno::UNO_QUERY); + if (xSet.is()) + xSet->getPropertyValue("FrameURL") >>= aLinkURL; + bIFrame = true; + } + } + + if (!aLinkURL.isEmpty()) // this is a file link so the model link manager should handle it + { + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + + if ( pLinkManager ) + { + SdrEmbedObjectLink* pEmbedObjectLink = nullptr; + if (!bIFrame) + { + pEmbedObjectLink = new SdrEmbedObjectLink(this); + mpImpl->mpObjectLink = pEmbedObjectLink; + } + else + mpImpl->mpObjectLink = new SdrIFrameLink(this); + mpImpl->maLinkURL = aLinkURL; + pLinkManager->InsertFileLink( *mpImpl->mpObjectLink, sfx2::SvBaseLinkObjectType::ClientOle, aLinkURL ); + if (pEmbedObjectLink) + pEmbedObjectLink->Connect(); + } + } + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "SdrOle2Obj::CheckFileLink_Impl()"); + } +} + +void SdrOle2Obj::Connect_Impl(SvxOle2Shape* pCreator) +{ + if(mpImpl->aPersistName.isEmpty() ) + return; + + try + { + ::comphelper::IEmbeddedHelper* pPers(getSdrModelFromSdrObject().GetPersist()); + + if ( pPers ) + { + comphelper::EmbeddedObjectContainer& rContainer = pPers->getEmbeddedObjectContainer(); + + if ( !rContainer.HasEmbeddedObject( mpImpl->aPersistName ) + || ( mpImpl->mxObjRef.is() && !rContainer.HasEmbeddedObject( mpImpl->mxObjRef.GetObject() ) ) ) + { + // object not known to container document + // No object -> disaster! + DBG_ASSERT( mpImpl->mxObjRef.is(), "No object in connect!"); + if ( mpImpl->mxObjRef.is() ) + { + // object came from the outside, now add it to the container + OUString aTmp; + rContainer.InsertEmbeddedObject( mpImpl->mxObjRef.GetObject(), aTmp ); + mpImpl->aPersistName = aTmp; + } + } + else if ( !mpImpl->mxObjRef.is() ) + { + mpImpl->mxObjRef.Assign( rContainer.GetEmbeddedObject( mpImpl->aPersistName ), mpImpl->mxObjRef.GetViewAspect() ); + mpImpl->mbTypeAsked = false; + } + + if ( mpImpl->mxObjRef.GetObject().is() ) + { + mpImpl->mxObjRef.AssignToContainer( &rContainer, mpImpl->aPersistName ); + mpImpl->mbConnected = true; + mpImpl->mxObjRef.Lock(); + } + } + + if (pCreator) + { + OUString sFrameURL(pCreator->GetAndClearInitialFrameURL()); + if (!sFrameURL.isEmpty() && svt::EmbeddedObjectRef::TryRunningState(mpImpl->mxObjRef.GetObject())) + { + uno::Reference<beans::XPropertySet> xSet(mpImpl->mxObjRef->getComponent(), uno::UNO_QUERY); + if (xSet.is()) + xSet->setPropertyValue("FrameURL", uno::Any(sFrameURL)); + } + } + + if ( mpImpl->mxObjRef.is() ) + { + if ( !mpImpl->mxLightClient.is() ) + mpImpl->mxLightClient = new SdrLightEmbeddedClient_Impl( this ); + + mpImpl->mxObjRef->addStateChangeListener( mpImpl->mxLightClient ); + mpImpl->mxObjRef->addEventListener( mpImpl->mxLightClient ); + + if ( mpImpl->mxObjRef->getCurrentState() != embed::EmbedStates::LOADED ) + GetSdrGlobalData().GetOLEObjCache().InsertObj(this); + + CheckFileLink_Impl(); + + uno::Reference< container::XChild > xChild( mpImpl->mxObjRef.GetObject(), uno::UNO_QUERY ); + if( xChild.is() ) + { + uno::Reference< uno::XInterface > xParent( getSdrModelFromSdrObject().getUnoModel()); + if( xParent.is()) + xChild->setParent( getSdrModelFromSdrObject().getUnoModel() ); + } + + } + } + catch( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::Connect_Impl()" ); + } +} + +void SdrOle2Obj::ObjectLoaded() +{ + AddListeners_Impl(); +} + +void SdrOle2Obj::AddListeners_Impl() +{ + if( !(mpImpl->mxObjRef.is() && mpImpl->mxObjRef->getCurrentState() != embed::EmbedStates::LOADED) ) + return; + + // register modify listener + if (!mpImpl->mxModifyListener.is()) + { + mpImpl->mxModifyListener = new SvxUnoShapeModifyListener(this); + } + + uno::Reference< util::XModifyBroadcaster > xBC( getXModel(), uno::UNO_QUERY ); + if (xBC.is()) + { + xBC->addModifyListener( mpImpl->mxModifyListener ); + } +} + +void SdrOle2Obj::Disconnect() +{ + if( IsEmptyPresObj() ) + return; + + if( !mpImpl->mbConnected ) + { + OSL_FAIL("Disconnect() called on disconnected object!"); + return; + } + + RemoveListeners_Impl(); + Disconnect_Impl(); +} + +void SdrOle2Obj::RemoveListeners_Impl() +{ + if ( !mpImpl->mxObjRef.is() || mpImpl->aPersistName.isEmpty() ) + return; + + try + { + sal_Int32 nState = mpImpl->mxObjRef->getCurrentState(); + if ( nState != embed::EmbedStates::LOADED ) + { + uno::Reference< util::XModifyBroadcaster > xBC( getXModel(), uno::UNO_QUERY ); + if (xBC.is() && mpImpl->mxModifyListener.is()) + { + xBC->removeModifyListener( mpImpl->mxModifyListener ); + } + } + } + catch( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::RemoveListeners_Impl()" ); + } +} + +void SdrOle2Obj::Disconnect_Impl() +{ + try + { + if ( !mpImpl->aPersistName.isEmpty() ) + { + if( getSdrModelFromSdrObject().IsInDestruction() ) + { + // TODO/LATER: here we must assume that the destruction of the model is enough to make clear that we will not + // remove the object from the container, even if the DrawingObject itself is not destroyed (unfortunately this + // There is no real need to do the following removing of the object from the container + // in case the model has correct persistence, but in case of problems such a removing + // would make the behavior of the office more stable + + comphelper::EmbeddedObjectContainer* pContainer = mpImpl->mxObjRef.GetContainer(); + if ( pContainer ) + { + pContainer->CloseEmbeddedObject( mpImpl->mxObjRef.GetObject() ); + mpImpl->mxObjRef.AssignToContainer( nullptr, mpImpl->aPersistName ); + } + + // happens later than the destruction of the model, so we can't assert that). + //DBG_ASSERT( bInDestruction, "Model is destroyed, but not me?!" ); + //TODO/LATER: should be make sure that the ObjectShell also forgets the object, because we will close it soon? + /* + uno::Reference < util::XCloseable > xClose( xObjRef, uno::UNO_QUERY ); + if ( xClose.is() ) + { + try + { + xClose->close( true ); + } + catch ( util::CloseVetoException& ) + { + // there's still someone who needs the object! + } + } + + xObjRef = NULL;*/ + } + else if ( mpImpl->mxObjRef.is() ) + { + if ( getSdrModelFromSdrObject().getUnoModel().is() ) + { + // remove object, but don't close it (that's up to someone else) + comphelper::EmbeddedObjectContainer* pContainer = mpImpl->mxObjRef.GetContainer(); + if ( pContainer ) + { + pContainer->RemoveEmbeddedObject( mpImpl->mxObjRef.GetObject() ); + + // TODO/LATER: mpImpl->aPersistName contains outdated information, to keep it updated + // it should be returned from RemoveEmbeddedObject call. Currently it is no problem, + // since no container is adjusted, actually the empty string could be provided as a name here + mpImpl->mxObjRef.AssignToContainer( nullptr, mpImpl->aPersistName ); + } + + DisconnectFileLink_Impl(); + } + } + } + + if ( mpImpl->mxObjRef.is() && mpImpl->mxLightClient.is() ) + { + mpImpl->mxObjRef->removeStateChangeListener ( mpImpl->mxLightClient ); + mpImpl->mxObjRef->removeEventListener( mpImpl->mxLightClient ); + mpImpl->mxObjRef->setClientSite( nullptr ); + + GetSdrGlobalData().GetOLEObjCache().RemoveObj(this); + } + } + catch( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::Disconnect_Impl()" ); + } + + mpImpl->mbConnected = false; +} + +rtl::Reference<SdrObject> SdrOle2Obj::createSdrGrafObjReplacement(bool bAddText) const +{ + const Graphic* pOLEGraphic = GetGraphic(); + + if(pOLEGraphic) + { + // #i118485# allow creating a SdrGrafObj representation + rtl::Reference<SdrGrafObj> pClone = new SdrGrafObj( + getSdrModelFromSdrObject(), + *pOLEGraphic); + + // copy transformation + basegfx::B2DHomMatrix aMatrix; + basegfx::B2DPolyPolygon aPolyPolygon; + + TRGetBaseGeometry(aMatrix, aPolyPolygon); + pClone->TRSetBaseGeometry(aMatrix, aPolyPolygon); + + // copy all attributes to support graphic styles for OLEs + pClone->SetStyleSheet(GetStyleSheet(), false); + pClone->SetMergedItemSet(GetMergedItemSet()); + + if(bAddText) + { + // #i118485# copy text (Caution! Model needed, as guaranteed in aw080) + OutlinerParaObject* pOPO = GetOutlinerParaObject(); + + if(pOPO) + { + pClone->NbcSetOutlinerParaObject(*pOPO); + } + } + + return rtl::Reference<SdrObject>(pClone); + } + else + { + // #i100710# pOLEGraphic may be zero (no visualisation available), + // so we need to use the OLE replacement graphic + rtl::Reference<SdrRectObj> pClone = new SdrRectObj( + getSdrModelFromSdrObject(), + GetSnapRect()); + + // gray outline + pClone->SetMergedItem(XLineStyleItem(css::drawing::LineStyle_SOLID)); + const svtools::ColorConfig aColorConfig; + const svtools::ColorConfigValue aColor(aColorConfig.GetColorValue(svtools::OBJECTBOUNDARIES)); + pClone->SetMergedItem(XLineColorItem(OUString(), aColor.nColor)); + + // bitmap fill + pClone->SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + pClone->SetMergedItem(XFillBitmapItem(OUString(), GetEmptyOLEReplacementGraphic())); + pClone->SetMergedItem(XFillBmpTileItem(false)); + pClone->SetMergedItem(XFillBmpStretchItem(false)); + + return rtl::Reference<SdrObject>(pClone); + } +} + +rtl::Reference<SdrObject> SdrOle2Obj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + // #i118485# missing converter added + rtl::Reference<SdrObject> pRetval = createSdrGrafObjReplacement(true); + + if(pRetval) + { + return pRetval->DoConvertToPolyObj(bBezier, bAddText); + } + + return nullptr; +} + +void SdrOle2Obj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + const bool bRemove(pNewPage == nullptr && pOldPage != nullptr); + const bool bInsert(pNewPage != nullptr && pOldPage == nullptr); + + if (bRemove && mpImpl->mbConnected ) + { + Disconnect(); + } + + // call parent + SdrRectObj::handlePageChange(pOldPage, pNewPage); + + if (bInsert && !mpImpl->mbConnected ) + { + Connect(); + } +} + +void SdrOle2Obj::SetObjRef( const css::uno::Reference < css::embed::XEmbeddedObject >& rNewObjRef ) +{ + DBG_ASSERT( !rNewObjRef.is() || !mpImpl->mxObjRef.GetObject().is(), "SetObjRef called on already initialized object!"); + if( rNewObjRef == mpImpl->mxObjRef.GetObject() ) + return; + + // the caller of the method is responsible to control the old object, it will not be closed here + // Otherwise WW8 import crashes because it transfers control to OLENode by this method + if ( mpImpl->mxObjRef.GetObject().is() ) + mpImpl->mxObjRef.Lock( false ); + + // avoid removal of object in Disconnect! It is definitely a HACK to call SetObjRef(0)! + // This call will try to close the objects; so if anybody else wants to keep it, it must be locked by a CloseListener + mpImpl->mxObjRef.Clear(); + + if ( mpImpl->mbConnected ) + Disconnect(); + + mpImpl->mxObjRef.Assign( rNewObjRef, GetAspect() ); + mpImpl->mbTypeAsked = false; + + if ( mpImpl->mxObjRef.is() ) + { + mpImpl->moGraphic.reset(); + + if ( mpImpl->mxObjRef->getStatus( GetAspect() ) & embed::EmbedMisc::EMBED_NEVERRESIZE ) + SetResizeProtect(true); + + // For math objects, set closed state to transparent + SetClosedObj(!ImplIsMathObj( rNewObjRef )); + + Connect(); + } + + SetChanged(); + BroadcastObjectChange(); +} + +void SdrOle2Obj::SetClosedObj( bool bIsClosed ) +{ + // TODO/LATER: do we still need this hack? + // Allow changes to the closed state of OLE objects + m_bClosedObj = bIsClosed; +} + +rtl::Reference<SdrObject> SdrOle2Obj::getFullDragClone() const +{ + // #i118485# use central replacement generator + return createSdrGrafObjReplacement(false); +} + +void SdrOle2Obj::SetPersistName( const OUString& rPersistName, SvxOle2Shape* pCreator ) +{ + DBG_ASSERT( mpImpl->aPersistName.isEmpty(), "Persist name changed!"); + + mpImpl->aPersistName = rPersistName; + mpImpl->mbLoadingOLEObjectFailed = false; + + Connect(pCreator); + SetChanged(); +} + +void SdrOle2Obj::AbandonObject() +{ + mpImpl->aPersistName.clear(); + mpImpl->mbLoadingOLEObjectFailed = false; + SetObjRef(nullptr); +} + +const OUString& SdrOle2Obj::GetPersistName() const +{ + return mpImpl->aPersistName; +} + +void SdrOle2Obj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + // #i118485# Allowing much more attributes for OLEs + rInfo.bRotateFreeAllowed = true; + rInfo.bRotate90Allowed = true; + rInfo.bMirrorFreeAllowed = true; + rInfo.bMirror45Allowed = true; + rInfo.bMirror90Allowed = true; + rInfo.bTransparenceAllowed = true; + rInfo.bShearAllowed = true; + rInfo.bEdgeRadiusAllowed = false; + rInfo.bNoOrthoDesired = false; + rInfo.bCanConvToPath = true; + rInfo.bCanConvToPoly = true; + rInfo.bCanConvToPathLineToArea = false; + rInfo.bCanConvToPolyLineToArea = false; + rInfo.bCanConvToContour = true; +} + +SdrObjKind SdrOle2Obj::GetObjIdentifier() const +{ + return mpImpl->mbFrame ? SdrObjKind::OLEPluginFrame : SdrObjKind::OLE2; +} + +OUString SdrOle2Obj::TakeObjNameSingul() const +{ + OUStringBuffer sName(SvxResId(mpImpl->mbFrame ? STR_ObjNameSingulFrame : STR_ObjNameSingulOLE2)); + + const OUString aName(GetName()); + + if (!aName.isEmpty()) + { + sName.append(" '" + aName + "\'"); + } + + return sName.makeStringAndClear(); +} + +OUString SdrOle2Obj::TakeObjNamePlural() const +{ + return SvxResId(mpImpl->mbFrame ? STR_ObjNamePluralFrame : STR_ObjNamePluralOLE2); +} + +rtl::Reference<SdrObject> SdrOle2Obj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrOle2Obj(rTargetModel, *this); +} + +void SdrOle2Obj::ImpSetVisAreaSize() +{ + // #i118524# do not again set VisAreaSize when the call comes from OLE client (e.g. ObjectAreaChanged) + if (mpImpl->mbSuppressSetVisAreaSize) + return; + + // currently there is no need to recalculate scaling for iconified objects + // TODO/LATER: it might be needed in future when it is possible to change the icon + if ( GetAspect() == embed::Aspects::MSOLE_ICON ) + return; + + // the object area of an embedded object was changed, e.g. by user interaction an a selected object + GetObjRef(); + if (!mpImpl->mxObjRef.is()) + return; + + sal_Int64 nMiscStatus = mpImpl->mxObjRef->getStatus( GetAspect() ); + + // the client is required to get access to scaling + SfxInPlaceClient* pClient( + SfxInPlaceClient::GetClient( + dynamic_cast<SfxObjectShell*>( + getSdrModelFromSdrObject().GetPersist()), + mpImpl->mxObjRef.GetObject())); + const bool bHasOwnClient( + mpImpl->mxLightClient.is() && + mpImpl->mxObjRef->getClientSite() == uno::Reference< embed::XEmbeddedClient >( mpImpl->mxLightClient ) ); + + if ( pClient || bHasOwnClient ) + { + // TODO: IMHO we need to do similar things when object is UIActive or OutplaceActive?! + if ( ((nMiscStatus & embed::EmbedMisc::MS_EMBED_RECOMPOSEONRESIZE) && + svt::EmbeddedObjectRef::TryRunningState( mpImpl->mxObjRef.GetObject() )) + || mpImpl->mxObjRef->getCurrentState() == embed::EmbedStates::INPLACE_ACTIVE + ) + { + Fraction aScaleWidth; + Fraction aScaleHeight; + if ( pClient ) + { + aScaleWidth = pClient->GetScaleWidth(); + aScaleHeight = pClient->GetScaleHeight(); + } + else + { + aScaleWidth = mpImpl->mxLightClient->GetScaleWidth(); + aScaleHeight = mpImpl->mxLightClient->GetScaleHeight(); + } + + // The object wants to resize itself (f.e. Chart wants to recalculate the layout) + // or object is inplace active and so has a window that must be resized also + // In these cases the change in the object area size will be reflected in a change of the + // objects' visual area. The scaling will not change, but it might exist already and must + // be used in calculations + MapUnit aMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( mpImpl->mxObjRef->getMapUnit( GetAspect() ) ); + Size aVisSize; + if (sal_Int32(aScaleWidth) != 0 && sal_Int32(aScaleHeight) != 0) // avoid div by zero + aVisSize = Size( static_cast<tools::Long>( Fraction( getRectangle().GetWidth() ) / aScaleWidth ), + static_cast<tools::Long>( Fraction( getRectangle().GetHeight() ) / aScaleHeight ) ); + + aVisSize = OutputDevice::LogicToLogic( + aVisSize, + MapMode(getSdrModelFromSdrObject().GetScaleUnit()), + MapMode(aMapUnit)); + awt::Size aSz; + aSz.Width = aVisSize.Width(); + aSz.Height = aVisSize.Height(); + mpImpl->mxObjRef->setVisualAreaSize( GetAspect(), aSz ); + + try + { + aSz = mpImpl->mxObjRef->getVisualAreaSize( GetAspect() ); + } + catch( embed::NoVisualAreaSizeException& ) + {} + + tools::Rectangle aAcceptedVisArea; + aAcceptedVisArea.SetSize( Size( static_cast<tools::Long>( Fraction( tools::Long( aSz.Width ) ) * aScaleWidth ), + static_cast<tools::Long>( Fraction( tools::Long( aSz.Height ) ) * aScaleHeight ) ) ); + if (aVisSize != aAcceptedVisArea.GetSize()) + { + // server changed VisArea to its liking and the VisArea is different than the suggested one + // store the new value as given by the object + MapUnit aNewMapUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( mpImpl->mxObjRef->getMapUnit( GetAspect() ) ); + auto aSize = OutputDevice::LogicToLogic(aAcceptedVisArea.GetSize(), MapMode(aNewMapUnit), MapMode(getSdrModelFromSdrObject().GetScaleUnit())); + setRectangleSize(aSize.Width(), aSize.Height()); + } + + // make the new object area known to the client + // compared to the "else" branch aRect might have been changed by the object and no additional scaling was applied + // WHY this -> OSL_ASSERT( pClient ); + if( pClient ) + pClient->SetObjArea(getRectangle()); + + // we need a new replacement image as the object has resized itself + + //#i79578# don't request a new replacement image for charts to often + //a chart sends a modified call to the framework if it was changed + //thus the replacement update is already handled there + if( !IsChart() ) + mpImpl->mxObjRef.UpdateReplacement(); + } + else + { + // The object isn't active and does not want to resize itself so the changed object area size + // will be reflected in a changed object scaling + Fraction aScaleWidth; + Fraction aScaleHeight; + Size aObjAreaSize; + if ( CalculateNewScaling( aScaleWidth, aScaleHeight, aObjAreaSize ) ) + { + if ( pClient ) + { + tools::Rectangle aScaleRect(getRectangle().TopLeft(), aObjAreaSize); + pClient->SetObjAreaAndScale( aScaleRect, aScaleWidth, aScaleHeight); + } + else + { + mpImpl->mxLightClient->SetSizeScale( aScaleWidth, aScaleHeight ); + } + } + } + } + else if( (nMiscStatus & embed::EmbedMisc::MS_EMBED_RECOMPOSEONRESIZE) && + svt::EmbeddedObjectRef::TryRunningState( mpImpl->mxObjRef.GetObject() ) ) + { + //also handle not sfx based ole objects e.g. charts + //#i83860# resizing charts in impress distorts fonts + uno::Reference< embed::XVisualObject > xVisualObject( getXModel(), uno::UNO_QUERY ); + if( xVisualObject.is() ) + { + const MapUnit aMapUnit( + VCLUnoHelper::UnoEmbed2VCLMapUnit( + mpImpl->mxObjRef->getMapUnit(GetAspect()))); + const Point aTL( getRectangle().TopLeft() ); + const Point aBR( getRectangle().BottomRight() ); + const Point aTL2( + OutputDevice::LogicToLogic( + aTL, + MapMode(getSdrModelFromSdrObject().GetScaleUnit()), + MapMode(aMapUnit))); + const Point aBR2( + OutputDevice::LogicToLogic( + aBR, + MapMode(getSdrModelFromSdrObject().GetScaleUnit()), + MapMode(aMapUnit))); + const tools::Rectangle aNewRect( + aTL2, + aBR2); + + xVisualObject->setVisualAreaSize( + GetAspect(), + awt::Size( + aNewRect.GetWidth(), + aNewRect.GetHeight())); + } + } +} + +void SdrOle2Obj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + if(!getSdrModelFromSdrObject().isLocked()) + { + GetObjRef(); + + if ( mpImpl->mxObjRef.is() && ( mpImpl->mxObjRef->getStatus( GetAspect() ) & embed::EmbedMisc::MS_EMBED_RECOMPOSEONRESIZE ) ) + { + // if the object needs recompose on resize + // the client site should be created before the resize will take place + // check whether there is no client site and create it if necessary + AddOwnLightClient(); + } + } + + SdrRectObj::NbcResize(rRef,xFact,yFact); + + if( !getSdrModelFromSdrObject().isLocked() ) + ImpSetVisAreaSize(); +} + +void SdrOle2Obj::SetGeoData(const SdrObjGeoData& rGeo) +{ + SdrRectObj::SetGeoData(rGeo); + + if( !getSdrModelFromSdrObject().isLocked() ) + ImpSetVisAreaSize(); +} + +void SdrOle2Obj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + SdrRectObj::NbcSetSnapRect(rRect); + + if( !getSdrModelFromSdrObject().isLocked() ) + ImpSetVisAreaSize(); + + if ( mpImpl->mxObjRef.is() && IsChart() ) + { + //#i103460# charts do not necessarily have an own size within ODF files, + //for this case they need to use the size settings from the surrounding frame, + //which is made available with this method as there is no other way + mpImpl->mxObjRef.SetDefaultSizeForChart( Size( rRect.GetWidth(), rRect.GetHeight() ) ); + } +} + +void SdrOle2Obj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + SdrRectObj::NbcSetLogicRect(rRect); + + if( !getSdrModelFromSdrObject().isLocked() ) + ImpSetVisAreaSize(); +} + +const Graphic* SdrOle2Obj::GetGraphic() const +{ + if ( mpImpl->mxObjRef.is() ) + return mpImpl->mxObjRef.GetGraphic(); + return mpImpl->moGraphic ? &*mpImpl->moGraphic : nullptr; +} + +void SdrOle2Obj::GetNewReplacement() +{ + if ( mpImpl->mxObjRef.is() ) + mpImpl->mxObjRef.UpdateReplacement(); +} + +Size SdrOle2Obj::GetOrigObjSize( MapMode const * pTargetMapMode ) const +{ + return mpImpl->mxObjRef.GetSize( pTargetMapMode ); +} + +void SdrOle2Obj::setSuppressSetVisAreaSize( bool bNew ) +{ + mpImpl->mbSuppressSetVisAreaSize = bNew; +} + +void SdrOle2Obj::NbcMove(const Size& rSize) +{ + SdrRectObj::NbcMove(rSize); + + if( !getSdrModelFromSdrObject().isLocked() ) + ImpSetVisAreaSize(); +} + +bool SdrOle2Obj::CanUnloadRunningObj( const uno::Reference< embed::XEmbeddedObject >& xObj, sal_Int64 nAspect ) +{ + uno::Reference<embed::XEmbedPersist2> xPersist(xObj, uno::UNO_QUERY); + if (xPersist.is()) + { + if (!xPersist->isStored()) + // It doesn't have persistent storage. We can't unload this. + return false; + } + + bool bResult = false; + + sal_Int32 nState = xObj->getCurrentState(); + if ( nState == embed::EmbedStates::LOADED ) + { + // the object is already unloaded + bResult = true; + } + else + { + uno::Reference < util::XModifiable > xModifiable( xObj->getComponent(), uno::UNO_QUERY ); + if ( !xModifiable.is() ) + bResult = true; + else + { + sal_Int64 nMiscStatus = xObj->getStatus( nAspect ); + + if ( embed::EmbedMisc::MS_EMBED_ALWAYSRUN != ( nMiscStatus & embed::EmbedMisc::MS_EMBED_ALWAYSRUN ) && + embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY != ( nMiscStatus & embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY ) && + !( xModifiable.is() && xModifiable->isModified() ) && + !( nState == embed::EmbedStates::INPLACE_ACTIVE || nState == embed::EmbedStates::UI_ACTIVE || nState == embed::EmbedStates::ACTIVE ) ) + { + bResult = true; + } + } + } + + return bResult; +} + +bool SdrOle2Obj::Unload( const uno::Reference< embed::XEmbeddedObject >& xObj, sal_Int64 nAspect ) +{ + bool bResult = false; + + if ( CanUnloadRunningObj( xObj, nAspect ) ) + { + try + { + xObj->changeState( embed::EmbedStates::LOADED ); + bResult = true; + } + catch( css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrOle2Obj::Unload()" ); + } + } + + return bResult; +} + +bool SdrOle2Obj::Unload() +{ + if (!mpImpl->mxObjRef.is()) + // Already unloaded. + return true; + + return Unload(mpImpl->mxObjRef.GetObject(), GetAspect()); +} + +void SdrOle2Obj::GetObjRef_Impl() +{ + if ( !mpImpl->mxObjRef.is() && !mpImpl->aPersistName.isEmpty() && getSdrModelFromSdrObject().GetPersist() ) + { + // Only try loading if it did not went wrong up to now + if(!mpImpl->mbLoadingOLEObjectFailed) + { + mpImpl->mxObjRef.Assign( + getSdrModelFromSdrObject().GetPersist()->getEmbeddedObjectContainer().GetEmbeddedObject(mpImpl->aPersistName), + GetAspect()); + mpImpl->mbTypeAsked = false; + CheckFileLink_Impl(); + + // If loading of OLE object failed, remember that to not invoke an endless + // loop trying to load it again and again. + if( mpImpl->mxObjRef.is() ) + { + mpImpl->mbLoadingOLEObjectFailed = true; + } + + // For math objects, set closed state to transparent + SetClosedObj(!ImplIsMathObj( mpImpl->mxObjRef.GetObject() )); + } + + if ( mpImpl->mxObjRef.is() ) + { + if( !IsEmptyPresObj() ) + { + // remember modified status of model + const bool bWasChanged(getSdrModelFromSdrObject().IsChanged()); + + // perhaps preview not valid anymore + // This line changes the modified state of the model + ClearGraphic(); + + // if status was not set before, force it back + // to not set, so that SetGraphic(0) above does not + // set the modified state of the model. + if(!bWasChanged && getSdrModelFromSdrObject().IsChanged()) + { + getSdrModelFromSdrObject().SetChanged( false ); + } + } + } + + if ( mpImpl->mxObjRef.is() ) + Connect(); + } + + if ( mpImpl->mbConnected ) + { + // move object to first position in cache + GetSdrGlobalData().GetOLEObjCache().InsertObj(this); + } +} + +uno::Reference < embed::XEmbeddedObject > const & SdrOle2Obj::GetObjRef() const +{ + const_cast<SdrOle2Obj*>(this)->GetObjRef_Impl(); + return mpImpl->mxObjRef.GetObject(); +} + +uno::Reference < embed::XEmbeddedObject > const & SdrOle2Obj::GetObjRef_NoInit() const +{ + return mpImpl->mxObjRef.GetObject(); +} + +uno::Reference< frame::XModel > SdrOle2Obj::getXModel() const +{ + if (svt::EmbeddedObjectRef::TryRunningState(GetObjRef())) + return uno::Reference< frame::XModel >( mpImpl->mxObjRef->getComponent(), uno::UNO_QUERY ); + else + return uno::Reference< frame::XModel >(); +} + +bool SdrOle2Obj::IsChart() const +{ + if (!mpImpl->mbTypeAsked) + { + mpImpl->mbIsChart = mpImpl->mxObjRef.IsChart(); + mpImpl->mbTypeAsked = true; + } + return mpImpl->mbIsChart; +} + +void SdrOle2Obj::SetGraphicToObj( const Graphic& aGraphic ) +{ + mpImpl->mxObjRef.SetGraphic( aGraphic, OUString() ); + // if the object isn't valid, e.g. link to something that doesn't exist, set the fallback + // graphic as mxGraphic so SdrOle2Obj::GetGraphic will show the fallback + if (const Graphic* pObjGraphic = mpImpl->mxObjRef.is() ? nullptr : mpImpl->mxObjRef.GetGraphic()) + mpImpl->moGraphic.emplace(*pObjGraphic); +} + +void SdrOle2Obj::SetGraphicToObj( const uno::Reference< io::XInputStream >& xGrStream, const OUString& aMediaType ) +{ + mpImpl->mxObjRef.SetGraphicStream( xGrStream, aMediaType ); + // if the object isn't valid, e.g. link to something that doesn't exist, set the fallback + // graphic as mxGraphic so SdrOle2Obj::GetGraphic will show the fallback + if (const Graphic* pObjGraphic = mpImpl->mxObjRef.is() ? nullptr : mpImpl->mxObjRef.GetGraphic()) + mpImpl->moGraphic.emplace(*pObjGraphic); +} + +bool SdrOle2Obj::IsCalc() const +{ + if ( !mpImpl->mxObjRef.is() ) + return false; + + SvGlobalName aObjClsId( mpImpl->mxObjRef->getClassID() ); + return SvGlobalName(SO3_SC_CLASSID_30) == aObjClsId + || SvGlobalName(SO3_SC_CLASSID_40) == aObjClsId + || SvGlobalName(SO3_SC_CLASSID_50) == aObjClsId + || SvGlobalName(SO3_SC_CLASSID_60) == aObjClsId + || SvGlobalName(SO3_SC_OLE_EMBED_CLASSID_60) == aObjClsId + || SvGlobalName(SO3_SC_OLE_EMBED_CLASSID_8) == aObjClsId + || SvGlobalName(SO3_SC_CLASSID) == aObjClsId; +} + +uno::Reference< frame::XModel > SdrOle2Obj::GetParentXModel() const +{ + return getSdrModelFromSdrObject().getUnoModel(); +} + +bool SdrOle2Obj::CalculateNewScaling( Fraction& aScaleWidth, Fraction& aScaleHeight, Size& aObjAreaSize ) +{ + // TODO/LEAN: to avoid rounding errors scaling always uses the VisArea. + // If we don't cache it for own objects also we must load the object here + if (!mpImpl->mxObjRef.is()) + return false; + + MapMode aMapMode(getSdrModelFromSdrObject().GetScaleUnit()); + aObjAreaSize = mpImpl->mxObjRef.GetSize( &aMapMode ); + + Size aSize = getRectangle().GetSize(); + if (!aObjAreaSize.Width() || !aObjAreaSize.Height()) + { + // avoid invalid fractions + aScaleWidth = Fraction(1,1); + aScaleHeight = Fraction(1,1); + } + else + { + aScaleWidth = Fraction(aSize.Width(), aObjAreaSize.Width() ); + aScaleHeight = Fraction(aSize.Height(), aObjAreaSize.Height() ); + // reduce to 10 binary digits + aScaleHeight.ReduceInaccurate(10); + aScaleWidth.ReduceInaccurate(10); + } + + return true; +} + +bool SdrOle2Obj::AddOwnLightClient() +{ + // The Own Light Client must be registered in object only using this method! + if ( !SfxInPlaceClient::GetClient( dynamic_cast<SfxObjectShell*>(getSdrModelFromSdrObject().GetPersist()), mpImpl->mxObjRef.GetObject() ) + && !( mpImpl->mxLightClient.is() && mpImpl->mxObjRef->getClientSite() == uno::Reference< embed::XEmbeddedClient >( mpImpl->mxLightClient ) ) ) + { + Connect(); + + if ( mpImpl->mxObjRef.is() && mpImpl->mxLightClient.is() ) + { + Fraction aScaleWidth; + Fraction aScaleHeight; + Size aObjAreaSize; + if ( CalculateNewScaling( aScaleWidth, aScaleHeight, aObjAreaSize ) ) + { + mpImpl->mxLightClient->SetSizeScale( aScaleWidth, aScaleHeight ); + try { + mpImpl->mxObjRef->setClientSite( mpImpl->mxLightClient ); + return true; + } catch( uno::Exception& ) + {} + } + + } + + return false; + } + + return true; +} + +Graphic SdrOle2Obj::GetEmptyOLEReplacementGraphic() +{ + return Graphic(BitmapEx(BMP_SVXOLEOBJ)); +} + +void SdrOle2Obj::SetWindow(const css::uno::Reference < css::awt::XWindow >& _xWindow) +{ + if ( mpImpl->mxObjRef.is() && mpImpl->mxLightClient.is() ) + { + mpImpl->mxLightClient->setWindow(_xWindow); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdopage.cxx b/svx/source/svdraw/svdopage.cxx new file mode 100644 index 0000000000..448758646a --- /dev/null +++ b/svx/source/svdraw/svdopage.cxx @@ -0,0 +1,180 @@ +/* -*- 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/svdopage.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <sdr/properties/pageproperties.hxx> +#include <sdr/contact/viewcontactofpageobj.hxx> + + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrPageObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::PageProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrPageObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfPageObj>(*this); +} + + +// this method is called from the destructor of the referenced page. +// do all necessary action to forget the page. It is not necessary to call +// RemovePageUser(), that is done from the destructor. +void SdrPageObj::PageInDestruction(const SdrPage& rPage) +{ + if(mpShownPage && mpShownPage == &rPage) + { + // #i58769# Do not call ActionChanged() here, because that would + // lead to the construction of a view contact object for a page that + // is being destroyed. + + mpShownPage = nullptr; + } +} + +SdrPageObj::SdrPageObj( + SdrModel& rSdrModel, + SdrPage* pNewPage) +: SdrObject(rSdrModel), + mpShownPage(pNewPage) +{ + if(mpShownPage) + { + mpShownPage->AddPageUser(*this); + } +} + +SdrPageObj::SdrPageObj(SdrModel& rSdrModel, SdrPageObj const & rSource) +: SdrObject(rSdrModel, rSource), + mpShownPage(nullptr) +{ + SetReferencedPage( rSource.GetReferencedPage()); +} + +SdrPageObj::SdrPageObj( + SdrModel& rSdrModel, + const tools::Rectangle& rRect, + SdrPage* pNewPage) +: SdrObject(rSdrModel), + mpShownPage(pNewPage) +{ + if(mpShownPage) + { + mpShownPage->AddPageUser(*this); + } + + setOutRectangle(rRect); +} + +SdrPageObj::~SdrPageObj() +{ + if(mpShownPage) + { + mpShownPage->RemovePageUser(*this); + } +} + + +void SdrPageObj::SetReferencedPage(SdrPage* pNewPage) +{ + if(mpShownPage == pNewPage) + return; + + if(mpShownPage) + { + mpShownPage->RemovePageUser(*this); + } + + mpShownPage = pNewPage; + + if(mpShownPage) + { + mpShownPage->AddPageUser(*this); + } + + SetChanged(); + BroadcastObjectChange(); +} + +// #i96598# +void SdrPageObj::SetBoundRectDirty() +{ + // avoid resetting aOutRect which in case of this object is model data, + // not re-creatable view data +} + +SdrObjKind SdrPageObj::GetObjIdentifier() const +{ + return SdrObjKind::Page; +} + +void SdrPageObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bRotateFreeAllowed=false; + rInfo.bRotate90Allowed =false; + rInfo.bMirrorFreeAllowed=false; + rInfo.bMirror45Allowed =false; + rInfo.bMirror90Allowed =false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed =false; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bNoOrthoDesired =false; + rInfo.bCanConvToPath =false; + rInfo.bCanConvToPoly =false; + rInfo.bCanConvToPathLineToArea=false; + rInfo.bCanConvToPolyLineToArea=false; +} + +rtl::Reference<SdrObject> SdrPageObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrPageObj(rTargetModel, *this); +} + +OUString SdrPageObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulPAGE)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrPageObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralPAGE); +} + +void SdrPageObj::NbcRotate(const Point& /*rRef*/, Degree100 /*nAngle*/, double /*sinAngle*/, double /*cosAngle*/) +{ + assert(false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdopath.cxx b/svx/source/svdraw/svdopath.cxx new file mode 100644 index 0000000000..d76faf3204 --- /dev/null +++ b/svx/source/svdraw/svdopath.cxx @@ -0,0 +1,2986 @@ +/* -*- 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 <o3tl/unit_conversion.hxx> +#include <tools/bigint.hxx> +#include <tools/helpers.hxx> +#include <svx/svdopath.hxx> +#include <math.h> +#include <svx/xpoly.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdview.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <svx/polypolygoneditor.hxx> +#include <sdr/contact/viewcontactofsdrpathobj.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <sdr/attribute/sdrtextattribute.hxx> +#include <sdr/primitive2d/sdrattributecreator.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sdr/attribute/sdrformtextattribute.hxx> +#include <utility> +#include <vcl/ptrstyle.hxx> +#include <memory> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace sdr; + +static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed) +{ + if (nPnt>0) { + nPnt--; + } else { + nPnt=nPntMax; + if (bClosed) nPnt--; + } + return nPnt; +} + +static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed) +{ + nPnt++; + if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0; + return nPnt; +} + +namespace { + +struct ImpSdrPathDragData : public SdrDragStatUserData +{ + XPolygon aXP; // section of the original polygon + bool bValid; // FALSE = too few points + bool bClosed; // closed object? + sal_uInt16 nPoly; // number of the polygon in the PolyPolygon + sal_uInt16 nPnt; // number of point in the above polygon + sal_uInt16 nPointCount; // number of points of the polygon + bool bBegPnt; // dragged point is first point of a Polyline + bool bEndPnt; // dragged point is finishing point of a Polyline + sal_uInt16 nPrevPnt; // index of previous point + sal_uInt16 nNextPnt; // index of next point + bool bPrevIsBegPnt; // previous point is first point of a Polyline + bool bNextIsEndPnt; // next point is first point of a Polyline + sal_uInt16 nPrevPrevPnt; // index of point before previous point + sal_uInt16 nNextNextPnt; // index of point after next point + bool bControl; // point is a control point + bool bIsNextControl; // point is a control point after a support point + bool bPrevIsControl; // if nPnt is a support point: a control point comes before + bool bNextIsControl; // if nPnt is a support point: a control point comes after + sal_uInt16 nPrevPrevPnt0; + sal_uInt16 nPrevPnt0; + sal_uInt16 nPnt0; + sal_uInt16 nNextPnt0; + sal_uInt16 nNextNextPnt0; + bool bEliminate; // delete point? (is set by MovDrag) + + bool mbMultiPointDrag; + const XPolyPolygon maOrig; + XPolyPolygon maMove; + std::vector<SdrHdl*> maHandles; + +public: + ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag); + void ResetPoly(const SdrPathObj& rPO); + bool IsMultiPointDrag() const { return mbMultiPointDrag; } +}; + +} + +ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag) + : aXP(5) + , bValid(false) + , bClosed(false) + , nPoly(0) + , nPnt(0) + , nPointCount(0) + , bBegPnt(false) + , bEndPnt(false) + , nPrevPnt(0) + , nNextPnt(0) + , bPrevIsBegPnt(false) + , bNextIsEndPnt(false) + , nPrevPrevPnt(0) + , nNextNextPnt(0) + , bControl(false) + , bIsNextControl(false) + , bPrevIsControl(false) + , bNextIsControl(false) + , nPrevPrevPnt0(0) + , nPrevPnt0(0) + , nPnt0(0) + , nNextPnt0(0) + , nNextNextPnt0(0) + , bEliminate(false) + , mbMultiPointDrag(bMuPoDr) + , maOrig(rPO.GetPathPoly()) + , maHandles(0) +{ + if(mbMultiPointDrag) + { + const SdrMarkView& rMarkView = *rDrag.GetView(); + const SdrHdlList& rHdlList = rMarkView.GetHdlList(); + const size_t nHdlCount = rHdlList.GetHdlCount(); + const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr); + + for(size_t a = 0; a < nHdlCount; ++a) + { + SdrHdl* pTestHdl = rHdlList.GetHdl(a); + + if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject) + { + maHandles.push_back(pTestHdl); + } + } + + maMove = maOrig; + bValid = true; + } + else + { + sal_uInt16 nPntMax = 0; // maximum index + bValid=false; + bClosed=rPO.IsClosed(); // closed object? + nPoly=static_cast<sal_uInt16>(rHdl.GetPolyNum()); // number of the polygon in the PolyPolygon + nPnt=static_cast<sal_uInt16>(rHdl.GetPointNum()); // number of points in the above polygon + const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly)); + nPointCount=aTmpXP.GetPointCount(); // number of point of the polygon + if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon + nPntMax=nPointCount-1; // maximum index + bBegPnt=!bClosed && nPnt==0; // dragged point is first point of a Polyline + bEndPnt=!bClosed && nPnt==nPntMax; // dragged point is finishing point of a Polyline + if (bClosed && nPointCount<=3) { // if polygon is only a line + bBegPnt=(nPointCount<3) || nPnt==0; + bEndPnt=(nPointCount<3) || nPnt==nPntMax-1; + } + nPrevPnt=nPnt; // index of previous point + nNextPnt=nPnt; // index of next point + if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed); + if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed); + bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0); + bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax); + nPrevPrevPnt=nPnt; // index of point before previous point + nNextNextPnt=nPnt; // index of point after next point + if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed); + if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed); + bControl=rHdl.IsPlusHdl(); // point is a control point + bIsNextControl=false; // point is a control point after a support point + bPrevIsControl=false; // if nPnt is a support point: a control point comes before + bNextIsControl=false; // if nPnt is a support point: a control point comes after + if (bControl) { + bIsNextControl=!aTmpXP.IsControl(nPrevPnt); + } else { + bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control; + bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control; + } + nPrevPrevPnt0=nPrevPrevPnt; + nPrevPnt0 =nPrevPnt; + nPnt0 =nPnt; + nNextPnt0 =nNextPnt; + nNextNextPnt0=nNextNextPnt; + nPrevPrevPnt=0; + nPrevPnt=1; + nPnt=2; + nNextPnt=3; + nNextNextPnt=4; + bEliminate=false; + ResetPoly(rPO); + bValid=true; + } +} + +void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO) +{ + const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly)); + aXP[0]=aTmpXP[nPrevPrevPnt0]; aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0)); + aXP[1]=aTmpXP[nPrevPnt0]; aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0)); + aXP[2]=aTmpXP[nPnt0]; aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0)); + aXP[3]=aTmpXP[nNextPnt0]; aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0)); + aXP[4]=aTmpXP[nNextNextPnt0]; aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0)); +} + +namespace { + +struct ImpPathCreateUser : public SdrDragStatUserData +{ + Point aBezControl0; + Point aBezStart; + Point aBezCtrl1; + Point aBezCtrl2; + Point aBezEnd; + Point aCircStart; + Point aCircEnd; + Point aCircCenter; + Point aLineStart; + Point aLineEnd; + Point aRectP1; + Point aRectP2; + Point aRectP3; + tools::Long nCircRadius; + Degree100 nCircStAngle; + Degree100 nCircRelAngle; + bool bBezier; + bool bBezHasCtrl0; + bool bCircle; + bool bAngleSnap; + bool bLine; + bool bLine90; + bool bRect; + bool bMixedCreate; + sal_uInt16 nBezierStartPoint; + SdrObjKind eStartKind; + SdrObjKind eCurrentKind; + +public: + ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0), + bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false), + bMixedCreate(false),nBezierStartPoint(0),eStartKind(SdrObjKind::NONE),eCurrentKind(SdrObjKind::NONE) { } + + void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; } + bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; } + XPolygon GetFormPoly() const; + void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown); + XPolygon GetBezierPoly() const; + void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); + XPolygon GetCirclePoly() const; + void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); + static Point CalcLine(const Point& rCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView); + XPolygon GetLinePoly() const; + void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView); + XPolygon GetRectPoly() const; +}; + +} + +XPolygon ImpPathCreateUser::GetFormPoly() const +{ + if (bBezier) return GetBezierPoly(); + if (bCircle) return GetCirclePoly(); + if (bLine) return GetLinePoly(); + if (bRect) return GetRectPoly(); + return XPolygon(); +} + +void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown) +{ + aBezStart=rP1; + aBezCtrl1=rP1+rDir; + aBezCtrl2=rP2; + + // #i21479# + // Also copy the end point when no end point is set yet + if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2; + + bBezier=true; +} + +XPolygon ImpPathCreateUser::GetBezierPoly() const +{ + XPolygon aXP(4); + aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth); + aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control); + aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control); + aXP[3]=aBezEnd; + return aXP; +} + +void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) +{ + Degree100 nTangAngle=GetAngle(rDir); + aCircStart=rP1; + aCircEnd=rP2; + aCircCenter=rP1; + tools::Long dx=rP2.X()-rP1.X(); + tools::Long dy=rP2.Y()-rP1.Y(); + Degree100 dAngle=GetAngle(Point(dx,dy))-nTangAngle; + dAngle=NormAngle36000(dAngle); + Degree100 nTmpAngle=NormAngle36000(9000_deg100-dAngle); + bool bRet=nTmpAngle!=9000_deg100 && nTmpAngle!=27000_deg100; + tools::Long nRad=0; + if (bRet) { + double cs = cos(toRadians(nTmpAngle)); + double nR=static_cast<double>(GetLen(Point(dx,dy)))/cs/2; + nRad=std::abs(FRound(nR)); + } + if (dAngle<18000_deg100) { + nCircStAngle=NormAngle36000(nTangAngle-9000_deg100); + nCircRelAngle=NormAngle36000(2_deg100*dAngle); + aCircCenter.AdjustX(FRound(nRad * cos(toRadians(nTangAngle + 9000_deg100)))); + aCircCenter.AdjustY(-(FRound(nRad * sin(toRadians(nTangAngle + 9000_deg100))))); + } else { + nCircStAngle=NormAngle36000(nTangAngle+9000_deg100); + nCircRelAngle=-NormAngle36000(36000_deg100-2_deg100*dAngle); + aCircCenter.AdjustX(FRound(nRad * cos(toRadians(nTangAngle - 9000_deg100)))); + aCircCenter.AdjustY(-(FRound(nRad * sin(toRadians(nTangAngle - 9000_deg100))))); + } + bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled(); + if (bAngleSnap) { + Degree100 nSA=pView->GetSnapAngle(); + if (nSA) { // angle snapping + bool bNeg=nCircRelAngle<0_deg100; + if (bNeg) nCircRelAngle=-nCircRelAngle; + nCircRelAngle+=nSA/2_deg100; + nCircRelAngle/=nSA; + nCircRelAngle*=nSA; + nCircRelAngle=NormAngle36000(nCircRelAngle); + if (bNeg) nCircRelAngle=-nCircRelAngle; + } + } + nCircRadius=nRad; + if (nRad==0 || abs(nCircRelAngle).get()<5) bRet=false; + bCircle=bRet; +} + +XPolygon ImpPathCreateUser::GetCirclePoly() const +{ + if (nCircRelAngle>=0_deg100) { + XPolygon aXP(aCircCenter,nCircRadius,nCircRadius, + nCircStAngle, nCircStAngle+nCircRelAngle,false); + aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth); + if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd; + return aXP; + } else { + XPolygon aXP(aCircCenter,nCircRadius,nCircRadius, + NormAngle36000(nCircStAngle+nCircRelAngle), nCircStAngle,false); + sal_uInt16 nCount=aXP.GetPointCount(); + for (sal_uInt16 nNum=nCount/2; nNum>0;) { + nNum--; // reverse XPoly's order of points + sal_uInt16 n2=nCount-nNum-1; + Point aPt(aXP[nNum]); + aXP[nNum]=aXP[n2]; + aXP[n2]=aPt; + } + aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth); + if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd; + return aXP; + } +} + +Point ImpPathCreateUser::CalcLine(const Point& aCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView) +{ + tools::Long x=aCsr.X(); + tools::Long y=aCsr.Y(); + bool bHLin=nDirY==0; + bool bVLin=nDirX==0; + if (bHLin) y=0; + else if (bVLin) x=0; + else { + tools::Long x1=BigMulDiv(y,nDirX,nDirY); + tools::Long y1=y; + tools::Long x2=x; + tools::Long y2=BigMulDiv(x,nDirY,nDirX); + tools::Long l1=std::abs(x1)+std::abs(y1); + tools::Long l2=std::abs(x2)+std::abs(y2); + if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) { + x=x1; y=y1; + } else { + x=x2; y=y2; + } + } + return Point(x,y); +} + +void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) +{ + aLineStart=rP1; + aLineEnd=rP2; + bLine90=false; + if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; } + Point aTmpPt(rP2-rP1); + tools::Long nDirX=rDir.X(); + tools::Long nDirY=rDir.Y(); + Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; tools::Long nQ1=std::abs(aP1.X())+std::abs(aP1.Y()); + Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; tools::Long nQ2=std::abs(aP2.X())+std::abs(aP2.Y()); + if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle + bLine90=nQ1>2*nQ2; + if (!bLine90) { // smooth transition + aLineEnd+=aP1; + } else { // rectangular transition + aLineEnd+=aP2; + } + bLine=true; +} + +XPolygon ImpPathCreateUser::GetLinePoly() const +{ + XPolygon aXP(2); + aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth); + aXP[1]=aLineEnd; + return aXP; +} + +void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView) +{ + aRectP1=rP1; + aRectP2=rP1; + aRectP3=rP2; + if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; } + Point aTmpPt(rP2-rP1); + tools::Long nDirX=rDir.X(); + tools::Long nDirY=rDir.Y(); + tools::Long x=aTmpPt.X(); + tools::Long y=aTmpPt.Y(); + bool bHLin=nDirY==0; + bool bVLin=nDirX==0; + if (bHLin) y=0; + else if (bVLin) x=0; + else { + y=BigMulDiv(x,nDirY,nDirX); + tools::Long nHypLen=aTmpPt.Y()-y; + Degree100 nTangAngle=-GetAngle(rDir); + // sin=g/h, g=h*sin + double a = toRadians(nTangAngle); + double sn=sin(a); + double cs=cos(a); + double nGKathLen=nHypLen*sn; + y+=FRound(nGKathLen*sn); + x+=FRound(nGKathLen*cs); + } + aRectP2.AdjustX(x ); + aRectP2.AdjustY(y ); + if (pView!=nullptr && pView->IsOrtho()) { + tools::Long dx1=aRectP2.X()-aRectP1.X(); tools::Long dx1a=std::abs(dx1); + tools::Long dy1=aRectP2.Y()-aRectP1.Y(); tools::Long dy1a=std::abs(dy1); + tools::Long dx2=aRectP3.X()-aRectP2.X(); tools::Long dx2a=std::abs(dx2); + tools::Long dy2=aRectP3.Y()-aRectP2.Y(); tools::Long dy2a=std::abs(dy2); + bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a; + if (b1MoreThan2 != pView->IsBigOrtho()) { + tools::Long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp; + tools::Long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp; + aRectP2.AdjustX(xtemp ); + aRectP2.AdjustY(ytemp ); + aRectP3.AdjustX(xtemp ); + aRectP3.AdjustY(ytemp ); + } else { + tools::Long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp; + tools::Long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp; + aRectP3.AdjustX(xtemp ); + aRectP3.AdjustY(ytemp ); + } + } + bRect=true; +} + +XPolygon ImpPathCreateUser::GetRectPoly() const +{ + XPolygon aXP(3); + aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth); + aXP[1]=aRectP2; + if (aRectP3!=aRectP2) aXP[2]=aRectP3; + return aXP; +} + +class ImpPathForDragAndCreate +{ + SdrPathObj& mrSdrPathObject; + XPolyPolygon aPathPolygon; + SdrObjKind meObjectKind; + std::unique_ptr<ImpSdrPathDragData> + mpSdrPathDragData; + bool mbCreating; + +public: + explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject); + + // drag stuff + bool beginPathDrag( SdrDragStat const & rDrag ) const; + bool movePathDrag( SdrDragStat& rDrag ) const; + bool endPathDrag( SdrDragStat const & rDrag ); + OUString getSpecialDragComment(const SdrDragStat& rDrag) const; + basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const; + + // create stuff + void BegCreate(SdrDragStat& rStat); + bool MovCreate(SdrDragStat& rStat); + bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd); + bool BckCreate(SdrDragStat const & rStat); + void BrkCreate(SdrDragStat& rStat); + PointerStyle GetCreatePointer() const; + + // helping stuff + static bool IsClosed(SdrObjKind eKind) { return eKind==SdrObjKind::Polygon || eKind==SdrObjKind::PathPoly || eKind==SdrObjKind::PathFill || eKind==SdrObjKind::FreehandFill; } + static bool IsFreeHand(SdrObjKind eKind) { return eKind==SdrObjKind::FreehandLine || eKind==SdrObjKind::FreehandFill; } + static bool IsBezier(SdrObjKind eKind) { return eKind==SdrObjKind::PathLine || eKind==SdrObjKind::PathFill; } + bool IsCreating() const { return mbCreating; } + + // get the polygon + basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const; + static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag); + basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return aPathPolygon.getB2DPolyPolygon(); } +}; + +ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject) +: mrSdrPathObject(rSdrPathObject), + aPathPolygon(rSdrPathObject.GetPathPoly()), + meObjectKind(mrSdrPathObject.meKind), + mbCreating(false) +{ +} + +bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag ) const +{ + const SdrHdl* pHdl=rDrag.GetHdl(); + if(!pHdl) + return false; + + bool bMultiPointDrag(true); + + if(aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())].IsControl(static_cast<sal_uInt16>(pHdl->GetPointNum()))) + bMultiPointDrag = false; + + if(bMultiPointDrag) + { + const SdrMarkView& rMarkView = *rDrag.GetView(); + const SdrHdlList& rHdlList = rMarkView.GetHdlList(); + const size_t nHdlCount = rHdlList.GetHdlCount(); + const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr); + sal_uInt32 nSelectedPoints(0); + + for(size_t a = 0; a < nHdlCount; ++a) + { + SdrHdl* pTestHdl = rHdlList.GetHdl(a); + + if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject) + { + nSelectedPoints++; + } + } + + if(nSelectedPoints <= 1) + bMultiPointDrag = false; + } + + const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) ); + + if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) + { + OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid."); + const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset(); + return false; + } + + return true; +} + +bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const +{ + if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) + { + OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); + return false; + } + + if(mpSdrPathDragData->IsMultiPointDrag()) + { + Point aDelta(rDrag.GetNow() - rDrag.GetStart()); + + if(aDelta.X() || aDelta.Y()) + { + for(SdrHdl* pHandle : mpSdrPathDragData->maHandles) + { + const sal_uInt16 nPolyIndex(static_cast<sal_uInt16>(pHandle->GetPolyNum())); + const sal_uInt16 nPointIndex(static_cast<sal_uInt16>(pHandle->GetPointNum())); + const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex]; + XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex]; + const sal_uInt16 nPointCount(rOrig.GetPointCount()); + bool bClosed(rOrig[0] == rOrig[nPointCount-1]); + + // move point itself + rMove[nPointIndex] = rOrig[nPointIndex] + aDelta; + + // when point is first and poly closed, move close point, too. + if(nPointCount > 0 && !nPointIndex && bClosed) + { + rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta; + + // when moving the last point it may be necessary to move the + // control point in front of this one, too. + if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2)) + rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta; + } + + // is a control point before this? + if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1)) + { + // Yes, move it, too + rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta; + } + + // is a control point after this? + if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1)) + { + // Yes, move it, too + rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta; + } + } + } + } + else + { + mpSdrPathDragData->ResetPoly(mrSdrPathObject); + + // copy certain data locally to use less code and have faster access times + bool bClosed =mpSdrPathDragData->bClosed ; // closed object? + sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of point in the above polygon + bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is first point of a Polyline + bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is last point of a Polyline + sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of previous point + sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of next point + bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline + bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline + sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point + sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index if the point after the next point + bool bControl =mpSdrPathDragData->bControl ; // point is a control point + bool bIsNextControl =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point + bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before + bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after + + // Ortho for lines/polygons: keep angle + if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) { + bool bBigOrtho=rDrag.GetView()->IsBigOrtho(); + Point aPos(rDrag.GetNow()); // current position + Point aPnt(mpSdrPathDragData->aXP[nPnt]); // the dragged point + sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points + Point aNewPos1,aNewPos2; // new alternative for aPos + bool bPnt1 = false, bPnt2 = false; // are these valid alternatives? + if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines + if (!bBegPnt) nPnt1=nPrevPnt; + if (!bEndPnt) nPnt2=nNextPnt; + } + if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon + nPnt1=nPrevPnt; + nPnt2=nNextPnt; + } + if (nPnt1!=0xFFFF && !bPrevIsControl) { + Point aPnt1=mpSdrPathDragData->aXP[nPnt1]; + tools::Long ndx0=aPnt.X()-aPnt1.X(); + tools::Long ndy0=aPnt.Y()-aPnt1.Y(); + bool bHLin=ndy0==0; + bool bVLin=ndx0==0; + if (!bHLin || !bVLin) { + tools::Long ndx=aPos.X()-aPnt1.X(); + tools::Long ndy=aPos.Y()-aPnt1.Y(); + bPnt1=true; + double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0); + double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0); + bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho); + bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho); + if (bHor) ndy=tools::Long(ndy0*nXFact); + if (bVer) ndx=tools::Long(ndx0*nYFact); + aNewPos1=aPnt1; + aNewPos1.AdjustX(ndx ); + aNewPos1.AdjustY(ndy ); + } + } + if (nPnt2!=0xFFFF && !bNextIsControl) { + Point aPnt2=mpSdrPathDragData->aXP[nPnt2]; + tools::Long ndx0=aPnt.X()-aPnt2.X(); + tools::Long ndy0=aPnt.Y()-aPnt2.Y(); + bool bHLin=ndy0==0; + bool bVLin=ndx0==0; + if (!bHLin || !bVLin) { + tools::Long ndx=aPos.X()-aPnt2.X(); + tools::Long ndy=aPos.Y()-aPnt2.Y(); + bPnt2=true; + double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0); + double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0); + bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho); + bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho); + if (bHor) ndy=tools::Long(ndy0*nXFact); + if (bVer) ndx=tools::Long(ndx0*nYFact); + aNewPos2=aPnt2; + aNewPos2.AdjustX(ndx ); + aNewPos2.AdjustY(ndy ); + } + } + if (bPnt1 && bPnt2) { // both alternatives exist (and compete) + BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1; + BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1; + BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2; + BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2; + nX1+=nY1; // correction distance to square + nX2+=nY2; // correction distance to square + // let the alternative that allows fewer correction win + if (nX1<nX2) bPnt2=false; else bPnt1=false; + } + if (bPnt1) rDrag.SetNow(aNewPos1); + if (bPnt2) rDrag.SetNow(aNewPos2); + } + rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow())); + + // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway + if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() && + !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl) + { + Point aPt(mpSdrPathDragData->aXP[nNextPnt]); + aPt-=rDrag.GetNow(); + Degree100 nAngle1=GetAngle(aPt); + aPt=rDrag.GetNow(); + aPt-=mpSdrPathDragData->aXP[nPrevPnt]; + Degree100 nAngle2=GetAngle(aPt); + Degree100 nDiff=nAngle1-nAngle2; + nDiff=abs(nDiff); + mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle(); + if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends + aPt=mpSdrPathDragData->aXP[nNextPnt]; + aPt+=mpSdrPathDragData->aXP[nPrevPnt]; + aPt/=2; + rDrag.SetNow(aPt); + } + } + + // we dragged by this distance + Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt]; + + /* There are 8 possible cases: + X 1. A control point neither on the left nor on the right. + o--X--o 2. There are control points on the left and the right, we are dragging a support point. + o--X 3. There is a control point on the left, we are dragging a support point. + X--o 4. There is a control point on the right, we are dragging a support point. + x--O--o 5. There are control points on the left and the right, we are dragging the left one. + x--O 6. There is a control point on the left, we are dragging it. + o--O--x 7. There are control points on the left and the right, we are dragging the right one. + O--x 8. There is a control point on the right, we are dragging it. + Note: modifying a line (not a curve!) might create a curve on the other end of the line + if Smooth is set there (with control points aligned to line). + */ + + mpSdrPathDragData->aXP[nPnt]+=aDiff; + + // now check symmetric plus handles + if (bControl) { // cases 5,6,7,8 + sal_uInt16 nSt; // the associated support point + sal_uInt16 nFix; // the opposing control point + if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point + nSt=nPrevPnt; + nFix=nPrevPrevPnt; + } else { + nSt=nNextPnt; + nFix=nNextNextPnt; + } + if (mpSdrPathDragData->aXP.IsSmooth(nSt)) { + mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix); + } + } + + if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below. + // move both control points + if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff; + if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff; + // align control point to line, if appropriate + if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) { + if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3 + mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt); + } + if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4 + mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt); + } + } + // Now check the other ends of the line (nPnt+-1). If there is a + // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the + // associated control point (nPnt+-2) has to be adapted. + if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) { + if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { + mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt); + } + } + if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) { + if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { + mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt); + } + } + } + } + + return true; +} + +bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag) +{ + Point aLinePt1; + Point aLinePt2; + bool bLineGlueMirror(SdrObjKind::Line == meObjectKind); + if (bLineGlueMirror) { + XPolygon& rXP=aPathPolygon[0]; + aLinePt1=rXP[0]; + aLinePt2=rXP[1]; + } + + if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) + { + OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); + return false; + } + + if(mpSdrPathDragData->IsMultiPointDrag()) + { + aPathPolygon = mpSdrPathDragData->maMove; + } + else + { + const SdrHdl* pHdl=rDrag.GetHdl(); + + // reference the polygon + XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())]; + + // the 5 points that might have changed + if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt]; + if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt]; + if (!mpSdrPathDragData->bBegPnt) rXP[mpSdrPathDragData->nPrevPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt]; + if (!mpSdrPathDragData->bEndPnt) rXP[mpSdrPathDragData->nNextPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt]; + rXP[mpSdrPathDragData->nPnt0] =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt]; + + // for closed objects: last point has to be equal to first point + if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0]; + + if (mpSdrPathDragData->bEliminate) + { + basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon()); + sal_uInt32 nPoly,nPnt; + + if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt)) + { + basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly)); + aCandidate.remove(nPnt); + + if(aCandidate.count() < 2) + { + aTempPolyPolygon.remove(nPoly); + } + else + { + aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate); + } + } + + aPathPolygon = XPolyPolygon(aTempPolyPolygon); + } + + // adapt angle for text beneath a simple line + if (bLineGlueMirror) + { + Point aLinePt1_(aPathPolygon[0][0]); + Point aLinePt2_(aPathPolygon[0][1]); + bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X()); + bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y()); + if (bXMirr || bYMirr) { + Point aRef1(mrSdrPathObject.GetSnapRect().Center()); + if (bXMirr) { + Point aRef2(aRef1); + aRef2.AdjustY( 1 ); + mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2); + } + if (bYMirr) { + Point aRef2(aRef1); + aRef2.AdjustX( 1 ); + mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2); + } + } + } + } + + mpSdrPathDragData.reset(); + + return true; +} + +OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + OUString aStr; + const SdrHdl* pHdl = rDrag.GetHdl(); + const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment && rDrag.GetUser()) + { + // #i103058# re-add old creation comment mode + const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser()); + const SdrObjKind eOriginalKind(meObjectKind); + mrSdrPathObject.meKind = pU->eCurrentKind; + aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj); + mrSdrPathObject.meKind = eOriginalKind; + + Point aPrev(rDrag.GetPrev()); + Point aNow(rDrag.GetNow()); + + if(pU->bLine) + aNow = pU->aLineEnd; + + aNow -= aPrev; + aStr += " ("; + + if(pU->bCircle) + { + aStr += SdrModel::GetAngleString(abs(pU->nCircRelAngle)) + + " r=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true); + } + + aStr += "dx=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true) + + " dy=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true); + + if(!IsFreeHand(meObjectKind)) + { + sal_Int32 nLen(GetLen(aNow)); + Degree100 nAngle(GetAngle(aNow)); + aStr += " l=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + + " " + + SdrModel::GetAngleString(nAngle); + } + + aStr += ")"; + } + else if(!pHdl) + { + // #i103058# fallback when no model and/or Handle, both needed + // for else-path + aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj); + } + else + { + // #i103058# standard for modification; model and handle needed + ImpSdrPathDragData* pDragData = mpSdrPathDragData.get(); + + if(!pDragData) + { + // getSpecialDragComment is also used from create, so fallback to GetUser() + // when mpSdrPathDragData is not set + pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser()); + } + + if(!pDragData) + { + OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); + return OUString(); + } + + if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate) + { + // point of ... + aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint); + + // delete %O + OUString aStr2(SvxResId(STR_EditDelete)); + + // UNICODE: delete point of ... + aStr2 = aStr2.replaceFirst("%1", aStr); + + return aStr2; + } + + // dx=0.00 dy=0.00 -- both sides bezier + // dx=0.00 dy=0.00 l=0.00 0.00\302\260 -- one bezier/lever on one side, a start, or an ending + // dx=0.00 dy=0.00 l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between + Point aBeg(rDrag.GetStart()); + Point aNow(rDrag.GetNow()); + + aStr.clear(); + aStr += "dx=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true) + + " dy=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true); + + if(!pDragData->IsMultiPointDrag()) + { + sal_uInt16 nPntNum(static_cast<sal_uInt16>(pHdl->GetPointNum())); + const XPolygon& rXPoly = aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())]; + sal_uInt16 nPointCount(rXPoly.GetPointCount()); + bool bClose(IsClosed(meObjectKind)); + + if(bClose) + nPointCount--; + + if(pHdl->IsPlusHdl()) + { + // lever + sal_uInt16 nRef(nPntNum); + + if(rXPoly.IsControl(nPntNum + 1)) + nRef--; + else + nRef++; + + aNow -= rXPoly[nRef]; + + sal_Int32 nLen(GetLen(aNow)); + Degree100 nAngle(GetAngle(aNow)); + aStr += " l=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + + " " + + SdrModel::GetAngleString(nAngle); + } + else if(nPointCount > 1) + { + sal_uInt16 nPntMax(nPointCount - 1); + bool bIsClosed(IsClosed(meObjectKind)); + bool bPt1(nPntNum > 0); + bool bPt2(nPntNum < nPntMax); + + if(bIsClosed && nPointCount > 2) + { + bPt1 = true; + bPt2 = true; + } + + sal_uInt16 nPt1,nPt2; + + if(nPntNum > 0) + nPt1 = nPntNum - 1; + else + nPt1 = nPntMax; + + if(nPntNum < nPntMax) + nPt2 = nPntNum + 1; + else + nPt2 = 0; + + if(bPt1 && rXPoly.IsControl(nPt1)) + bPt1 = false; // don't display + + if(bPt2 && rXPoly.IsControl(nPt2)) + bPt2 = false; // of bezier data + + if(bPt1) + { + Point aPt(aNow); + aPt -= rXPoly[nPt1]; + + sal_Int32 nLen(GetLen(aPt)); + Degree100 nAngle(GetAngle(aPt)); + aStr += " l=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + + " " + + SdrModel::GetAngleString(nAngle); + } + + if(bPt2) + { + if(bPt1) + aStr += " / "; + else + aStr += " "; + + Point aPt(aNow); + aPt -= rXPoly[nPt2]; + + sal_Int32 nLen(GetLen(aPt)); + Degree100 nAngle(GetAngle(aPt)); + aStr += "l=" + + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true) + + " " + + SdrModel::GetAngleString(nAngle); + } + } + } + } + + return aStr; +} + +basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const +{ + if(!mpSdrPathDragData || !mpSdrPathDragData->bValid) + { + OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid."); + return basegfx::B2DPolyPolygon(); + } + + XPolyPolygon aRetval; + + if(mpSdrPathDragData->IsMultiPointDrag()) + { + aRetval.Insert(mpSdrPathDragData->maMove); + } + else + { + const XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())]; + if (rXP.GetPointCount()<=2) { + XPolygon aXPoly(rXP); + aXPoly[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow(); + aRetval.Insert(std::move(aXPoly)); + return aRetval.getB2DPolyPolygon(); + } + // copy certain data locally to use less code and have faster access times + bool bClosed =mpSdrPathDragData->bClosed ; // closed object? + sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points + sal_uInt16 nPnt =mpSdrPathDragData->nPnt ; // number of points in the polygon + bool bBegPnt =mpSdrPathDragData->bBegPnt ; // dragged point is the first point of a Polyline + bool bEndPnt =mpSdrPathDragData->bEndPnt ; // dragged point is the last point of a Polyline + sal_uInt16 nPrevPnt =mpSdrPathDragData->nPrevPnt ; // index of the previous point + sal_uInt16 nNextPnt =mpSdrPathDragData->nNextPnt ; // index of the next point + bool bPrevIsBegPnt =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline + bool bNextIsEndPnt =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline + sal_uInt16 nPrevPrevPnt =mpSdrPathDragData->nPrevPrevPnt ; // index of the point before the previous point + sal_uInt16 nNextNextPnt =mpSdrPathDragData->nNextNextPnt ; // index of the point after the last point + bool bControl =mpSdrPathDragData->bControl ; // point is a control point + bool bIsNextControl =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point + bool bPrevIsControl =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before + bool bNextIsControl =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after + XPolygon aXPoly(mpSdrPathDragData->aXP); + XPolygon aLine1(2); + XPolygon aLine2(2); + XPolygon aLine3(2); + XPolygon aLine4(2); + if (bControl) { + aLine1[1]=mpSdrPathDragData->aXP[nPnt]; + if (bIsNextControl) { // is this a control point after the support point? + aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt]; + aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt]; + aLine2[1]=mpSdrPathDragData->aXP[nNextPnt]; + if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { + aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control); + aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal); + // leverage lines for the opposing curve segment + aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt]; + aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt]; + aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2]; + aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1]; + } else { + aXPoly.Remove(0,1); + } + } else { // else this is a control point before a support point + aLine1[0]=mpSdrPathDragData->aXP[nNextPnt]; + aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt]; + aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt]; + if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { + aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control); + aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal); + // leverage lines for the opposing curve segment + aLine3[0]=mpSdrPathDragData->aXP[nNextPnt]; + aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt]; + aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2]; + aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1]; + } else { + aXPoly.Remove(aXPoly.GetPointCount()-1,1); + } + } + } else { // else is not a control point + if (mpSdrPathDragData->bEliminate) { + aXPoly.Remove(2,1); + } + if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal); + else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) { + aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control); + aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal); + } else { + aXPoly.Remove(0,1); + if (bBegPnt) aXPoly.Remove(0,1); + } + if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal); + else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) { + aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control); + aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal); + } else { + aXPoly.Remove(aXPoly.GetPointCount()-1,1); + if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1); + } + if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged + if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) { + sal_uInt16 a=aXPoly.GetPointCount(); + aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2)); + aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3)); + aXPoly.Remove(0,3); + } + } + } + aRetval.Insert(std::move(aXPoly)); + if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1)); + if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2)); + if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3)); + if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4)); + } + + return aRetval.getB2DPolyPolygon(); +} + +void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat) +{ + bool bFreeHand(IsFreeHand(meObjectKind)); + rStat.SetNoSnap(bFreeHand); + rStat.SetOrtho8Possible(); + aPathPolygon.Clear(); + mbCreating=true; + bool bMakeStartPoint = true; + SdrView* pView=rStat.GetView(); + if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() && + (meObjectKind==SdrObjKind::Polygon || meObjectKind==SdrObjKind::PolyLine || meObjectKind==SdrObjKind::PathLine || meObjectKind==SdrObjKind::PathFill)) { + bMakeStartPoint = false; + } + aPathPolygon.Insert(XPolygon()); + aPathPolygon[0][0]=rStat.GetStart(); + if (bMakeStartPoint) { + aPathPolygon[0][1]=rStat.GetNow(); + } + std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser); + pU->eStartKind=meObjectKind; + pU->eCurrentKind=meObjectKind; + rStat.SetUser(std::move(pU)); +} + +bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat) +{ + ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser()); + SdrView* pView=rStat.GetView(); + XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; + if (pView!=nullptr && pView->IsCreateMode()) { + // switch to different CreateTool, if appropriate + SdrObjKind nIdent; + SdrInventor nInvent; + pView->TakeCurrentObj(nIdent,nInvent); + if (nInvent==SdrInventor::Default && pU->eCurrentKind != nIdent) { + SdrObjKind eNewKind = nIdent; + switch (eNewKind) { + case SdrObjKind::CircleArc: + case SdrObjKind::CircleOrEllipse: + case SdrObjKind::CircleCut: + case SdrObjKind::CircleSection: + eNewKind=SdrObjKind::CircleArc; + [[fallthrough]]; + case SdrObjKind::Rectangle: + case SdrObjKind::Line: + case SdrObjKind::PolyLine: + case SdrObjKind::Polygon: + case SdrObjKind::PathLine: + case SdrObjKind::PathFill: + case SdrObjKind::FreehandLine: + case SdrObjKind::FreehandFill: + { + pU->eCurrentKind=eNewKind; + pU->bMixedCreate=true; + pU->nBezierStartPoint=rXPoly.GetPointCount(); + if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--; + } break; + default: break; + } // switch + } + } + sal_uInt16 nCurrentPoint=rXPoly.GetPointCount(); + if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) { + rXPoly[0]=rStat.GetPos0(); + rXPoly[1]=rStat.GetNow(); + nCurrentPoint=2; + } + if (nCurrentPoint==0) { + rXPoly[0]=rStat.GetPos0(); + } else nCurrentPoint--; + bool bFreeHand=IsFreeHand(pU->eCurrentKind); + rStat.SetNoSnap(bFreeHand); + rStat.SetOrtho8Possible(pU->eCurrentKind!=SdrObjKind::CircleArc && pU->eCurrentKind!=SdrObjKind::Rectangle && (!pU->bMixedCreate || pU->eCurrentKind!=SdrObjKind::Line)); + rXPoly[nCurrentPoint]=rStat.GetNow(); + if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line && rXPoly.GetPointCount()>=1) { + Point aPt(rStat.GetStart()); + if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) { + aPt+=aPt; + aPt-=rStat.GetNow(); + } + rXPoly[0]=aPt; + } + OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice(); + if (bFreeHand) { + if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint; + if (rStat.IsMouseDown() && nCurrentPoint>0) { + // don't allow two consecutive points to occupy too similar positions + tools::Long nMinDist=1; + if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix(); + if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width(); + if (nMinDist<1) nMinDist=1; + + Point aPt0(rXPoly[nCurrentPoint-1]); + Point aPt1(rStat.GetNow()); + tools::Long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx; + tools::Long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy; + if (dx<nMinDist && dy<nMinDist) return false; + + // TODO: the following is copied from EndCreate (with a few smaller modifications) + // and should be combined into a method with the code there. + + if (nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) { + rXPoly.PointsToBezier(nCurrentPoint-3); + rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control); + rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control); + + if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) { + rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2); + rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth); + } + } + rXPoly[nCurrentPoint+1]=rStat.GetNow(); + rStat.NextPoint(); + } else { + pU->nBezierStartPoint=nCurrentPoint; + } + } + + pU->ResetFormFlags(); + if (IsBezier(pU->eCurrentKind)) { + if (nCurrentPoint>=2) { + pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown()); + } else if (pU->bBezHasCtrl0) { + pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown()); + } + } + if (pU->eCurrentKind==SdrObjKind::CircleArc && nCurrentPoint>=2) { + pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); + } + if (pU->eCurrentKind==SdrObjKind::Line && nCurrentPoint>=2) { + pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); + } + if (pU->eCurrentKind==SdrObjKind::Rectangle && nCurrentPoint>=2) { + pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView); + } + + return true; +} + +bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser()); + bool bRet = false; + SdrView* pView=rStat.GetView(); + bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface(); + XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; + sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1; + rXPoly[nCurrentPoint]=rStat.GetNow(); + if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line) { + if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd; + bRet = eCmd==SdrCreateCmd::ForceEnd; + if (bRet) { + mbCreating = false; + rStat.SetUser(nullptr); + } + return bRet; + } + + if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) { + if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd; + bRet=eCmd==SdrCreateCmd::ForceEnd; + if (bRet) { + mbCreating=false; + rStat.SetUser(nullptr); + } + return bRet; + } + if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) { + // don't allow two consecutive points to occupy the same position + if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) { + if (bIncomp) { + if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint; + if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) { + rXPoly.PointsToBezier(nCurrentPoint-3); + rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control); + rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control); + + if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) { + rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2); + rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth); + } + } + } else { + if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) { + pU->aBezControl0=rStat.GetNow(); + pU->bBezHasCtrl0=true; + nCurrentPoint--; + } + if (pU->IsFormFlag()) { + sal_uInt16 nPointCount0=rXPoly.GetPointCount(); + rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form + rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly()); + sal_uInt16 nPointCount1=rXPoly.GetPointCount(); + for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work + if (!rXPoly.IsControl(i)) rStat.NextPoint(); + } + nCurrentPoint=rXPoly.GetPointCount()-1; + } + } + nCurrentPoint++; + rXPoly[nCurrentPoint]=rStat.GetNow(); + } + if (eCmd==SdrCreateCmd::NextObject) { + if (rXPoly.GetPointCount()>=2) { + pU->bBezHasCtrl0=false; + // only a singular polygon may be opened, so close this + rXPoly[nCurrentPoint]=rXPoly[0]; + XPolygon aXP; + aXP[0]=rStat.GetNow(); + aPathPolygon.Insert(std::move(aXP)); + } + } + } + + sal_uInt16 nPolyCount=aPathPolygon.Count(); + if (nPolyCount!=0) { + // delete last point, if necessary + if (eCmd==SdrCreateCmd::ForceEnd) { + XPolygon& rXP=aPathPolygon[nPolyCount-1]; + sal_uInt16 nPointCount=rXP.GetPointCount(); + if (nPointCount>=2) { + if (!rXP.IsControl(nPointCount-2)) { + if (rXP[nPointCount-1]==rXP[nPointCount-2]) { + rXP.Remove(nPointCount-1,1); + } + } else { + if (rXP[nPointCount-3]==rXP[nPointCount-2]) { + rXP.Remove(nPointCount-3,3); + } + } + } + } + for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) { + nPolyNum--; + XPolygon& rXP=aPathPolygon[nPolyNum]; + sal_uInt16 nPointCount=rXP.GetPointCount(); + // delete polygons with too few points + if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) { + if (nPointCount<2) aPathPolygon.Remove(nPolyNum); + } + } + } + pU->ResetFormFlags(); + bRet=eCmd==SdrCreateCmd::ForceEnd; + if (bRet) { + mbCreating=false; + rStat.SetUser(nullptr); + } + return bRet; +} + +bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat) +{ + ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser()); + if (aPathPolygon.Count()>0) { + XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1]; + sal_uInt16 nCurrentPoint=rXPoly.GetPointCount(); + if (nCurrentPoint>0) { + nCurrentPoint--; + // make the last part of a bezier curve a line + rXPoly.Remove(nCurrentPoint,1); + if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) { + // there should never be a bezier segment at the end, so this is just in case... + rXPoly.Remove(nCurrentPoint-1,1); + if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1); + } + } + nCurrentPoint=rXPoly.GetPointCount(); + if (nCurrentPoint>=4) { // no bezier segment at the end + nCurrentPoint--; + if (rXPoly.IsControl(nCurrentPoint-1)) { + rXPoly.Remove(nCurrentPoint-1,1); + if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1); + } + } + if (rXPoly.GetPointCount()<2) { + aPathPolygon.Remove(aPathPolygon.Count()-1); + } + if (aPathPolygon.Count()>0) { + XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1]; + sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount(); + if (nLocalCurrentPoint>0) { + nLocalCurrentPoint--; + rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow(); + } + } + } + pU->ResetFormFlags(); + return aPathPolygon.Count()!=0; +} + +void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat) +{ + aPathPolygon.Clear(); + mbCreating=false; + rStat.SetUser(nullptr); +} + +basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon()); + SdrView* pView = rDrag.GetView(); + + if(pView && pView->IsUseIncompatiblePathCreateInterface()) + return aRetval; + + ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser()); + basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon()); + + if(pU->IsFormFlag() && aNewPolygon.count() > 1) + { + // remove last segment and replace with current + // do not forget to rescue the previous control point which will be lost when + // the point it's associated with is removed + const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2); + const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex)); + + aNewPolygon.remove(nChangeIndex, 2); + aNewPolygon.append(pU->GetFormPoly().getB2DPolygon()); + + if(nChangeIndex < aNewPolygon.count()) + { + // if really something was added, set the saved previous control point to the + // point where it belongs + aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint); + } + } + + if(aRetval.count()) + { + aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon); + } + else + { + aRetval.append(aNewPolygon); + } + + return aRetval; +} + +basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag) +{ + basegfx::B2DPolyPolygon aRetval; + SdrView* pView = rDrag.GetView(); + + if(pView && pView->IsUseIncompatiblePathCreateInterface()) + return aRetval; + + const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser()); + + if(pU && pU->bBezier && rDrag.IsMouseDown()) + { + // no more XOR, no need for complicated helplines + basegfx::B2DPolygon aHelpline; + aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y())); + aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y())); + aRetval.append(aHelpline); + } + + return aRetval; +} + +PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const +{ + switch (meObjectKind) { + case SdrObjKind::Line : return PointerStyle::DrawLine; + case SdrObjKind::Polygon : return PointerStyle::DrawPolygon; + case SdrObjKind::PolyLine : return PointerStyle::DrawPolygon; + case SdrObjKind::PathLine: return PointerStyle::DrawBezier; + case SdrObjKind::PathFill: return PointerStyle::DrawBezier; + case SdrObjKind::FreehandLine: return PointerStyle::DrawFreehand; + case SdrObjKind::FreehandFill: return PointerStyle::DrawFreehand; + case SdrObjKind::PathPoly: return PointerStyle::DrawPolygon; + case SdrObjKind::PathPolyLine: return PointerStyle::DrawPolygon; + default: break; + } // switch + return PointerStyle::Cross; +} + +SdrPathObjGeoData::SdrPathObjGeoData() + : meKind(SdrObjKind::NONE) +{ +} + +SdrPathObjGeoData::~SdrPathObjGeoData() +{ +} + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrPathObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrPathObj>(*this); +} + + +SdrPathObj::SdrPathObj( + SdrModel& rSdrModel, + SdrObjKind eNewKind) +: SdrTextObj(rSdrModel), + meKind(eNewKind) +{ + m_bClosedObj = IsClosed(); +} + +SdrPathObj::SdrPathObj(SdrModel& rSdrModel, SdrPathObj const & rSource) +: SdrTextObj(rSdrModel, rSource), + meKind(rSource.meKind) +{ + m_bClosedObj = IsClosed(); + maPathPolygon = rSource.GetPathPoly(); +} + +SdrPathObj::SdrPathObj( + SdrModel& rSdrModel, + SdrObjKind eNewKind, + basegfx::B2DPolyPolygon aPathPoly) +: SdrTextObj(rSdrModel), + maPathPolygon(std::move(aPathPoly)), + meKind(eNewKind) +{ + m_bClosedObj = IsClosed(); + ImpForceKind(); +} + +SdrPathObj::~SdrPathObj() = default; + +static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count()); +} + +static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon)); + + if (aRange.isEmpty()) + return tools::Rectangle(); + + return tools::Rectangle( + FRound(aRange.getMinX()), FRound(aRange.getMinY()), + FRound(aRange.getMaxX()), FRound(aRange.getMaxY())); +} + +void SdrPathObj::ImpForceLineAngle() +{ + if(SdrObjKind::Line != meKind || !lcl_ImpIsLine(GetPathPoly())) + return; + + const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0)); + const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0)); + const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1)); + const Point aPoint0(FRound(aB2DPoint0.getX()), FRound(aB2DPoint0.getY())); + const Point aPoint1(FRound(aB2DPoint1.getX()), FRound(aB2DPoint1.getY())); + const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0); + const Point aDelt(FRound(aB2DDelt.getX()), FRound(aB2DDelt.getY())); + + maGeo.m_nRotationAngle=GetAngle(aDelt); + maGeo.m_nShearAngle=0_deg100; + maGeo.RecalcSinCos(); + maGeo.RecalcTan(); + + // for SdrTextObj, keep aRect up to date + setRectangle(tools::Rectangle::Normalize(aPoint0, aPoint1)); +} + +void SdrPathObj::ImpForceKind() +{ + if (meKind==SdrObjKind::PathPolyLine) meKind=SdrObjKind::PolyLine; + if (meKind==SdrObjKind::PathPoly) meKind=SdrObjKind::Polygon; + + if(GetPathPoly().areControlPointsUsed()) + { + switch (meKind) + { + case SdrObjKind::Line: meKind=SdrObjKind::PathLine; break; + case SdrObjKind::PolyLine: meKind=SdrObjKind::PathLine; break; + case SdrObjKind::Polygon: meKind=SdrObjKind::PathFill; break; + default: break; + } + } + else + { + switch (meKind) + { + case SdrObjKind::PathLine: meKind=SdrObjKind::PolyLine; break; + case SdrObjKind::FreehandLine: meKind=SdrObjKind::PolyLine; break; + case SdrObjKind::PathFill: meKind=SdrObjKind::Polygon; break; + case SdrObjKind::FreehandFill: meKind=SdrObjKind::Polygon; break; + default: break; + } + } + + if (meKind==SdrObjKind::Line && !lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::PolyLine; + if (meKind==SdrObjKind::PolyLine && lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::Line; + + m_bClosedObj=IsClosed(); + + if (meKind==SdrObjKind::Line) + { + ImpForceLineAngle(); + } + else + { + // #i10659#, for polys with more than 2 points. + + // Here i again need to fix something, because when Path-Polys are Copy-Pasted + // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is + // a scaling loop started from SdrExchangeView::Paste. In itself, this is not + // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If + // this is the case, some size needs to be set here in aRect to avoid that the cycle + // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is + // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called + // from the local Resize() implementation. + + // Basic problem is that the member aRect in SdrTextObj basically is a unrotated + // text rectangle for the text object itself and methods at SdrTextObj do handle it + // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect + // which is basically wrong. To make the SdrText methods which deal with aRect directly + // work it is necessary to always keep aRect updated. This e.g. not done after a Clone() + // command for SdrPathObj. Since adding this update mechanism with #101412# to + // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle() + // was called, once here below and once on a 2nd place below. + + // #i10659# for SdrTextObj, keep aRect up to date + if(GetPathPoly().count()) + { + setRectangle(lcl_ImpGetBoundRect(GetPathPoly())); + } + } + + // #i75974# adapt polygon state to object type. This may include a reinterpretation + // of a closed geometry as open one, but with identical first and last point + for(auto& rPolygon : maPathPolygon) + { + if(IsClosed() != rPolygon.isClosed()) + { + // #i80213# really change polygon geometry; else e.g. the last point which + // needs to be identical with the first one will be missing when opening + // due to OBJ_PATH type + if(rPolygon.isClosed()) + { + basegfx::utils::openWithGeometryChange(rPolygon); + } + else + { + basegfx::utils::closeWithGeometryChange(rPolygon); + } + } + } +} + +void SdrPathObj::ImpSetClosed(bool bClose) +{ + if(bClose) + { + switch (meKind) + { + case SdrObjKind::Line : meKind=SdrObjKind::Polygon; break; + case SdrObjKind::PolyLine : meKind=SdrObjKind::Polygon; break; + case SdrObjKind::PathLine: meKind=SdrObjKind::PathFill; break; + case SdrObjKind::FreehandLine: meKind=SdrObjKind::FreehandFill; break; + default: break; + } + + m_bClosedObj = true; + } + else + { + switch (meKind) + { + case SdrObjKind::Polygon : meKind=SdrObjKind::PolyLine; break; + case SdrObjKind::PathFill: meKind=SdrObjKind::PathLine; break; + case SdrObjKind::FreehandFill: meKind=SdrObjKind::FreehandLine; break; + default: break; + } + + m_bClosedObj = false; + } + + ImpForceKind(); +} + +void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bNoContortion=false; + + bool bCanConv = !HasText() || ImpCanConvTextToCurve(); + bool bIsPath = IsBezier(); + + rInfo.bEdgeRadiusAllowed = false; + rInfo.bCanConvToPath = bCanConv && !bIsPath; + rInfo.bCanConvToPoly = bCanConv && bIsPath; + rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrPathObj::GetObjIdentifier() const +{ + return meKind; +} + +rtl::Reference<SdrObject> SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrPathObj(rTargetModel, *this); +} + +OUString SdrPathObj::TakeObjNameSingul() const +{ + OUString sName; + + if(SdrObjKind::Line == meKind) + { + TranslateId pId(STR_ObjNameSingulLINE); + + if(lcl_ImpIsLine(GetPathPoly())) + { + const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0)); + const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0)); + const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1)); + + if(aB2DPoint0 != aB2DPoint1) + { + if(aB2DPoint0.getY() == aB2DPoint1.getY()) + { + pId = STR_ObjNameSingulLINE_Hori; + } + else if(aB2DPoint0.getX() == aB2DPoint1.getX()) + { + pId = STR_ObjNameSingulLINE_Vert; + } + else + { + const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX())); + const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY())); + + if(fDx == fDy) + { + pId = STR_ObjNameSingulLINE_Diag; + } + } + } + } + + sName = SvxResId(pId); + } + else if(SdrObjKind::PolyLine == meKind || SdrObjKind::Polygon == meKind) + { + const bool bClosed(SdrObjKind::Polygon == meKind); + TranslateId pId; + + if(mpDAC && mpDAC->IsCreating()) + { + if(bClosed) + { + pId = STR_ObjNameSingulPOLY; + } + else + { + pId = STR_ObjNameSingulPLIN; + } + + sName = SvxResId(pId); + } + else + { + // get point count + sal_uInt32 nPointCount(0); + + for(auto const& rPolygon : GetPathPoly()) + { + nPointCount += rPolygon.count(); + } + + if(bClosed) + { + pId = STR_ObjNameSingulPOLY_PointCount; + } + else + { + pId = STR_ObjNameSingulPLIN_PointCount; + } + + // #i96537# + sName = SvxResId(pId).replaceFirst("%2", OUString::number(nPointCount)); + } + } + else + { + switch (meKind) + { + case SdrObjKind::PathLine: sName = SvxResId(STR_ObjNameSingulPATHLINE); break; + case SdrObjKind::FreehandLine: sName = SvxResId(STR_ObjNameSingulFREELINE); break; + case SdrObjKind::PathFill: sName = SvxResId(STR_ObjNameSingulPATHFILL); break; + case SdrObjKind::FreehandFill: sName = SvxResId(STR_ObjNameSingulFREEFILL); break; + default: break; + } + } + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrPathObj::TakeObjNamePlural() const +{ + OUString sName; + switch(meKind) + { + case SdrObjKind::Line : sName=SvxResId(STR_ObjNamePluralLINE ); break; + case SdrObjKind::PolyLine : sName=SvxResId(STR_ObjNamePluralPLIN ); break; + case SdrObjKind::Polygon : sName=SvxResId(STR_ObjNamePluralPOLY ); break; + case SdrObjKind::PathLine: sName=SvxResId(STR_ObjNamePluralPATHLINE); break; + case SdrObjKind::FreehandLine: sName=SvxResId(STR_ObjNamePluralFREELINE); break; + case SdrObjKind::PathFill: sName=SvxResId(STR_ObjNamePluralPATHFILL); break; + case SdrObjKind::FreehandFill: sName=SvxResId(STR_ObjNamePluralFREEFILL); break; + default: break; + } + return sName; +} + +basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const +{ + return GetPathPoly(); +} + +sal_uInt32 SdrPathObj::GetHdlCount() const +{ + sal_uInt32 nRetval(0); + + for(auto const& rPolygon : GetPathPoly()) + { + nRetval += rPolygon.count(); + } + + return nRetval; +} + +void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + // keep old stuff to be able to keep old SdrHdl stuff, too + const XPolyPolygon aOldPathPolygon(GetPathPoly()); + sal_uInt16 nPolyCnt=aOldPathPolygon.Count(); + bool bClosed=IsClosed(); + sal_uInt16 nIdx=0; + + for (sal_uInt16 i=0; i<nPolyCnt; i++) { + const XPolygon& rXPoly=aOldPathPolygon.GetObject(i); + sal_uInt16 nPntCnt=rXPoly.GetPointCount(); + if (bClosed && nPntCnt>1) nPntCnt--; + + for (sal_uInt16 j=0; j<nPntCnt; j++) { + if (rXPoly.GetFlags(j)!=PolyFlags::Control) { + const Point& rPnt=rXPoly[j]; + std::unique_ptr<SdrHdl> pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly)); + pHdl->SetPolyNum(i); + pHdl->SetPointNum(j); + pHdl->Set1PixMore(j==0); + pHdl->SetSourceHdlNum(nIdx); + nIdx++; + rHdlList.AddHdl(std::move(pHdl)); + } + } + } +} + +void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const +{ + // keep old stuff to be able to keep old SdrHdl stuff, too + const XPolyPolygon aOldPathPolygon(GetPathPoly()); + sal_uInt16 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum()); + sal_uInt16 nPolyNum = static_cast<sal_uInt16>(rHdl.GetPolyNum()); + + if (nPolyNum>=aOldPathPolygon.Count()) + return; + + const XPolygon& rXPoly = aOldPathPolygon[nPolyNum]; + sal_uInt16 nPntMax = rXPoly.GetPointCount(); + + if (nPntMax<=0) + return; + nPntMax--; + if (nPnt>nPntMax) + return; + + // calculate the number of plus points + sal_uInt16 nCnt = 0; + if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control) + { + if (nPnt==0 && IsClosed()) + nPnt=nPntMax; + if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control) + nCnt++; + if (nPnt==nPntMax && IsClosed()) + nPnt=0; + if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control) + nCnt++; + } + + // construct the plus points + for (sal_uInt32 nPlusNum = 0; nPlusNum < nCnt; ++nPlusNum) + { + nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum()); + std::unique_ptr<SdrHdl> pHdl(new SdrHdlBezWgt(&rHdl)); + pHdl->SetPolyNum(rHdl.GetPolyNum()); + + if (nPnt==0 && IsClosed()) + nPnt=nPntMax; + if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0) + { + pHdl->SetPos(rXPoly[nPnt-1]); + pHdl->SetPointNum(nPnt-1); + } + else + { + if (nPnt==nPntMax && IsClosed()) + nPnt=0; + if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control) + { + pHdl->SetPos(rXPoly[nPnt+1]); + pHdl->SetPointNum(nPnt+1); + } + } + + pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum()); + pHdl->SetPlusHdl(true); + rHdlList.AddHdl(std::move(pHdl)); + } +} + +// tdf#123321: Make sure that SdrPathObj (e.g. line) has big enough extent for +// visibility. This is realised by ensuring GetLogicRect() is the same as +// GetSnapRect() for the SdrPathObj. Other SdrTextObj objects like +// SdrObjCustomShape will still use a different version of this method that +// does not consider the rotation. Otherwise, the rotated SdrObjCustomShape +// would become mistakenly larger after save and reload (tdf#91687). +// The invocation of the GetLogicRect() method that caused tdf#123321 was in +// PlcDrawObj::WritePlc(). +const tools::Rectangle &SdrPathObj::GetLogicRect() const +{ + return GetSnapRect(); +} + +// dragging + +bool SdrPathObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this)); + + return aDragAndCreate.beginPathDrag(rDrag); +} + +bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag) +{ + ImpPathForDragAndCreate aDragAndCreate(*this); + bool bRetval(aDragAndCreate.beginPathDrag(rDrag)); + + if(bRetval) + { + bRetval = aDragAndCreate.movePathDrag(rDrag); + } + + if(bRetval) + { + bRetval = aDragAndCreate.endPathDrag(rDrag); + } + + if(bRetval) + { + NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon()); + } + + return bRetval; +} + +OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + OUString aRetval; + + if(mpDAC) + { + // #i103058# also get a comment when in creation + const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment) + { + aRetval = mpDAC->getSpecialDragComment(rDrag); + } + } + else + { + ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this)); + bool bDidWork(aDragAndCreate.beginPathDrag(rDrag)); + + if(bDidWork) + { + aRetval = aDragAndCreate.getSpecialDragComment(rDrag); + } + } + + return aRetval; +} + +basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval; + ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this)); + bool bDidWork(aDragAndCreate.beginPathDrag(rDrag)); + + if(bDidWork) + { + aRetval = aDragAndCreate.getSpecialDragPoly(rDrag); + } + + return aRetval; +} + +// creation + +bool SdrPathObj::BegCreate(SdrDragStat& rStat) +{ + mpDAC.reset(); + impGetDAC().BegCreate(rStat); + return true; +} + +bool SdrPathObj::MovCreate(SdrDragStat& rStat) +{ + return impGetDAC().MovCreate(rStat); +} + +bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + bool bRetval(impGetDAC().EndCreate(rStat, eCmd)); + + if(bRetval && mpDAC) + { + SetPathPoly(mpDAC->getModifiedPolyPolygon()); + + // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate + // to be able to use the type-changing ImpSetClosed method + if(!IsClosedObj()) + { + SdrView* pView = rStat.GetView(); + + if(pView && !pView->IsUseIncompatiblePathCreateInterface()) + { + OutputDevice* pOut = pView->GetFirstOutputDevice(); + + if(pOut) + { + if(GetPathPoly().count()) + { + const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0)); + + if(aCandidate.count() > 2) + { + // check distance of first and last point + const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width()); + const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0)); + + if(aDistVector.getLength() <= static_cast<double>(nCloseDist)) + { + // close it + ImpSetClosed(true); + } + } + } + } + } + } + + mpDAC.reset(); + } + + return bRetval; +} + +bool SdrPathObj::BckCreate(SdrDragStat& rStat) +{ + return impGetDAC().BckCreate(rStat); +} + +void SdrPathObj::BrkCreate(SdrDragStat& rStat) +{ + impGetDAC().BrkCreate(rStat); + mpDAC.reset(); +} + +// polygons + +basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval; + + if(mpDAC) + { + aRetval = mpDAC->TakeObjectPolyPolygon(rDrag); + aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag)); + } + + return aRetval; +} + +// during drag or create, allow accessing the so-far created/modified polyPolygon +basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval; + + if(mpDAC) + { + aRetval = mpDAC->TakeObjectPolyPolygon(rDrag); + } + + return aRetval; +} + +basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval; + + if(mpDAC) + { + aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag); + } + + return aRetval; +} + +PointerStyle SdrPathObj::GetCreatePointer() const +{ + return impGetDAC().GetCreatePointer(); +} + +void SdrPathObj::NbcMove(const Size& rSiz) +{ + maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height())); + + // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) + SdrTextObj::NbcMove(rSiz); +} + +void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + const double fResizeX(xFact); + const double fResizeY(yFact); + + if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0)) + { + // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not + // manipulate at all - that may change maGeo rapidly (and wrongly) in + // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply' + // a rotation but to manipulate the existing one, this is fatal. So just + // avoid this error as long as we have to deal with imprecise geometry + // manipulations + return; + } + + basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y())); + aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix( + double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans; + maPathPolygon.transform(aTrans); + + // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) + SdrTextObj::NbcResize(rRef,xFact,yFact); +} + +void SdrPathObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + // Thank JOE, the angles are defined mirrored to the mathematical meanings + const basegfx::B2DHomMatrix aTrans( + basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -toRadians(nAngle))); + maPathPolygon.transform(aTrans); + + // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) + SdrTextObj::NbcRotate(rRef,nAngle,sn,cs); +} + +void SdrPathObj::NbcShear(const Point& rRefPnt, Degree100 nAngle, double fTan, bool bVShear) +{ + basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y())); + + if(bVShear) + { + // Thank JOE, the angles are defined mirrored to the mathematical meanings + aTrans.shearY(-fTan); + } + else + { + aTrans.shearX(-fTan); + } + + aTrans.translate(rRefPnt.X(), rRefPnt.Y()); + maPathPolygon.transform(aTrans); + + // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) + SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear); +} + +void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2) +{ + const double fDiffX(rRefPnt2.X() - rRefPnt1.X()); + const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y()); + const double fRot(atan2(fDiffY, fDiffX)); + basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y())); + aTrans.rotate(-fRot); + aTrans.scale(1.0, -1.0); + aTrans.rotate(fRot); + aTrans.translate(rRefPnt1.X(), rRefPnt1.Y()); + maPathPolygon.transform(aTrans); + + // Do Joe's special handling for lines when mirroring, too + ImpForceKind(); + + // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints) + SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2); +} + +void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + if(!maGeo.m_nRotationAngle) + { + rRect = GetSnapRect(); + } + else + { + XPolyPolygon aXPP(GetPathPoly()); + RotateXPoly(aXPP,Point(),-maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + rRect=aXPP.GetBoundRect(); + Point aTmp(rRect.TopLeft()); + RotatePoint(aTmp,Point(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + aTmp-=rRect.TopLeft(); + rRect.Move(aTmp.X(),aTmp.Y()); + } +} + +void SdrPathObj::RecalcSnapRect() +{ + if(GetPathPoly().count()) + { + maSnapRect = lcl_ImpGetBoundRect(GetPathPoly()); + } +} + +void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aOld(GetSnapRect()); + if (aOld.IsEmpty()) + { + Fraction aX(1,1); + Fraction aY(1,1); + NbcResize(aOld.TopLeft(), aX, aY); + NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top())); + return; + } + + // Take empty into account when calculating scale factors + tools::Long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right() - rRect.Left(); + + tools::Long nDivX = aOld.Right() - aOld.Left(); + + // Take empty into account when calculating scale factors + tools::Long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top(); + + tools::Long nDivY = aOld.Bottom() - aOld.Top(); + if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; } + if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; } + if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; } + if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; } + Fraction aX(nMulX,nDivX); + Fraction aY(nMulY,nDivY); + NbcResize(aOld.TopLeft(), aX, aY); + NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top())); +} + +sal_uInt32 SdrPathObj::GetSnapPointCount() const +{ + return GetHdlCount(); +} + +Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const +{ + sal_uInt32 nPoly,nPnt; + if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt)) + { + SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist."); + } + + const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt)); + return Point(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY())); +} + +bool SdrPathObj::IsPolyObj() const +{ + return true; +} + +sal_uInt32 SdrPathObj::GetPointCount() const +{ + sal_uInt32 nRetval(0); + + for(auto const& rPolygon : GetPathPoly()) + { + nRetval += rPolygon.count(); + } + + return nRetval; +} + +Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const +{ + Point aRetval; + sal_uInt32 nPoly,nPnt; + + if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt)) + { + const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly)); + const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt)); + aRetval = Point(FRound(aPoint.getX()), FRound(aPoint.getY())); + } + + return aRetval; +} + +void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum) +{ + sal_uInt32 nPoly,nPnt; + + if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt)) + return; + + basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly)); + aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y())); + maPathPolygon.setB2DPolygon(nPoly, aNewPolygon); + + if(meKind==SdrObjKind::Line) + { + ImpForceLineAngle(); + } + else + { + if(GetPathPoly().count()) + { + // #i10659# for SdrTextObj, keep aRect up to date + setRectangle(lcl_ImpGetBoundRect(GetPathPoly())); + } + } + + SetBoundAndSnapRectsDirty(); +} + +sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj) +{ + sal_uInt32 nNewHdl; + + if(bNewObj) + { + nNewHdl = NbcInsPoint(rPos, true); + } + else + { + // look for smallest distance data + const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y()); + sal_uInt32 nSmallestPolyIndex(0); + sal_uInt32 nSmallestEdgeIndex(0); + double fSmallestCut; + basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut); + + nNewHdl = NbcInsPoint(rPos, false); + } + + ImpForceKind(); + return nNewHdl; +} + +sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj) +{ + sal_uInt32 nNewHdl; + + if(bNewObj) + { + basegfx::B2DPolygon aNewPoly; + const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y()); + aNewPoly.append(aPoint); + aNewPoly.setClosed(IsClosed()); + maPathPolygon.append(aNewPoly); + SetBoundAndSnapRectsDirty(); + nNewHdl = GetHdlCount(); + } + else + { + // look for smallest distance data + const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y()); + sal_uInt32 nSmallestPolyIndex(0); + sal_uInt32 nSmallestEdgeIndex(0); + double fSmallestCut; + basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut); + basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex)); + const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut); + const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut); + + if(bBefore) + { + // before first point + aCandidate.insert(0, aTestPoint); + + if(aCandidate.areControlPointsUsed()) + { + if(aCandidate.isNextControlPointUsed(1)) + { + aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0))); + aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0))); + } + } + + nNewHdl = 0; + } + else if(bAfter) + { + // after last point + aCandidate.append(aTestPoint); + + if(aCandidate.areControlPointsUsed()) + { + if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2)) + { + aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0))); + aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0))); + } + } + + nNewHdl = aCandidate.count() - 1; + } + else + { + // in between + bool bSegmentSplit(false); + const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count()); + + if(aCandidate.areControlPointsUsed()) + { + if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex)) + { + bSegmentSplit = true; + } + } + + if(bSegmentSplit) + { + // rebuild original segment to get the split data + basegfx::B2DCubicBezier aBezierA, aBezierB; + const basegfx::B2DCubicBezier aBezier( + aCandidate.getB2DPoint(nSmallestEdgeIndex), + aCandidate.getNextControlPoint(nSmallestEdgeIndex), + aCandidate.getPrevControlPoint(nNextIndex), + aCandidate.getB2DPoint(nNextIndex)); + + // split and insert hit point + aBezier.split(fSmallestCut, &aBezierA, &aBezierB); + aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint); + + // since we inserted hit point and not split point, we need to add an offset + // to the control points to get the C1 continuity we want to achieve + const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint()); + aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset); + aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset); + aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset); + aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset); + } + else + { + aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint); + } + + nNewHdl = nSmallestEdgeIndex + 1; + } + + maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate); + + // create old polygon index from it + for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++) + { + nNewHdl += GetPathPoly().getB2DPolygon(a).count(); + } + } + + ImpForceKind(); + return nNewHdl; +} + +rtl::Reference<SdrPathObj> SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index) +{ + rtl::Reference<SdrPathObj> pNewObj; + const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly()); + sal_uInt32 nPoly, nPnt; + + if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt)) + { + if(0 == nPoly) + { + const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly)); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount) + { + if(IsClosed()) + { + // when closed, RipPoint means to open the polygon at the selected point. To + // be able to do that, it is necessary to make the selected point the first one + basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt)); + SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon)); + ToggleClosed(); + + // give back new position of old start point (historical reasons) + rNewPt0Index = (nPointCount - nPnt) % nPointCount; + } + else + { + if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount) + { + // split in two objects at point nPnt + basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1); + SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA)); + + pNewObj = SdrObject::Clone(*this, getSdrModelFromSdrObject()); + basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt); + pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB)); + } + } + } + } + } + + return pNewObj; +} + +rtl::Reference<SdrObject> SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + // #i89784# check for FontWork with activated HideContour + const drawinglayer::attribute::SdrTextAttribute aText( + drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0))); + const bool bHideContour( + !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour()); + + rtl::Reference<SdrObject> pRet; + + if(!bHideContour) + { + rtl::Reference<SdrPathObj> pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier); + + if(pPath->GetPathPoly().areControlPointsUsed()) + { + if(!bBezier) + { + // reduce all bezier curves + pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly())); + } + } + else + { + if(bBezier) + { + // create bezier curves + pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly())); + } + } + pRet = std::move(pPath); + } + + if(bAddText) + { + pRet = ImpConvertAddText(std::move(pRet), bBezier); + } + + return pRet; +} + +std::unique_ptr<SdrObjGeoData> SdrPathObj::NewGeoData() const +{ + return std::make_unique<SdrPathObjGeoData>(); +} + +void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrTextObj::SaveGeoData(rGeo); + SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo ); + rPGeo.maPathPolygon=GetPathPoly(); + rPGeo.meKind=meKind; +} + +void SdrPathObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrTextObj::RestoreGeoData(rGeo); + const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo); + maPathPolygon=rPGeo.maPathPolygon; + meKind=rPGeo.meKind; + ImpForceKind(); // to set bClosed (among other things) +} + +void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly) +{ + if(GetPathPoly() != rPathPoly) + { + maPathPolygon=rPathPoly; + ImpForceKind(); + SetBoundAndSnapRectsDirty(); + } +} + +void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly) +{ + if(GetPathPoly() != rPathPoly) + { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetPathPoly(rPathPoly); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrPathObj::ToggleClosed() +{ + tools::Rectangle aBoundRect0; + if(m_pUserCall != nullptr) + aBoundRect0 = GetLastBoundRect(); + ImpSetClosed(!IsClosed()); // set new ObjKind + ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line + SetBoundAndSnapRectsDirty(); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize, aBoundRect0); +} + +ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const +{ + if(!mpDAC) + { + const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this))); + } + + return *mpDAC; +} + + +// transformation interface for StarOfficeAPI. This implements support for +// homogeneous 3x3 matrices containing the transformation of the SdrObject. At the +// moment it contains a shearX, rotation and translation, but for setting all linear +// transforms like Scale, ShearX, ShearY, Rotate and Translate are supported. + + +// gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon +// with the base geometry and returns TRUE. Otherwise it returns FALSE. +bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const +{ + double fRotate(0.0); + double fShearX(0.0); + basegfx::B2DTuple aScale(1.0, 1.0); + basegfx::B2DTuple aTranslate(0.0, 0.0); + + if(GetPathPoly().count()) + { + // copy geometry + basegfx::B2DHomMatrix aMoveToZeroMatrix; + rPolyPolygon = GetPathPoly(); + + if(SdrObjKind::Line == meKind) + { + // ignore shear and rotate, just use scale and translate + OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)"); + // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon + // itself, else this method will no longer return the full polygon information (curve will + // be lost) + const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); + aScale = aPolyRangeNoCurve.getRange(); + aTranslate = aPolyRangeNoCurve.getMinimum(); + + // define matrix for move polygon to zero point + aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY()); + } + else + { + if(maGeo.m_nShearAngle || maGeo.m_nRotationAngle) + { + // get rotate and shear in drawingLayer notation + fRotate = toRadians(maGeo.m_nRotationAngle); + fShearX = toRadians(maGeo.m_nShearAngle); + + // build mathematically correct (negative shear and rotate) object transform + // containing shear and rotate to extract unsheared, unrotated polygon + basegfx::B2DHomMatrix aObjectMatrix; + aObjectMatrix.shearX(-maGeo.mfTanShearAngle); + aObjectMatrix.rotate(toRadians(36000_deg100 - maGeo.m_nRotationAngle)); + + // create inverse from it and back-transform polygon + basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix); + aInvObjectMatrix.invert(); + rPolyPolygon.transform(aInvObjectMatrix); + + // get range from unsheared, unrotated polygon and extract scale and translate. + // transform topLeft from it back to transformed state to get original + // topLeft (rotation center) + // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon + // itself, else this method will no longer return the full polygon information (curve will + // be lost) + const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); + aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum(); + aScale = aCorrectedRangeNoCurve.getRange(); + + // define matrix for move polygon to zero point + // #i112280# Added missing minus for Y-Translation + aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY()); + } + else + { + // get scale and translate from unsheared, unrotated polygon + // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon + // itself, else this method will no longer return the full polygon information (curve will + // be lost) + const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon)); + aScale = aPolyRangeNoCurve.getRange(); + aTranslate = aPolyRangeNoCurve.getMinimum(); + + // define matrix for move polygon to zero point + aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY()); + } + } + + // move polygon to zero point with pre-defined matrix + rPolyPolygon.transform(aMoveToZeroMatrix); + } + + // position maybe relative to anchorpos, convert + if( getSdrModelFromSdrObject().IsWriter() ) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build return value matrix + rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX), + basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate, + aTranslate); + + return true; +} + +void SdrPathObj::SetHandleScale(bool bHandleScale) +{ + mbHandleScale = bHandleScale; +} + +// Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix. +// If it's an SdrPathObj it will use the provided geometry information. The Polygon has +// to use (0,0) as upper left and will be scaled to the given size in the matrix. +void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings + // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly + if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) + { + aScale.setX(fabs(aScale.getX())); + aScale.setY(fabs(aScale.getY())); + fRotate = fmod(fRotate + M_PI, 2 * M_PI); + } + + // copy poly + basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon); + + // reset object shear and rotations + maGeo.m_nRotationAngle = 0_deg100; + maGeo.RecalcSinCos(); + maGeo.m_nShearAngle = 0_deg100; + maGeo.RecalcTan(); + + if( getSdrModelFromSdrObject().IsWriter() ) + { + // if anchor is used, make position relative to it + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // create transformation for polygon, set values at maGeo direct + basegfx::B2DHomMatrix aTransform; + + // #i75086# + // Given polygon is already scaled (for historical reasons), but not mirrored yet. + // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly. + double fScaleX(basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0); + double fScaleY(basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0); + + // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale + // the polygon. But draw:transform might introduce additional scaling factors, which need to + // be applied to the polygon too, so aScale cannot be ignored while loading. + // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better + // idea? The behavior in other cases is the same as it was before this fix. + if (maSnapRect.IsEmpty() && GetPathPoly().count() && mbHandleScale) + { + // In case of a Writer document, the scaling factors were converted to twips. That is not + // correct here, because width and height are already in the points coordinates and aScale + // is no length but only a factor here. Convert back. + if (getSdrModelFromSdrObject().IsWriter()) + { + aScale.setX(o3tl::convert(aScale.getX(), o3tl::Length::twip, o3tl::Length::mm100)); + aScale.setY(o3tl::convert(aScale.getY(), o3tl::Length::twip, o3tl::Length::mm100)); + } + fScaleX *= fabs(aScale.getX()); + fScaleY *= fabs(aScale.getY()); + } + + if (fScaleX != 1.0 || fScaleY != 1.0) + aTransform.scale(fScaleX, fScaleY); + + if(!basegfx::fTools::equalZero(fShearX)) + { + aTransform.shearX(tan(-atan(fShearX))); + maGeo.m_nShearAngle = Degree100(FRound(basegfx::rad2deg<100>(atan(fShearX)))); + maGeo.RecalcTan(); + } + + if(!basegfx::fTools::equalZero(fRotate)) + { + // #i78696# + // fRotate is mathematically correct for linear transformations, so it's + // the one to use for the geometry change + aTransform.rotate(fRotate); + + // #i78696# + // fRotate is mathematically correct, but aGeoStat.nRotationAngle is + // mirrored -> mirror value here + maGeo.m_nRotationAngle = NormAngle36000(Degree100(FRound(-basegfx::rad2deg<100>(fRotate)))); + maGeo.RecalcSinCos(); + } + + if(!aTranslate.equalZero()) + { + // #i39529# absolute positioning, so get current position (without control points (!)) + const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon)); + aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY()); + } + + // transform polygon and trigger change + aNewPolyPolygon.transform(aTransform); + SetPathPoly(aNewPolyPolygon); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdorect.cxx b/svx/source/svdraw/svdorect.cxx new file mode 100644 index 0000000000..7f805a2d28 --- /dev/null +++ b/svx/source/svdraw/svdorect.cxx @@ -0,0 +1,573 @@ +/* -*- 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/svdorect.hxx> +#include <svx/xpoly.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdview.hxx> +#include <svx/svdopath.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <sdr/properties/rectangleproperties.hxx> +#include <sdr/contact/viewcontactofsdrrectobj.hxx> +#include <tools/debug.hxx> +#include <vcl/ptrstyle.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +// BaseProperties section + +std::unique_ptr<sdr::properties::BaseProperties> SdrRectObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::RectangleProperties>(*this); +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrRectObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrRectObj>(*this); +} + + +SdrRectObj::SdrRectObj(SdrModel& rSdrModel) +: SdrTextObj(rSdrModel) +{ + m_bClosedObj=true; +} + +SdrRectObj::SdrRectObj(SdrModel& rSdrModel, SdrRectObj const & rSource) +: SdrTextObj(rSdrModel, rSource) +{ + m_bClosedObj=true; + mpXPoly = rSource.mpXPoly; +} + +SdrRectObj::SdrRectObj( + SdrModel& rSdrModel, + const tools::Rectangle& rRect) +: SdrTextObj(rSdrModel, rRect) +{ + m_bClosedObj=true; +} + +SdrRectObj::SdrRectObj( + SdrModel& rSdrModel, + SdrObjKind eNewTextKind) +: SdrTextObj(rSdrModel, eNewTextKind) +{ + DBG_ASSERT(meTextKind == SdrObjKind::Text || + meTextKind == SdrObjKind::OutlineText || meTextKind == SdrObjKind::TitleText, + "SdrRectObj::SdrRectObj(SdrObjKind) can only be applied to text frames."); + m_bClosedObj=true; +} + +SdrRectObj::SdrRectObj( + SdrModel& rSdrModel, + SdrObjKind eNewTextKind, + const tools::Rectangle& rRect) +: SdrTextObj(rSdrModel, eNewTextKind, rRect) +{ + DBG_ASSERT(meTextKind == SdrObjKind::Text || + meTextKind == SdrObjKind::OutlineText || meTextKind == SdrObjKind::TitleText, + "SdrRectObj::SdrRectObj(SdrObjKind,...) can only be applied to text frames."); + m_bClosedObj=true; +} + +SdrRectObj::~SdrRectObj() +{ +} + +void SdrRectObj::SetXPolyDirty() +{ + mpXPoly.reset(); +} + +XPolygon SdrRectObj::ImpCalcXPoly(const tools::Rectangle& rRect1, tools::Long nRad1) const +{ + XPolygon aXPoly(rRect1,nRad1,nRad1); + const sal_uInt16 nPointCnt(aXPoly.GetPointCount()); + XPolygon aNewPoly(nPointCnt+1); + sal_uInt16 nShift=nPointCnt-2; + if (nRad1!=0) nShift=nPointCnt-5; + sal_uInt16 j=nShift; + for (sal_uInt16 i=1; i<nPointCnt; i++) { + aNewPoly[i]=aXPoly[j]; + aNewPoly.SetFlags(i,aXPoly.GetFlags(j)); + j++; + if (j>=nPointCnt) j=1; + } + aNewPoly[0]=rRect1.BottomCenter(); + aNewPoly[nPointCnt]=aNewPoly[0]; + aXPoly=aNewPoly; + + // these angles always relate to the top left corner of aRect + if (maGeo.m_nShearAngle) ShearXPoly(aXPoly, getRectangle().TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) RotateXPoly(aXPoly, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + return aXPoly; +} + +void SdrRectObj::RecalcXPoly() +{ + mpXPoly = ImpCalcXPoly(getRectangle(), GetEckenradius()); +} + +const XPolygon& SdrRectObj::GetXPoly() const +{ + if(!mpXPoly) + { + const_cast<SdrRectObj*>(this)->RecalcXPoly(); + } + + return *mpXPoly; +} + +void SdrRectObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + bool bNoTextFrame=!IsTextFrame(); + rInfo.bResizeFreeAllowed=bNoTextFrame || ((maGeo.m_nRotationAngle.get() % 9000) == 0); + rInfo.bResizePropAllowed=true; + rInfo.bRotateFreeAllowed=true; + rInfo.bRotate90Allowed =true; + rInfo.bMirrorFreeAllowed=bNoTextFrame; + rInfo.bMirror45Allowed =bNoTextFrame; + rInfo.bMirror90Allowed =bNoTextFrame; + + // allow transparency + rInfo.bTransparenceAllowed = true; + + rInfo.bShearAllowed =bNoTextFrame; + rInfo.bEdgeRadiusAllowed=true; + + bool bCanConv=!HasText() || ImpCanConvTextToCurve(); + if (bCanConv && !bNoTextFrame && !HasText()) { + bCanConv=HasFill() || HasLine(); + } + rInfo.bCanConvToPath =bCanConv; + rInfo.bCanConvToPoly =bCanConv; + rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrRectObj::GetObjIdentifier() const +{ + if (IsTextFrame()) + return meTextKind; + else return SdrObjKind::Rectangle; +} + +void SdrRectObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + rRect = getRectangle(); + if (maGeo.m_nShearAngle==0_deg100) + return; + + tools::Long nDst=FRound((getRectangle().Bottom()-getRectangle().Top()) * maGeo.mfTanShearAngle); + if (maGeo.m_nShearAngle>0_deg100) + { + Point aRef(rRect.TopLeft()); + rRect.AdjustLeft( -nDst ); + Point aTmpPt(rRect.TopLeft()); + RotatePoint(aTmpPt,aRef,maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + aTmpPt-=rRect.TopLeft(); + rRect.Move(aTmpPt.X(),aTmpPt.Y()); + } + else + { + rRect.AdjustRight( -nDst ); + } +} + +OUString SdrRectObj::TakeObjNameSingul() const +{ + if (IsTextFrame()) + { + return SdrTextObj::TakeObjNameSingul(); + } + + bool bRounded = GetEckenradius() != 0; // rounded down + TranslateId pResId = bRounded ? STR_ObjNameSingulRECTRND : STR_ObjNameSingulRECT; + if (maGeo.m_nShearAngle) + { + pResId = bRounded ? STR_ObjNameSingulPARALRND : STR_ObjNameSingulPARAL; // parallelogram or, maybe, rhombus + } + else if (getRectangle().GetWidth() == getRectangle().GetHeight()) + { + pResId = bRounded ? STR_ObjNameSingulQUADRND : STR_ObjNameSingulQUAD; // square + } + OUString sName(SvxResId(pResId)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrRectObj::TakeObjNamePlural() const +{ + if (IsTextFrame()) + { + return SdrTextObj::TakeObjNamePlural(); + } + + bool bRounded = GetEckenradius() != 0; // rounded down + TranslateId pResId = bRounded ? STR_ObjNamePluralRECTRND : STR_ObjNamePluralRECT; + if (maGeo.m_nShearAngle) + { + pResId = bRounded ? STR_ObjNamePluralPARALRND : STR_ObjNamePluralPARAL; // parallelogram or rhombus + } + else if (getRectangle().GetWidth() == getRectangle().GetHeight()) + { + pResId = bRounded ? STR_ObjNamePluralQUADRND : STR_ObjNamePluralQUAD; // square + } + + return SvxResId(pResId); +} + +rtl::Reference<SdrObject> SdrRectObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrRectObj(rTargetModel, *this); +} + +basegfx::B2DPolyPolygon SdrRectObj::TakeXorPoly() const +{ + XPolyPolygon aXPP; + aXPP.Insert(ImpCalcXPoly(getRectangle(), GetEckenradius())); + return aXPP.getB2DPolyPolygon(); +} + +void SdrRectObj::RecalcSnapRect() +{ + tools::Long nEckRad=GetEckenradius(); + if ((maGeo.m_nRotationAngle || maGeo.m_nShearAngle) && nEckRad!=0) { + maSnapRect=GetXPoly().GetBoundRect(); + } else { + SdrTextObj::RecalcSnapRect(); + } +} + +void SdrRectObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + SdrTextObj::NbcSetSnapRect(rRect); + SetXPolyDirty(); +} + +void SdrRectObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + SdrTextObj::NbcSetLogicRect(rRect); + SetXPolyDirty(); +} + +sal_uInt32 SdrRectObj::GetHdlCount() const +{ + return IsTextFrame() ? 10 : 9; +} + +void SdrRectObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + // A text box has an additional (pseudo-)handle for the blinking frame. + if(IsTextFrame()) + { + OSL_ENSURE(!IsTextEditActive(), "Do not use an ImpTextframeHdl for highlighting text in active text edit, this will collide with EditEngine paints (!)"); + std::unique_ptr<SdrHdl> pH(new ImpTextframeHdl(getRectangle())); + pH->SetObj(const_cast<SdrRectObj*>(this)); + pH->SetRotationAngle(maGeo.m_nRotationAngle); + rHdlList.AddHdl(std::move(pH)); + } + + for(sal_Int32 nHdlNum = 1; nHdlNum <= 9; ++nHdlNum) + { + Point aPnt; + SdrHdlKind eKind = SdrHdlKind::Move; + auto const& rRectangle = getRectangle(); + switch(nHdlNum) + { + case 1: // Handle for changing the corner radius + { + tools::Long a = GetEckenradius(); + tools::Long b = std::max(rRectangle.GetWidth(), rRectangle.GetHeight())/2; // rounded up, because GetWidth() adds 1 + if (a>b) a=b; + if (a<0) a=0; + aPnt = rRectangle.TopLeft(); + aPnt.AdjustX(a ); + eKind = SdrHdlKind::Circle; + break; + } + case 2: aPnt = rRectangle.TopLeft(); eKind = SdrHdlKind::UpperLeft; break; + case 3: aPnt = rRectangle.TopCenter(); eKind = SdrHdlKind::Upper; break; + case 4: aPnt = rRectangle.TopRight(); eKind = SdrHdlKind::UpperRight; break; + case 5: aPnt = rRectangle.LeftCenter(); eKind = SdrHdlKind::Left ; break; + case 6: aPnt = rRectangle.RightCenter(); eKind = SdrHdlKind::Right; break; + case 7: aPnt = rRectangle.BottomLeft(); eKind = SdrHdlKind::LowerLeft; break; + case 8: aPnt = rRectangle.BottomCenter(); eKind = SdrHdlKind::Lower; break; + case 9: aPnt = rRectangle.BottomRight(); eKind = SdrHdlKind::LowerRight; break; + } + + if (maGeo.m_nShearAngle) + { + ShearPoint(aPnt,rRectangle.TopLeft(),maGeo.mfTanShearAngle); + } + if (maGeo.m_nRotationAngle) + { + RotatePoint(aPnt,rRectangle.TopLeft(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + } + + std::unique_ptr<SdrHdl> pH(new SdrHdl(aPnt,eKind)); + pH->SetObj(const_cast<SdrRectObj*>(this)); + pH->SetRotationAngle(maGeo.m_nRotationAngle); + rHdlList.AddHdl(std::move(pH)); + } +} + +bool SdrRectObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrRectObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const bool bRad(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if(bRad) + { + rDrag.SetEndDragChangesAttributes(true); + + return true; + } + + return SdrTextObj::beginSpecialDrag(rDrag); +} + +bool SdrRectObj::applySpecialDrag(SdrDragStat& rDrag) +{ + const bool bRad(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if (bRad) + { + Point aPt(rDrag.GetNow()); + + if (maGeo.m_nRotationAngle) + RotatePoint(aPt, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + sal_Int32 nRad(aPt.X() - getRectangle().Left()); + + if (nRad < 0) + nRad = 0; + + if(nRad != GetEckenradius()) + { + NbcSetEckenradius(nRad); + } + + return true; + } + else + { + return SdrTextObj::applySpecialDrag(rDrag); + } +} + +OUString SdrRectObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj()); + + if(bCreateComment) + { + return OUString(); + } + else + { + const bool bRad(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind()); + + if(bRad) + { + Point aPt(rDrag.GetNow()); + + // -sin for reversal + if (maGeo.m_nRotationAngle) + RotatePoint(aPt, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + sal_Int32 nRad(aPt.X() - getRectangle().Left()); + + if(nRad < 0) + nRad = 0; + + return ImpGetDescriptionStr(STR_DragRectEckRad) + + " (" + + GetMetrStr(nRad) + + ")"; + } + else + { + return SdrTextObj::getSpecialDragComment(rDrag); + } + } +} + + +basegfx::B2DPolyPolygon SdrRectObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + tools::Rectangle aRect1; + rDrag.TakeCreateRect(aRect1); + aRect1.Normalize(); + + basegfx::B2DPolyPolygon aRetval; + aRetval.append(ImpCalcXPoly(aRect1,GetEckenradius()).getB2DPolygon()); + return aRetval; +} + +PointerStyle SdrRectObj::GetCreatePointer() const +{ + if (IsTextFrame()) return PointerStyle::DrawText; + return PointerStyle::DrawRect; +} + +void SdrRectObj::NbcMove(const Size& rSiz) +{ + SdrTextObj::NbcMove(rSiz); + SetXPolyDirty(); +} + +void SdrRectObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + SdrTextObj::NbcResize(rRef,xFact,yFact); + SetXPolyDirty(); +} + +void SdrRectObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + SdrTextObj::NbcRotate(rRef,nAngle,sn,cs); + SetXPolyDirty(); +} + +void SdrRectObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear); + SetXPolyDirty(); +} + +void SdrRectObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SdrTextObj::NbcMirror(rRef1,rRef2); + SetXPolyDirty(); +} + +SdrGluePoint SdrRectObj::GetVertexGluePoint(sal_uInt16 nPosNum) const +{ + sal_Int32 nWdt = ImpGetLineWdt(); // #i25616# + + // #i25616# + if(!LineIsOutsideGeometry()) + { + nWdt++; + nWdt /= 2; + } + + Point aPt; + auto const& rRectangle = getRectangle(); + switch (nPosNum) { + case 0: aPt = rRectangle.TopCenter(); aPt.AdjustY( -nWdt ); break; + case 1: aPt = rRectangle.RightCenter(); aPt.AdjustX(nWdt ); break; + case 2: aPt = rRectangle.BottomCenter(); aPt.AdjustY(nWdt ); break; + case 3: aPt = rRectangle.LeftCenter(); aPt.AdjustX( -nWdt ); break; + } + if (maGeo.m_nShearAngle) + ShearPoint(aPt, rRectangle.TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) + RotatePoint(aPt, rRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aPt-=GetSnapRect().Center(); + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + return aGP; +} + +SdrGluePoint SdrRectObj::GetCornerGluePoint(sal_uInt16 nPosNum) const +{ + sal_Int32 nWdt = ImpGetLineWdt(); // #i25616# + + // #i25616# + if(!LineIsOutsideGeometry()) + { + nWdt++; + nWdt /= 2; + } + + Point aPt; + auto const& rRectangle = getRectangle(); + switch (nPosNum) { + case 0: aPt = rRectangle.TopLeft(); aPt.AdjustX( -nWdt ); aPt.AdjustY( -nWdt ); break; + case 1: aPt = rRectangle.TopRight(); aPt.AdjustX(nWdt ); aPt.AdjustY( -nWdt ); break; + case 2: aPt = rRectangle.BottomRight(); aPt.AdjustX(nWdt ); aPt.AdjustY(nWdt ); break; + case 3: aPt = rRectangle.BottomLeft(); aPt.AdjustX( -nWdt ); aPt.AdjustY(nWdt ); break; + } + if (maGeo.m_nShearAngle) + ShearPoint(aPt, rRectangle.TopLeft(),maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) + RotatePoint(aPt, rRectangle.TopLeft(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + aPt-=GetSnapRect().Center(); + SdrGluePoint aGP(aPt); + aGP.SetPercent(false); + return aGP; +} + +rtl::Reference<SdrObject> SdrRectObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + XPolygon aXP(ImpCalcXPoly(getRectangle(), GetEckenradius())); + { // TODO: this is only for the moment, until we have the new TakeContour() + aXP.Remove(0,1); + aXP[aXP.GetPointCount()-1]=aXP[0]; + } + + basegfx::B2DPolyPolygon aPolyPolygon(aXP.getB2DPolygon()); + aPolyPolygon.removeDoublePoints(); + rtl::Reference<SdrObject> pRet; + + // small correction: Do not create something when no fill and no line. To + // be sure to not damage something with non-text frames, do this only + // when used with bAddText==false from other converters + if((bAddText && !IsTextFrame()) || HasFill() || HasLine()) + { + pRet = ImpConvertMakeObj(aPolyPolygon, true, bBezier); + } + + if(bAddText) + { + pRet = ImpConvertAddText(std::move(pRet), bBezier); + } + + return pRet; +} + +void SdrRectObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + SdrTextObj::Notify(rBC,rHint); + SetXPolyDirty(); // because of the corner radius +} + +void SdrRectObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + SdrTextObj::RestoreGeoData(rGeo); + SetXPolyDirty(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotext.cxx b/svx/source/svdraw/svdotext.cxx new file mode 100644 index 0000000000..e88e127e4f --- /dev/null +++ b/svx/source/svdraw/svdotext.cxx @@ -0,0 +1,2251 @@ +/* -*- 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 <comphelper/string.hxx> +#include <svl/stritem.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <editeng/writingmodeitem.hxx> +#include <svx/sdtfchim.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editobj.hxx> +#include <editeng/outliner.hxx> +#include <textchain.hxx> +#include <textchainflow.hxx> +#include <tools/helpers.hxx> +#include <svx/sderitm.hxx> +#include <svx/sdooitm.hxx> +#include <svx/sdshitm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtfsitm.hxx> +#include <svx/sdtmfitm.hxx> +#include <svx/xtextit0.hxx> +#include <svx/compatflags.hxx> +#include <sdr/properties/textproperties.hxx> +#include <sdr/contact/viewcontactoftextobj.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/virdev.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sal/log.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/temporary.hxx> +#include <unotools/configmgr.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> + +using namespace com::sun::star; + +// BaseProperties section +std::unique_ptr<sdr::properties::BaseProperties> SdrTextObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::TextProperties>(*this); +} + +// DrawContact section +std::unique_ptr<sdr::contact::ViewContact> SdrTextObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfTextObj>(*this); +} + +SdrTextObj::SdrTextObj(SdrModel& rSdrModel) + : SdrAttrObj(rSdrModel) + , mpEditingOutliner(nullptr) + , meTextKind(SdrObjKind::Text) + , maTextEditOffset(Point(0, 0)) + , mbTextFrame(false) + , mbNoShear(false) + , mbTextSizeDirty(false) + , mbInEditMode(false) + , mbDisableAutoWidthOnDragging(false) + , mbTextAnimationAllowed(true) + , mbInDownScale(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = true; +} + +SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrTextObj const & rSource) + : SdrAttrObj(rSdrModel, rSource) + , mpEditingOutliner(nullptr) + , meTextKind(rSource.meTextKind) + , maTextEditOffset(Point(0, 0)) + , mbTextFrame(rSource.mbTextFrame) + , mbNoShear(rSource.mbNoShear) + , mbTextSizeDirty(rSource.mbTextSizeDirty) + , mbInEditMode(false) + , mbDisableAutoWidthOnDragging(rSource.mbDisableAutoWidthOnDragging) + , mbTextAnimationAllowed(true) + , mbInDownScale(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = true; + + maRectangle = rSource.maRectangle; + maGeo = rSource.maGeo; + maTextSize = rSource.maTextSize; + + // Not all of the necessary parameters were copied yet. + SdrText* pText = getActiveText(); + + if( pText && rSource.HasText() ) + { + // before pNewOutlinerParaObject was created the same, but + // set at mpText (outside this scope), but mpText might be + // empty (this operator== seems not prepared for MultiText + // objects). In the current form it makes only sense to + // create locally and use locally on a known existing SdrText + const Outliner* pEO = rSource.mpEditingOutliner; + std::optional<OutlinerParaObject> pNewOutlinerParaObject; + + if (pEO!=nullptr) + { + pNewOutlinerParaObject = pEO->CreateParaObject(); + } + else if (nullptr != rSource.getActiveText()->GetOutlinerParaObject()) + { + pNewOutlinerParaObject = *rSource.getActiveText()->GetOutlinerParaObject(); + } + + pText->SetOutlinerParaObject( std::move(pNewOutlinerParaObject) ); + } + + ImpSetTextStyleSheetListeners(); +} + +SdrTextObj::SdrTextObj(SdrModel& rSdrModel, const tools::Rectangle& rNewRect) + : SdrAttrObj(rSdrModel) + , mpEditingOutliner(nullptr) + , meTextKind(SdrObjKind::Text) + , maTextEditOffset(Point(0, 0)) + , mbTextFrame(false) + , mbNoShear(false) + , mbTextSizeDirty(false) + , mbInEditMode(false) + , mbDisableAutoWidthOnDragging(false) + , mbTextAnimationAllowed(true) + , mbInDownScale(false) +{ + tools::Rectangle aRectangle(rNewRect); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = true; +} + +SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrObjKind eNewTextKind) + : SdrAttrObj(rSdrModel) + , mpEditingOutliner(nullptr) + , meTextKind(eNewTextKind) + , maTextEditOffset(Point(0, 0)) + , mbTextFrame(true) + , mbNoShear(true) + , mbTextSizeDirty(false) + , mbInEditMode(false) + , mbDisableAutoWidthOnDragging(false) + , mbTextAnimationAllowed(true) + , mbInDownScale(false) +{ + // #i25616# + mbSupportTextIndentingOnLineWidthChange = true; +} + +SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrObjKind eNewTextKind, + const tools::Rectangle& rNewRect) + : SdrAttrObj(rSdrModel) + , mpEditingOutliner(nullptr) + , meTextKind(eNewTextKind) + , maTextEditOffset(Point(0, 0)) + , mbTextFrame(true) + , mbNoShear(true) + , mbTextSizeDirty(false) + , mbInEditMode(false) + , mbDisableAutoWidthOnDragging(false) + , mbTextAnimationAllowed(true) + , mbInDownScale(false) +{ + tools::Rectangle aRectangle(rNewRect); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + + // #i25616# + mbSupportTextIndentingOnLineWidthChange = true; +} + +SdrTextObj::~SdrTextObj() +{ + mxText.clear(); + ImpDeregisterLink(); +} + +void SdrTextObj::FitFrameToTextSize() +{ + ImpJustifyRect(maRectangle); + + SdrText* pText = getActiveText(); + if(pText==nullptr || !pText->GetOutlinerParaObject()) + return; + + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + rOutliner.SetPaperSize(Size(getRectangle().Right() - getRectangle().Left(), getRectangle().Bottom() - getRectangle().Top())); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pText->GetOutlinerParaObject()); + Size aNewSize(rOutliner.CalcTextSize()); + rOutliner.Clear(); + aNewSize.AdjustWidth( 1 ); // because of possible rounding errors + aNewSize.AdjustWidth(GetTextLeftDistance()+GetTextRightDistance() ); + aNewSize.AdjustHeight(GetTextUpperDistance()+GetTextLowerDistance() ); + tools::Rectangle aNewRect(getRectangle()); + aNewRect.SetSize(aNewSize); + ImpJustifyRect(aNewRect); + + if (aNewRect != getRectangle()) + SetLogicRect(aNewRect); +} + +void SdrTextObj::NbcSetText(const OUString& rStr) +{ + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + rOutliner.SetStyleSheet( 0, GetStyleSheet()); + rOutliner.SetText(rStr,rOutliner.GetParagraph( 0 )); + std::optional<OutlinerParaObject> pNewText=rOutliner.CreateParaObject(); + NbcSetOutlinerParaObject(std::move(pNewText)); + mbTextSizeDirty=true; +} + +void SdrTextObj::SetText(const OUString& rStr) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetText(rStr); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrTextObj::NbcSetText(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat) +{ + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + rOutliner.SetStyleSheet( 0, GetStyleSheet()); + rOutliner.Read(rInput,rBaseURL,eFormat); + std::optional<OutlinerParaObject> pNewText=rOutliner.CreateParaObject(); + rOutliner.SetUpdateLayout(true); + Size aSize(rOutliner.CalcTextSize()); + rOutliner.Clear(); + NbcSetOutlinerParaObject(std::move(pNewText)); + maTextSize=aSize; + mbTextSizeDirty=false; +} + +void SdrTextObj::SetText(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcSetText(rInput,rBaseURL,eFormat); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +const Size& SdrTextObj::GetTextSize() const +{ + if (mbTextSizeDirty) + { + Size aSiz; + SdrText* pText = getActiveText(); + if( pText && pText->GetOutlinerParaObject ()) + { + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + rOutliner.SetText(*pText->GetOutlinerParaObject()); + rOutliner.SetUpdateLayout(true); + aSiz=rOutliner.CalcTextSize(); + rOutliner.Clear(); + } + // casting to nonconst twice + const_cast<SdrTextObj*>(this)->maTextSize = aSiz; + const_cast<SdrTextObj*>(this)->mbTextSizeDirty = false; + } + return maTextSize; +} + +bool SdrTextObj::IsAutoGrowHeight() const +{ + if(!mbTextFrame) + return false; // AutoGrow only together with TextFrames + + const SfxItemSet& rSet = GetObjectItemSet(); + bool bRet = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + + if(bRet) + { + SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue(); + + if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide) + { + SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + + if(eDirection == SdrTextAniDirection::Up || eDirection == SdrTextAniDirection::Down) + { + bRet = false; + } + } + } + return bRet; +} + +bool SdrTextObj::IsAutoGrowWidth() const +{ + if (!mbTextFrame) + return false; // AutoGrow only together with TextFrames + + const SfxItemSet& rSet = GetObjectItemSet(); + bool bRet = rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue(); + + bool bInEditMOde = IsInEditMode(); + + if(!bInEditMOde && bRet) + { + SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue(); + + if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide) + { + SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + + if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right) + { + bRet = false; + } + } + } + return bRet; +} + +SdrTextHorzAdjust SdrTextObj::GetTextHorizontalAdjust() const +{ + return GetTextHorizontalAdjust(GetObjectItemSet()); +} + +SdrTextHorzAdjust SdrTextObj::GetTextHorizontalAdjust(const SfxItemSet& rSet) const +{ + if(IsContourTextFrame()) + return SDRTEXTHORZADJUST_BLOCK; + + SdrTextHorzAdjust eRet = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue(); + + bool bInEditMode = IsInEditMode(); + + if(!bInEditMode && eRet == SDRTEXTHORZADJUST_BLOCK) + { + SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue(); + + if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide) + { + SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + + if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right) + { + eRet = SDRTEXTHORZADJUST_LEFT; + } + } + } + + return eRet; +} // defaults: BLOCK (justify) for text frame, CENTER for captions of drawing objects + +SdrTextVertAdjust SdrTextObj::GetTextVerticalAdjust() const +{ + return GetTextVerticalAdjust(GetObjectItemSet()); +} + +SdrTextVertAdjust SdrTextObj::GetTextVerticalAdjust(const SfxItemSet& rSet) const +{ + if(IsContourTextFrame()) + return SDRTEXTVERTADJUST_TOP; + + // Take care for vertical text animation here + SdrTextVertAdjust eRet = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue(); + bool bInEditMode = IsInEditMode(); + + // Take care for vertical text animation here + if(!bInEditMode && eRet == SDRTEXTVERTADJUST_BLOCK) + { + SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue(); + + if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide) + { + SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + + if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right) + { + eRet = SDRTEXTVERTADJUST_TOP; + } + } + } + + return eRet; +} // defaults: TOP for text frame, CENTER for captions of drawing objects + +void SdrTextObj::ImpJustifyRect(tools::Rectangle& rRect) +{ + if (!rRect.IsEmpty()) { + rRect.Normalize(); + if (rRect.Left()==rRect.Right()) rRect.AdjustRight( 1 ); + if (rRect.Top()==rRect.Bottom()) rRect.AdjustBottom( 1 ); + } +} + +void SdrTextObj::ImpCheckShear() +{ + if (mbNoShear && maGeo.m_nShearAngle) + { + maGeo.m_nShearAngle = 0_deg100; + maGeo.mfTanShearAngle = 0; + } +} + +void SdrTextObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + bool bNoTextFrame=!IsTextFrame(); + rInfo.bResizeFreeAllowed=bNoTextFrame || ((maGeo.m_nRotationAngle.get() % 9000) == 0); + rInfo.bResizePropAllowed=true; + rInfo.bRotateFreeAllowed=true; + rInfo.bRotate90Allowed =true; + rInfo.bMirrorFreeAllowed=bNoTextFrame; + rInfo.bMirror45Allowed =bNoTextFrame; + rInfo.bMirror90Allowed =bNoTextFrame; + + // allow transparency + rInfo.bTransparenceAllowed = true; + + rInfo.bShearAllowed =bNoTextFrame; + rInfo.bEdgeRadiusAllowed=true; + bool bCanConv=ImpCanConvTextToCurve(); + rInfo.bCanConvToPath =bCanConv; + rInfo.bCanConvToPoly =bCanConv; + rInfo.bCanConvToPathLineToArea=bCanConv; + rInfo.bCanConvToPolyLineToArea=bCanConv; + rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary()); +} + +SdrObjKind SdrTextObj::GetObjIdentifier() const +{ + return meTextKind; +} + +bool SdrTextObj::HasTextImpl( SdrOutliner const * pOutliner ) +{ + bool bRet=false; + if(pOutliner) + { + Paragraph* p1stPara=pOutliner->GetParagraph( 0 ); + sal_Int32 nParaCount=pOutliner->GetParagraphCount(); + if(p1stPara==nullptr) + nParaCount=0; + + if(nParaCount==1) + { + // if it is only one paragraph, check if that paragraph is empty + if( pOutliner->GetText(p1stPara).isEmpty() ) + nParaCount = 0; + } + + bRet= nParaCount!=0; + } + return bRet; +} + +void SdrTextObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + const bool bRemove(pNewPage == nullptr && pOldPage != nullptr); + const bool bInsert(pNewPage != nullptr && pOldPage == nullptr); + const bool bLinked(IsLinkedText()); + + if (bLinked && bRemove) + { + ImpDeregisterLink(); + } + + // call parent + SdrAttrObj::handlePageChange(pOldPage, pNewPage); + + if (bLinked && bInsert) + { + ImpRegisterLink(); + } +} + +void SdrTextObj::NbcSetEckenradius(tools::Long nRad) +{ + SetObjectItem(makeSdrEckenradiusItem(nRad)); +} + +// #115391# This implementation is based on the object size (aRect) and the +// states of IsAutoGrowWidth/Height to correctly set TextMinFrameWidth/Height +void SdrTextObj::AdaptTextMinSize() +{ + if (!mbTextFrame) + // Only do this for text frame. + return; + + if (getSdrModelFromSdrObject().IsPasteResize()) + // Don't do this during paste resize. + return; + + const bool bW = IsAutoGrowWidth(); + const bool bH = IsAutoGrowHeight(); + + if (!bW && !bH) + // No auto grow requested. Bail out. + return; + + SfxItemSetFixed<SDRATTR_TEXT_MINFRAMEHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT, + SDRATTR_TEXT_MINFRAMEWIDTH, SDRATTR_TEXT_AUTOGROWWIDTH> // contains SDRATTR_TEXT_MAXFRAMEWIDTH + aSet(*GetObjectItemSet().GetPool()); + + if(bW) + { + // Set minimum width. + const tools::Long nDist = GetTextLeftDistance() + GetTextRightDistance(); + const tools::Long nW = std::max<tools::Long>(0, getRectangle().GetWidth() - 1 - nDist); // text width without margins + + aSet.Put(makeSdrTextMinFrameWidthItem(nW)); + + if(!IsVerticalWriting() && mbDisableAutoWidthOnDragging) + { + mbDisableAutoWidthOnDragging = true; + aSet.Put(makeSdrTextAutoGrowWidthItem(false)); + } + } + + if(bH) + { + // Set Minimum height. + const tools::Long nDist = GetTextUpperDistance() + GetTextLowerDistance(); + const tools::Long nH = std::max<tools::Long>(0, getRectangle().GetHeight() - 1 - nDist); // text height without margins + + aSet.Put(makeSdrTextMinFrameHeightItem(nH)); + + if(IsVerticalWriting() && mbDisableAutoWidthOnDragging) + { + mbDisableAutoWidthOnDragging = false; + aSet.Put(makeSdrTextAutoGrowHeightItem(false)); + } + } + + SetObjectItemSet(aSet); +} + +void SdrTextObj::ImpSetContourPolygon( SdrOutliner& rOutliner, tools::Rectangle const & rAnchorRect, bool bLineWidth ) const +{ + basegfx::B2DPolyPolygon aXorPolyPolygon(TakeXorPoly()); + std::optional<basegfx::B2DPolyPolygon> pContourPolyPolygon; + basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix( + -rAnchorRect.Left(), -rAnchorRect.Top())); + + if(maGeo.m_nRotationAngle) + { + // Unrotate! + aMatrix.rotate(-toRadians(maGeo.m_nRotationAngle)); + } + + aXorPolyPolygon.transform(aMatrix); + + if( bLineWidth ) + { + // Take line width into account. + // When doing the hit test, avoid this. (Performance!) + pContourPolyPolygon.emplace(); + + // test if shadow needs to be avoided for TakeContour() + const SfxItemSet& rSet = GetObjectItemSet(); + bool bShadowOn = rSet.Get(SDRATTR_SHADOW).GetValue(); + + // #i33696# + // Remember TextObject currently set at the DrawOutliner, it WILL be + // replaced during calculating the outline since it uses an own paint + // and that one uses the DrawOutliner, too. + const SdrTextObj* pLastTextObject = rOutliner.GetTextObj(); + + if(bShadowOn) + { + // force shadow off + rtl::Reference<SdrTextObj> pCopy = SdrObject::Clone(*this, getSdrModelFromSdrObject()); + pCopy->SetMergedItem(makeSdrShadowItem(false)); + *pContourPolyPolygon = pCopy->TakeContour(); + } + else + { + *pContourPolyPolygon = TakeContour(); + } + + // #i33696# + // restore remembered text object + if(pLastTextObject != rOutliner.GetTextObj()) + { + rOutliner.SetTextObj(pLastTextObject); + } + + pContourPolyPolygon->transform(aMatrix); + } + + rOutliner.SetPolygon(aXorPolyPolygon, pContourPolyPolygon ? &*pContourPolyPolygon : nullptr); +} + +void SdrTextObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const +{ + rRect = getRectangle(); +} + +// See also: <unnamed>::getTextAnchorRange in svx/source/sdr/primitive2d/sdrdecompositiontools.cxx +void SdrTextObj::AdjustRectToTextDistance(tools::Rectangle& rAnchorRect) const +{ + const tools::Long nLeftDist = GetTextLeftDistance(); + const tools::Long nRightDist = GetTextRightDistance(); + const tools::Long nUpperDist = GetTextUpperDistance(); + const tools::Long nLowerDist = GetTextLowerDistance(); + if (!IsVerticalWriting()) + { + rAnchorRect.AdjustLeft(nLeftDist); + rAnchorRect.AdjustTop(nUpperDist); + rAnchorRect.AdjustRight(-nRightDist); + rAnchorRect.AdjustBottom(-nLowerDist); + } + else if (IsTopToBottom()) + { + rAnchorRect.AdjustLeft(nLowerDist); + rAnchorRect.AdjustTop(nLeftDist); + rAnchorRect.AdjustRight(-nUpperDist); + rAnchorRect.AdjustBottom(-nRightDist); + } + else + { + rAnchorRect.AdjustLeft(nUpperDist); + rAnchorRect.AdjustTop(nRightDist); + rAnchorRect.AdjustRight(-nLowerDist); + rAnchorRect.AdjustBottom(-nLeftDist); + } + + // Since sizes may be bigger than the object bounds it is necessary to + // justify the rect now. + ImpJustifyRect(rAnchorRect); +} + +void SdrTextObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const +{ + tools::Rectangle aAnkRect(getRectangle()); // the rectangle in which we anchor + bool bFrame=IsTextFrame(); + if (!bFrame) { + TakeUnrotatedSnapRect(aAnkRect); + } + Point aRotateRef(aAnkRect.TopLeft()); + AdjustRectToTextDistance(aAnkRect); + + if (bFrame) { + // TODO: Optimize this. + if (aAnkRect.GetWidth()<2) aAnkRect.SetRight(aAnkRect.Left()+1 ); // minimum size h and v: 2 px + if (aAnkRect.GetHeight()<2) aAnkRect.SetBottom(aAnkRect.Top()+1 ); + } + if (maGeo.m_nRotationAngle) { + Point aTmpPt(aAnkRect.TopLeft()); + RotatePoint(aTmpPt,aRotateRef,maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + aTmpPt-=aAnkRect.TopLeft(); + aAnkRect.Move(aTmpPt.X(),aTmpPt.Y()); + } + rAnchorRect=aAnkRect; +} + +void SdrTextObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, + tools::Rectangle* pAnchorRect, bool bLineWidth ) const +{ + tools::Rectangle aAnkRect; // the rectangle in which we anchor + TakeTextAnchorRect(aAnkRect); + SdrTextVertAdjust eVAdj=GetTextVerticalAdjust(); + SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust(); + SdrTextAniKind eAniKind=GetTextAniKind(); + SdrTextAniDirection eAniDirection=GetTextAniDirection(); + + bool bFitToSize(IsFitToSize()); + bool bContourFrame=IsContourTextFrame(); + + bool bFrame=IsTextFrame(); + EEControlBits nStat0=rOutliner.GetControlWord(); + Size aNullSize; + if (!bContourFrame) + { + rOutliner.SetControlWord(nStat0|EEControlBits::AUTOPAGESIZE); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + } + + if (!bFitToSize && !bContourFrame) + { + tools::Long nAnkWdt=aAnkRect.GetWidth(); + tools::Long nAnkHgt=aAnkRect.GetHeight(); + if (bFrame) + { + tools::Long nWdt=nAnkWdt; + tools::Long nHgt=nAnkHgt; + + bool bInEditMode = IsInEditMode(); + + if (!bInEditMode && (eAniKind==SdrTextAniKind::Scroll || eAniKind==SdrTextAniKind::Alternate || eAniKind==SdrTextAniKind::Slide)) + { + // unlimited paper size for ticker text + if (eAniDirection==SdrTextAniDirection::Left || eAniDirection==SdrTextAniDirection::Right) nWdt=1000000; + if (eAniDirection==SdrTextAniDirection::Up || eAniDirection==SdrTextAniDirection::Down) nHgt=1000000; + } + + bool bChainedFrame = IsChainable(); + // Might be required for overflow check working: do limit height to frame if box is chainable. + if (!bChainedFrame) { + // #i119885# Do not limit/force height to geometrical frame (vice versa for vertical writing) + + if(IsVerticalWriting()) + { + nWdt = 1000000; + } + else + { + nHgt = 1000000; + } + } + + rOutliner.SetMaxAutoPaperSize(Size(nWdt,nHgt)); + } + + // New try with _BLOCK for hor and ver after completely + // supporting full width for vertical text. + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(nAnkWdt, 0)); + rOutliner.SetMinColumnWrapHeight(nAnkHgt); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnkHgt)); + rOutliner.SetMinColumnWrapHeight(nAnkWdt); + } + } + + rOutliner.SetPaperSize(aNullSize); + if (bContourFrame) + ImpSetContourPolygon( rOutliner, aAnkRect, bLineWidth ); + + // put text into the outliner, if available from the edit outliner + SdrText* pText = getActiveText(); + OutlinerParaObject* pOutlinerParaObject = pText ? pText->GetOutlinerParaObject() : nullptr; + std::optional<OutlinerParaObject> pPara; + if (mpEditingOutliner && !bNoEditText) + pPara = mpEditingOutliner->CreateParaObject(); + else if (pOutlinerParaObject) + pPara = *pOutlinerParaObject; + + if (pPara) + { + const bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner); + const SdrTextObj* pTestObj = rOutliner.GetTextObj(); + + if( !pTestObj || !bHitTest || pTestObj != this || + pTestObj->GetOutlinerParaObject() != pOutlinerParaObject ) + { + if( bHitTest ) // #i33696# take back fix #i27510# + { + rOutliner.SetTextObj( this ); + rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pPara); + } + } + else + { + rOutliner.SetTextObj( nullptr ); + } + + rOutliner.SetUpdateLayout(true); + rOutliner.SetControlWord(nStat0); + + if( pText ) + pText->CheckPortionInfo(rOutliner); + + Point aTextPos(aAnkRect.TopLeft()); + Size aTextSiz(rOutliner.GetPaperSize()); // GetPaperSize() adds a little tolerance, right? + + // For draw objects containing text correct hor/ver alignment if text is bigger + // than the object itself. Without that correction, the text would always be + // formatted to the left edge (or top edge when vertical) of the draw object. + if(!IsTextFrame()) + { + if(aAnkRect.GetWidth() < aTextSiz.Width() && !IsVerticalWriting()) + { + // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTHORZADJUST_BLOCK == eHAdj) + { + eHAdj = SDRTEXTHORZADJUST_CENTER; + } + } + + if(aAnkRect.GetHeight() < aTextSiz.Height() && IsVerticalWriting()) + { + // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTVERTADJUST_BLOCK == eVAdj) + { + eVAdj = SDRTEXTVERTADJUST_CENTER; + } + } + } + + if (eHAdj==SDRTEXTHORZADJUST_CENTER || eHAdj==SDRTEXTHORZADJUST_RIGHT) + { + tools::Long nFreeWdt=aAnkRect.GetWidth()-aTextSiz.Width(); + if (eHAdj==SDRTEXTHORZADJUST_CENTER) + aTextPos.AdjustX(nFreeWdt/2 ); + if (eHAdj==SDRTEXTHORZADJUST_RIGHT) + aTextPos.AdjustX(nFreeWdt ); + } + if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM) + { + tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height(); + if (eVAdj==SDRTEXTVERTADJUST_CENTER) + aTextPos.AdjustY(nFreeHgt/2 ); + if (eVAdj==SDRTEXTVERTADJUST_BOTTOM) + aTextPos.AdjustY(nFreeHgt ); + } + if (maGeo.m_nRotationAngle) + RotatePoint(aTextPos,aAnkRect.TopLeft(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + + if (pAnchorRect) + *pAnchorRect=aAnkRect; + + // rTextRect might not be correct in some cases at ContourFrame + rTextRect=tools::Rectangle(aTextPos,aTextSiz); + if (bContourFrame) + rTextRect=aAnkRect; +} + +bool SdrTextObj::CanCreateEditOutlinerParaObject() const +{ + if( HasTextImpl( mpEditingOutliner ) ) + { + return mpEditingOutliner->GetParagraphCount() > 0; + } + return false; +} + +std::optional<OutlinerParaObject> SdrTextObj::CreateEditOutlinerParaObject() const +{ + std::optional<OutlinerParaObject> pPara; + if( HasTextImpl( mpEditingOutliner ) ) + { + sal_Int32 nParaCount = mpEditingOutliner->GetParagraphCount(); + pPara = mpEditingOutliner->CreateParaObject(0, nParaCount); + } + return pPara; +} + +void SdrTextObj::ImpSetCharStretching(SdrOutliner& rOutliner, const Size& rTextSize, const Size& rShapeSize, Fraction& rFitXCorrection) +{ + OutputDevice* pOut = rOutliner.GetRefDevice(); + bool bNoStretching(false); + + if(pOut && pOut->GetOutDevType() == OUTDEV_PRINTER) + { + // check whether CharStretching is possible at all + GDIMetaFile* pMtf = pOut->GetConnectMetaFile(); + OUString aTestString(u'J'); + + if(pMtf && (!pMtf->IsRecord() || pMtf->IsPause())) + pMtf = nullptr; + + if(pMtf) + pMtf->Pause(true); + + vcl::Font aOriginalFont(pOut->GetFont()); + vcl::Font aTmpFont( OutputDevice::GetDefaultFont( DefaultFontType::SERIF, LANGUAGE_SYSTEM, GetDefaultFontFlags::OnlyOne ) ); + + aTmpFont.SetFontSize(Size(0,100)); + pOut->SetFont(aTmpFont); + Size aSize1(pOut->GetTextWidth(aTestString), pOut->GetTextHeight()); + aTmpFont.SetFontSize(Size(800,100)); + pOut->SetFont(aTmpFont); + Size aSize2(pOut->GetTextWidth(aTestString), pOut->GetTextHeight()); + pOut->SetFont(aOriginalFont); + + if(pMtf) + pMtf->Pause(false); + + bNoStretching = (aSize1 == aSize2); + +#ifdef _WIN32 + // Windows zooms the font proportionally when using Size(100,500), + // we don't like that. + if(aSize2.Height() >= aSize1.Height() * 2) + { + bNoStretching = true; + } +#endif + } + + rOutliner.setRoundFontSizeToPt(false); + + unsigned nLoopCount=0; + bool bNoMoreLoop = false; + tools::Long nXDiff0=0x7FFFFFFF; + tools::Long nWantWdt=rShapeSize.Width(); + tools::Long nIsWdt=rTextSize.Width(); + if (nIsWdt==0) nIsWdt=1; + + tools::Long nWantHgt=rShapeSize.Height(); + tools::Long nIsHgt=rTextSize.Height(); + if (nIsHgt==0) nIsHgt=1; + + tools::Long nXTolPl=nWantWdt/100; // tolerance: +1% + tools::Long nXTolMi=nWantWdt/25; // tolerance: -4% + tools::Long nXCorr =nWantWdt/20; // correction scale: 5% + + double nX = (nWantWdt * 100.0) / double(nIsWdt); // calculate X stretching + double nY = (nWantHgt * 100.0) / double(nIsHgt); // calculate Y stretching + bool bChkX = true; + if (bNoStretching) + { // might only be possible proportionally + if (nX > nY) + { + nX = nY; + bChkX = false; + } + else + { + nY = nX; + } + } + + while (nLoopCount<5 && !bNoMoreLoop) + { + if (nX < 0.0) + nX = -nX; + if (nX < 1.0) + { + nX = 1.0; + bNoMoreLoop = true; + } + if (nX > 65535.0) + { + nX = 65535.0; + bNoMoreLoop = true; + } + + if (nY < 0.0) + { + nY = -nY; + } + if (nY < 1.0) + { + nY = 1.0; + bNoMoreLoop = true; + } + if (nY > 65535.0) + { + nY = 65535.0; + bNoMoreLoop = true; + } + + // exception, there is no text yet (horizontal case) + if (nIsWdt <= 1) + { + nX = nY; + bNoMoreLoop = true; + } + + // exception, there is no text yet (vertical case) + if (nIsHgt <= 1) + { + nY = nX; + bNoMoreLoop = true; + } + rOutliner.setGlobalScale(nX, nY); + nLoopCount++; + Size aSiz(rOutliner.CalcTextSize()); + tools::Long nXDiff = aSiz.Width() - nWantWdt; + rFitXCorrection=Fraction(nWantWdt,aSiz.Width()); + if (((nXDiff>=nXTolMi || !bChkX) && nXDiff<=nXTolPl) || nXDiff==nXDiff0) { + bNoMoreLoop = true; + } else { + // correct stretching factors + tools::Long nMul = nWantWdt; + tools::Long nDiv = aSiz.Width(); + if (std::abs(nXDiff) <= 2 * nXCorr) + { + if (nMul > nDiv) + nDiv += (nMul - nDiv) / 2.0; // but only add half of what we calculated, + else + nMul += (nDiv - nMul) / 2.0;// because the EditEngine calculates wrongly later on + } + nX = nX * double(nMul) / double(nDiv); + if (bNoStretching) + nY = nX; + } + nXDiff0 = nXDiff; + } +} + +OUString SdrTextObj::TakeObjNameSingul() const +{ + OUString aStr; + + switch(meTextKind) + { + case SdrObjKind::OutlineText: + { + aStr = SvxResId(STR_ObjNameSingulOUTLINETEXT); + break; + } + + case SdrObjKind::TitleText : + { + aStr = SvxResId(STR_ObjNameSingulTITLETEXT); + break; + } + + default: + { + if(IsLinkedText()) + aStr = SvxResId(STR_ObjNameSingulTEXTLNK); + else + aStr = SvxResId(STR_ObjNameSingulTEXT); + break; + } + } + + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if(pOutlinerParaObject && meTextKind != SdrObjKind::OutlineText) + { + // shouldn't currently cause any problems at OUTLINETEXT + OUString aStr2(comphelper::string::stripStart(pOutlinerParaObject->GetTextObject().GetText(0), ' ')); + + // avoid non expanded text portions in object name + // (second condition is new) + if(!aStr2.isEmpty() && aStr2.indexOf(u'\x00FF') == -1) + { + // space between ResStr and content text + aStr += " \'"; + + if(aStr2.getLength() > 10) + { + aStr2 = OUString::Concat(aStr2.subView(0, 8)) + "..."; + } + + aStr += aStr2 + "\'"; + } + } + + OUString sName(aStr); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrTextObj::TakeObjNamePlural() const +{ + OUString sName; + switch (meTextKind) + { + case SdrObjKind::OutlineText: sName=SvxResId(STR_ObjNamePluralOUTLINETEXT); break; + case SdrObjKind::TitleText : sName=SvxResId(STR_ObjNamePluralTITLETEXT); break; + default: { + if (IsLinkedText()) { + sName=SvxResId(STR_ObjNamePluralTEXTLNK); + } else { + sName=SvxResId(STR_ObjNamePluralTEXT); + } + } break; + } // switch + return sName; +} + +rtl::Reference<SdrObject> SdrTextObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrTextObj(rTargetModel, *this); +} + +basegfx::B2DPolyPolygon SdrTextObj::TakeXorPoly() const +{ + tools::Polygon aPol(getRectangle()); + if (maGeo.m_nShearAngle) + ShearPoly(aPol, getRectangle().TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) + RotatePoly(aPol, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + basegfx::B2DPolyPolygon aRetval; + aRetval.append(aPol.getB2DPolygon()); + return aRetval; +} + +basegfx::B2DPolyPolygon SdrTextObj::TakeContour() const +{ + basegfx::B2DPolyPolygon aRetval(SdrAttrObj::TakeContour()); + + // and now add the BoundRect of the text, if necessary + if ( GetOutlinerParaObject() && !IsFontwork() && !IsContourTextFrame() ) + { + // using Clone()-Paint() strategy inside TakeContour() leaves a destroyed + // SdrObject as pointer in DrawOutliner. Set *this again in fetching the outliner + // in every case + SdrOutliner& rOutliner=ImpGetDrawOutliner(); + + tools::Rectangle aAnchor2; + tools::Rectangle aR; + TakeTextRect(rOutliner,aR,false,&aAnchor2); + rOutliner.Clear(); + bool bFitToSize(IsFitToSize()); + if (bFitToSize) aR=aAnchor2; + tools::Polygon aPol(aR); + if (maGeo.m_nRotationAngle) RotatePoly(aPol,aR.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + aRetval.append(aPol.getB2DPolygon()); + } + + return aRetval; +} + +void SdrTextObj::RecalcSnapRect() +{ + if (maGeo.m_nRotationAngle || maGeo.m_nShearAngle) + { + maSnapRect = Rect2Poly(getRectangle(), maGeo).GetBoundRect(); + } else { + maSnapRect = getRectangle(); + } +} + +sal_uInt32 SdrTextObj::GetSnapPointCount() const +{ + return 4; +} + +Point SdrTextObj::GetSnapPoint(sal_uInt32 i) const +{ + Point aP; + auto aRectangle = getRectangle(); + switch (i) { + case 0: aP = aRectangle.TopLeft(); break; + case 1: aP = aRectangle.TopRight(); break; + case 2: aP = aRectangle.BottomLeft(); break; + case 3: aP = aRectangle.BottomRight(); break; + default: aP = aRectangle.Center(); break; + } + if (maGeo.m_nShearAngle) + ShearPoint(aP, aRectangle.TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) + RotatePoint(aP, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + return aP; +} + +// Extracted from ImpGetDrawOutliner() +void SdrTextObj::ImpInitDrawOutliner( SdrOutliner& rOutl ) const +{ + rOutl.SetUpdateLayout(false); + OutlinerMode nOutlinerMode = OutlinerMode::OutlineObject; + if ( !IsOutlText() ) + nOutlinerMode = OutlinerMode::TextObject; + rOutl.Init( nOutlinerMode ); + + rOutl.setGlobalScale(100.0, 100.0, 100.0, 100.0); + + EEControlBits nStat=rOutl.GetControlWord(); + nStat &= ~EEControlBits(EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE); + rOutl.SetControlWord(nStat); + Size aMaxSize(100000,100000); + rOutl.SetMinAutoPaperSize(Size()); + rOutl.SetMaxAutoPaperSize(aMaxSize); + rOutl.SetPaperSize(aMaxSize); + rOutl.ClearPolygon(); +} + +SdrOutliner& SdrTextObj::ImpGetDrawOutliner() const +{ + SdrOutliner& rOutl(getSdrModelFromSdrObject().GetDrawOutliner(this)); + + // Code extracted to ImpInitDrawOutliner() + ImpInitDrawOutliner( rOutl ); + + return rOutl; +} + +// Extracted from Paint() +void SdrTextObj::ImpSetupDrawOutlinerForPaint( bool bContourFrame, + SdrOutliner& rOutliner, + tools::Rectangle& rTextRect, + tools::Rectangle& rAnchorRect, + tools::Rectangle& rPaintRect, + Fraction& rFitXCorrection ) const +{ + if (!bContourFrame) + { + // FitToSize can't be used together with ContourFrame for now + if (IsFitToSize() || IsAutoFit()) + { + EEControlBits nStat=rOutliner.GetControlWord(); + nStat|=EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE; + rOutliner.SetControlWord(nStat); + } + } + rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + TakeTextRect(rOutliner, rTextRect, false, &rAnchorRect); + rPaintRect = rTextRect; + + if (bContourFrame) + return; + + // FitToSize can't be used together with ContourFrame for now + if (IsFitToSize()) + { + ImpSetCharStretching(rOutliner,rTextRect.GetSize(),rAnchorRect.GetSize(),rFitXCorrection); + rPaintRect=rAnchorRect; + } + else if (IsAutoFit()) + { + ImpAutoFitText(rOutliner); + } +} + +double SdrTextObj::GetFontScale() const +{ + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + // This eventually calls ImpAutoFitText + UpdateOutlinerFormatting(rOutliner, o3tl::temporary(tools::Rectangle())); + + double fScaleY; + rOutliner.getGlobalScale(o3tl::temporary(double()), fScaleY, o3tl::temporary(double()), o3tl::temporary(double())); + return fScaleY; +} + +double SdrTextObj::GetSpacingScale() const +{ + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + // This eventually calls ImpAutoFitText + UpdateOutlinerFormatting(rOutliner, o3tl::temporary(tools::Rectangle())); + + double fSpacingScaleY; + rOutliner.getGlobalScale(o3tl::temporary(double()), o3tl::temporary(double()), o3tl::temporary(double()), fSpacingScaleY); + return fSpacingScaleY; +} + +void SdrTextObj::ImpAutoFitText( SdrOutliner& rOutliner ) const +{ + const Size aShapeSize=GetSnapRect().GetSize(); + ImpAutoFitText( rOutliner, + Size(aShapeSize.Width()-GetTextLeftDistance()-GetTextRightDistance(), + aShapeSize.Height()-GetTextUpperDistance()-GetTextLowerDistance()), + IsVerticalWriting() ); +} + +void SdrTextObj::ImpAutoFitText(SdrOutliner& rOutliner, const Size& rTextSize, + bool bIsVerticalWriting) const +{ + autoFitTextForCompatibility(rOutliner, rTextSize, bIsVerticalWriting); +} + +void SdrTextObj::autoFitTextForCompatibility(SdrOutliner& rOutliner, const Size& rTextBoxSize, bool bIsVerticalWriting) const +{ + rOutliner.setRoundFontSizeToPt(true); + + const SdrTextFitToSizeTypeItem& rItem = GetObjectItem(SDRATTR_TEXT_FITTOSIZE); + double fMaxScale = rItem.GetMaxScale(); + if (fMaxScale > 0.0) + { + rOutliner.setGlobalScale(fMaxScale, fMaxScale, 100.0, 100.0); + } + else + { + fMaxScale = 100.0; + } + + Size aCurrentTextBoxSize = rOutliner.CalcTextSizeNTP(); + if (aCurrentTextBoxSize.Height() == 0) + return; + + tools::Long nExtendTextBoxBy = -50; + aCurrentTextBoxSize.extendBy(0, nExtendTextBoxBy); + double fCurrentFitFactor = 1.0; + + if (bIsVerticalWriting) + fCurrentFitFactor = double(rTextBoxSize.Width()) / aCurrentTextBoxSize.Width(); + else + fCurrentFitFactor = double(rTextBoxSize.Height()) / aCurrentTextBoxSize.Height(); + + double fInitialFontScaleY = 0.0; + double fInitialSpacing = 0.0; + rOutliner.getGlobalScale(o3tl::temporary(double()), fInitialFontScaleY, o3tl::temporary(double()), fInitialSpacing); + + if (fCurrentFitFactor >= 1.0 && fInitialFontScaleY >= 100.0 && fInitialSpacing >= 100.0) + return; + + sal_Int32 nFontHeight = GetObjectItemSet().Get(EE_CHAR_FONTHEIGHT).GetHeight(); + + double fFontHeightPt = o3tl::convert(double(nFontHeight), o3tl::Length::mm100, o3tl::Length::pt); + double fMinY = 0.0; + double fMaxY = fMaxScale; + + double fBestFontScale = 0.0; + double fBestSpacing = 100.0; + double fBestFitFactor = fCurrentFitFactor; + + if (fCurrentFitFactor >= 1.0) + { + fMinY = fInitialFontScaleY; + fBestFontScale = fInitialFontScaleY; + fBestSpacing = fInitialSpacing; + fBestFitFactor = fCurrentFitFactor; + } + else + { + fMaxY = std::min(fInitialFontScaleY, fMaxScale); + } + + double fInTheMidle = 0.5; + + int iteration = 0; + double fFitFactorTarget = 1.00; + + while (iteration < 10) + { + iteration++; + double fScaleY = fMinY + (fMaxY - fMinY) * fInTheMidle; + + double fScaledFontHeight = fFontHeightPt * (fScaleY / 100.0); + double fRoundedScaledFontHeight = std::floor(fScaledFontHeight * 10.0) / 10.0; + double fCurrentFontScale = (fRoundedScaledFontHeight / fFontHeightPt) * 100.0; + + fCurrentFitFactor = 0.0; // reset fit factor; + + for (double fCurrentSpacing : {100.0, 90.0, 80.0}) + { + if (fCurrentFitFactor >= fFitFactorTarget) + continue; + + rOutliner.setGlobalScale(fCurrentFontScale, fCurrentFontScale, 100.0, fCurrentSpacing); + + aCurrentTextBoxSize = rOutliner.CalcTextSizeNTP(); + aCurrentTextBoxSize.extendBy(0, nExtendTextBoxBy); + if (bIsVerticalWriting) + fCurrentFitFactor = double(rTextBoxSize.Width()) / aCurrentTextBoxSize.Width(); + else + fCurrentFitFactor = double(rTextBoxSize.Height()) / aCurrentTextBoxSize.Height(); + + + if (fCurrentSpacing == 100.0) + { + if (fCurrentFitFactor > fFitFactorTarget) + fMinY = fCurrentFontScale; + else + fMaxY = fCurrentFontScale; + } + + if ((fBestFitFactor < fFitFactorTarget && fCurrentFitFactor > fBestFitFactor) + || (fCurrentFitFactor >= fFitFactorTarget && fCurrentFitFactor < fBestFitFactor)) + { + fBestFontScale = fCurrentFontScale; + fBestSpacing = fCurrentSpacing; + fBestFitFactor = fCurrentFitFactor; + } + } + } + rOutliner.setGlobalScale(fBestFontScale, fBestFontScale, 100.0, fBestSpacing); +} + +void SdrTextObj::SetupOutlinerFormatting( SdrOutliner& rOutl, tools::Rectangle& rPaintRect ) const +{ + ImpInitDrawOutliner( rOutl ); + UpdateOutlinerFormatting( rOutl, rPaintRect ); +} + +void SdrTextObj::UpdateOutlinerFormatting( SdrOutliner& rOutl, tools::Rectangle& rPaintRect ) const +{ + tools::Rectangle aTextRect; + tools::Rectangle aAnchorRect; + Fraction aFitXCorrection(1,1); + + const bool bContourFrame(IsContourTextFrame()); + const MapMode aMapMode(getSdrModelFromSdrObject().GetScaleUnit()); + + rOutl.SetRefMapMode(aMapMode); + ImpSetupDrawOutlinerForPaint( + bContourFrame, + rOutl, + aTextRect, + aAnchorRect, + rPaintRect, + aFitXCorrection); +} + + +OutlinerParaObject* SdrTextObj::GetOutlinerParaObject() const +{ + SdrText* pText = getActiveText(); + if( pText ) + return pText->GetOutlinerParaObject(); + else + return nullptr; +} + +void SdrTextObj::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject) +{ + NbcSetOutlinerParaObjectForText( std::move(pTextObject), getActiveText() ); +} + +namespace +{ + bool IsAutoGrow(const SdrTextObj& rObj) + { + bool bAutoGrow = rObj.IsAutoGrowHeight() || rObj.IsAutoGrowWidth(); + return bAutoGrow && !utl::ConfigManager::IsFuzzing(); + } +} + +void SdrTextObj::NbcSetOutlinerParaObjectForText( std::optional<OutlinerParaObject> pTextObject, SdrText* pText ) +{ + if( pText ) + pText->SetOutlinerParaObject( std::move(pTextObject) ); + + if (pText && pText->GetOutlinerParaObject()) + { + SvxWritingModeItem aWritingMode(pText->GetOutlinerParaObject()->IsEffectivelyVertical() && pText->GetOutlinerParaObject()->IsTopToBottom() + ? css::text::WritingMode_TB_RL + : css::text::WritingMode_LR_TB, + SDRATTR_TEXTDIRECTION); + GetProperties().SetObjectItemDirect(aWritingMode); + } + + SetTextSizeDirty(); + if (IsTextFrame() && IsAutoGrow(*this)) + { // adapt text frame! + NbcAdjustTextFrameWidthAndHeight(); + } + if (!IsTextFrame()) + { + // the SnapRect keeps its size + SetBoundAndSnapRectsDirty(true); + } + + // always invalidate BoundRect on change + SetBoundRectDirty(); + ActionChanged(); + + ImpSetTextStyleSheetListeners(); +} + +void SdrTextObj::NbcReformatText() +{ + SdrText* pText = getActiveText(); + if( !(pText && pText->GetOutlinerParaObject()) ) + return; + + pText->ReformatText(); + if (mbTextFrame) + { + NbcAdjustTextFrameWidthAndHeight(); + } + else + { + // the SnapRect keeps its size + SetBoundRectDirty(); + SetBoundAndSnapRectsDirty(/*bNotMyself*/true); + } + SetTextSizeDirty(); + ActionChanged(); + // i22396 + // Necessary here since we have no compare operator at the outliner + // para object which may detect changes regarding the combination + // of outliner para data and configuration (e.g., change of + // formatting of text numerals) + GetViewContact().flushViewObjectContacts(false); +} + +std::unique_ptr<SdrObjGeoData> SdrTextObj::NewGeoData() const +{ + return std::make_unique<SdrTextObjGeoData>(); +} + +void SdrTextObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + SdrAttrObj::SaveGeoData(rGeo); + SdrTextObjGeoData& rTGeo=static_cast<SdrTextObjGeoData&>(rGeo); + rTGeo.maRect = getRectangle(); + rTGeo.maGeo = maGeo; +} + +void SdrTextObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ // RectsDirty is called by SdrObject + SdrAttrObj::RestoreGeoData(rGeo); + const SdrTextObjGeoData& rTGeo=static_cast<const SdrTextObjGeoData&>(rGeo); + NbcSetLogicRect(rTGeo.maRect); + maGeo = rTGeo.maGeo; + SetTextSizeDirty(); +} + +drawing::TextFitToSizeType SdrTextObj::GetFitToSize() const +{ + drawing::TextFitToSizeType eType = drawing::TextFitToSizeType_NONE; + + if(!IsAutoGrowWidth()) + eType = GetObjectItem(SDRATTR_TEXT_FITTOSIZE).GetValue(); + + return eType; +} + +const tools::Rectangle& SdrTextObj::GetGeoRect() const +{ + return getRectangle(); +} + +void SdrTextObj::ForceOutlinerParaObject() +{ + SdrText* pText = getActiveText(); + if( pText && (pText->GetOutlinerParaObject() == nullptr) ) + { + OutlinerMode nOutlMode = OutlinerMode::TextObject; + if( IsTextFrame() && meTextKind == SdrObjKind::OutlineText ) + nOutlMode = OutlinerMode::OutlineObject; + + pText->ForceOutlinerParaObject( nOutlMode ); + } +} + +TextChain *SdrTextObj::GetTextChain() const +{ + //if (!IsChainable()) + // return NULL; + + return getSdrModelFromSdrObject().GetTextChain(); +} + +bool SdrTextObj::IsVerticalWriting() const +{ + if(mpEditingOutliner) + { + return mpEditingOutliner->IsVertical(); + } + + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if(pOutlinerParaObject) + { + return pOutlinerParaObject->IsEffectivelyVertical(); + } + + return false; +} + +void SdrTextObj::SetVerticalWriting(bool bVertical) +{ + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + + if( !pOutlinerParaObject && bVertical ) + { + // we only need to force an outliner para object if the default of + // horizontal text is changed + ForceOutlinerParaObject(); + pOutlinerParaObject = GetOutlinerParaObject(); + } + + if (!pOutlinerParaObject || + (pOutlinerParaObject->IsEffectivelyVertical() == bVertical)) + return; + + // get item settings + const SfxItemSet& rSet = GetObjectItemSet(); + bool bAutoGrowWidth = rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue(); + bool bAutoGrowHeight = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + + // Also exchange hor/ver adjust items + SdrTextHorzAdjust eHorz = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue(); + SdrTextVertAdjust eVert = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue(); + + // rescue object size + tools::Rectangle aObjectRect = GetSnapRect(); + + // prepare ItemSet to set exchanged width and height items + SfxItemSetFixed<SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT, + // Expanded item ranges to also support hor and ver adjust. + SDRATTR_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST, + SDRATTR_TEXT_AUTOGROWWIDTH, SDRATTR_TEXT_HORZADJUST> aNewSet(*rSet.GetPool()); + + aNewSet.Put(rSet); + aNewSet.Put(makeSdrTextAutoGrowWidthItem(bAutoGrowHeight)); + aNewSet.Put(makeSdrTextAutoGrowHeightItem(bAutoGrowWidth)); + + // Exchange horz and vert adjusts + switch (eVert) + { + case SDRTEXTVERTADJUST_TOP: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); break; + case SDRTEXTVERTADJUST_CENTER: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_CENTER)); break; + case SDRTEXTVERTADJUST_BOTTOM: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); break; + case SDRTEXTVERTADJUST_BLOCK: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_BLOCK)); break; + } + switch (eHorz) + { + case SDRTEXTHORZADJUST_LEFT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BOTTOM)); break; + case SDRTEXTHORZADJUST_CENTER: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_CENTER)); break; + case SDRTEXTHORZADJUST_RIGHT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP)); break; + case SDRTEXTHORZADJUST_BLOCK: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BLOCK)); break; + } + + SetObjectItemSet(aNewSet); + + pOutlinerParaObject = GetOutlinerParaObject(); + if (pOutlinerParaObject) + { + // set ParaObject orientation accordingly + pOutlinerParaObject->SetVertical(bVertical); + } + + // restore object size + SetSnapRect(aObjectRect); +} + +bool SdrTextObj::IsTopToBottom() const +{ + if (mpEditingOutliner) + return mpEditingOutliner->IsTopToBottom(); + + if (OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject()) + return pOutlinerParaObject->IsTopToBottom(); + + return false; +} + +// transformation interface for StarOfficeAPI. This implements support for +// homogeneous 3x3 matrices containing the transformation of the SdrObject. At the +// moment it contains a shearX, rotation and translation, but for setting all linear +// transforms like Scale, ShearX, ShearY, Rotate and Translate are supported. + + +// gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon +// with the base geometry and returns TRUE. Otherwise it returns FALSE. +bool SdrTextObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const +{ + // get turn and shear + double fRotate = toRadians(maGeo.m_nRotationAngle); + double fShearX = toRadians(maGeo.m_nShearAngle); + + // get aRect, this is the unrotated snaprect + tools::Rectangle aRectangle(getRectangle()); + + // fill other values + basegfx::B2DTuple aScale(aRectangle.GetWidth(), aRectangle.GetHeight()); + basegfx::B2DTuple aTranslate(aRectangle.Left(), aRectangle.Top()); + + // position maybe relative to anchorpos, convert + if( getSdrModelFromSdrObject().IsWriter() ) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build matrix + rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, + basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX), + basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate, + aTranslate); + + return false; +} + +// sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix. +// If it's an SdrPathObj it will use the provided geometry information. The Polygon has +// to use (0,0) as upper left and will be scaled to the given size in the matrix. +void SdrTextObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/) +{ + // break up matrix + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate(0.0); + double fShearX(0.0); + rMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + // flip? + bool bFlipX = aScale.getX() < 0.0, + bFlipY = aScale.getY() < 0.0; + if (bFlipX) + { + aScale.setX(fabs(aScale.getX())); + } + if (bFlipY) + { + aScale.setY(fabs(aScale.getY())); + } + + // reset object shear and rotations + maGeo.m_nRotationAngle = 0_deg100; + maGeo.RecalcSinCos(); + maGeo.m_nShearAngle = 0_deg100; + maGeo.RecalcTan(); + + // if anchor is used, make position relative to it + if( getSdrModelFromSdrObject().IsWriter() ) + { + if(GetAnchorPos().X() || GetAnchorPos().Y()) + { + aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y()); + } + } + + // build and set BaseRect (use scale) + Size aSize(FRound(aScale.getX()), FRound(aScale.getY())); + tools::Rectangle aBaseRect(Point(), aSize); + SetSnapRect(aBaseRect); + + // flip? + if (bFlipX) + { + Mirror(Point(), Point(0, 1)); + } + if (bFlipY) + { + Mirror(Point(), Point(1, 0)); + } + + // shear? + if(!basegfx::fTools::equalZero(fShearX)) + { + GeoStat aGeoStat; + aGeoStat.m_nShearAngle = Degree100(FRound(basegfx::rad2deg<100>(atan(fShearX)))); + aGeoStat.RecalcTan(); + Shear(Point(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false); + } + + // rotation? + if(!basegfx::fTools::equalZero(fRotate)) + { + GeoStat aGeoStat; + + // #i78696# + // fRotate is matematically correct, but aGeoStat.nRotationAngle is + // mirrored -> mirror value here + aGeoStat.m_nRotationAngle = NormAngle36000(Degree100(FRound(-basegfx::rad2deg<100>(fRotate)))); + aGeoStat.RecalcSinCos(); + Rotate(Point(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle); + } + + // translate? + if(!aTranslate.equalZero()) + { + Move(Size(FRound(aTranslate.getX()), FRound(aTranslate.getY()))); + } +} + +bool SdrTextObj::IsReallyEdited() const +{ + return mpEditingOutliner && mpEditingOutliner->IsModified(); +} + +// moved inlines here form hxx + +tools::Long SdrTextObj::GetEckenradius() const +{ + return GetObjectItemSet().Get(SDRATTR_CORNER_RADIUS).GetValue(); +} + +tools::Long SdrTextObj::GetMinTextFrameHeight() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_MINFRAMEHEIGHT).GetValue(); +} + +tools::Long SdrTextObj::GetMaxTextFrameHeight() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_MAXFRAMEHEIGHT).GetValue(); +} + +tools::Long SdrTextObj::GetMinTextFrameWidth() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_MINFRAMEWIDTH).GetValue(); +} + +tools::Long SdrTextObj::GetMaxTextFrameWidth() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_MAXFRAMEWIDTH).GetValue(); +} + +bool SdrTextObj::IsFontwork() const +{ + return !mbTextFrame // Default is FALSE + && GetObjectItemSet().Get(XATTR_FORMTXTSTYLE).GetValue() != XFormTextStyle::NONE; +} + +bool SdrTextObj::IsHideContour() const +{ + return !mbTextFrame // Default is: no, don't HideContour; HideContour not together with TextFrames + && GetObjectItemSet().Get(XATTR_FORMTXTHIDEFORM).GetValue(); +} + +bool SdrTextObj::IsContourTextFrame() const +{ + return !mbTextFrame // ContourFrame not together with normal TextFrames + && GetObjectItemSet().Get(SDRATTR_TEXT_CONTOURFRAME).GetValue(); +} + +tools::Long SdrTextObj::GetTextLeftDistance() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_LEFTDIST).GetValue(); +} + +tools::Long SdrTextObj::GetTextRightDistance() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_RIGHTDIST).GetValue(); +} + +tools::Long SdrTextObj::GetTextUpperDistance() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_UPPERDIST).GetValue(); +} + +tools::Long SdrTextObj::GetTextLowerDistance() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_LOWERDIST).GetValue(); +} + +SdrTextAniKind SdrTextObj::GetTextAniKind() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_ANIKIND).GetValue(); +} + +SdrTextAniDirection SdrTextObj::GetTextAniDirection() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); +} + +bool SdrTextObj::HasTextColumnsNumber() const +{ + return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_NUMBER); +} + +sal_Int16 SdrTextObj::GetTextColumnsNumber() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_NUMBER).GetValue(); +} + +void SdrTextObj::SetTextColumnsNumber(sal_Int16 nColumns) +{ + SetObjectItem(SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, nColumns)); +} + +bool SdrTextObj::HasTextColumnsSpacing() const +{ + return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_SPACING); +} + +sal_Int32 SdrTextObj::GetTextColumnsSpacing() const +{ + return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_SPACING).GetValue(); +} + +void SdrTextObj::SetTextColumnsSpacing(sal_Int32 nSpacing) +{ + SetObjectItem(SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, nSpacing)); +} + +// Get necessary data for text scroll animation. ATM base it on a Text-Metafile and a +// painting rectangle. Rotation is excluded from the returned values. +GDIMetaFile* SdrTextObj::GetTextScrollMetaFileAndRectangle( + tools::Rectangle& rScrollRectangle, tools::Rectangle& rPaintRectangle) +{ + GDIMetaFile* pRetval = nullptr; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + tools::Rectangle aTextRect; + tools::Rectangle aAnchorRect; + tools::Rectangle aPaintRect; + Fraction aFitXCorrection(1,1); + bool bContourFrame(IsContourTextFrame()); + + // get outliner set up. To avoid getting a somehow rotated MetaFile, + // temporarily disable object rotation. + Degree100 nAngle(maGeo.m_nRotationAngle); + maGeo.m_nRotationAngle = 0_deg100; + ImpSetupDrawOutlinerForPaint( bContourFrame, rOutliner, aTextRect, aAnchorRect, aPaintRect, aFitXCorrection ); + maGeo.m_nRotationAngle = nAngle; + + tools::Rectangle aScrollFrameRect(aPaintRect); + const SfxItemSet& rSet = GetObjectItemSet(); + SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + + if(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection) + { + aScrollFrameRect.SetLeft( aAnchorRect.Left() ); + aScrollFrameRect.SetRight( aAnchorRect.Right() ); + } + + if(SdrTextAniDirection::Up == eDirection || SdrTextAniDirection::Down == eDirection) + { + aScrollFrameRect.SetTop( aAnchorRect.Top() ); + aScrollFrameRect.SetBottom( aAnchorRect.Bottom() ); + } + + // create the MetaFile + pRetval = new GDIMetaFile; + ScopedVclPtrInstance< VirtualDevice > pBlackHole; + pBlackHole->EnableOutput(false); + pRetval->Record(pBlackHole); + Point aPaintPos = aPaintRect.TopLeft(); + + rOutliner.Draw(*pBlackHole, aPaintPos); + + pRetval->Stop(); + pRetval->WindStart(); + + // return PaintRectanglePixel and pRetval; + rScrollRectangle = aScrollFrameRect; + rPaintRectangle = aPaintRect; + + return pRetval; +} + +// Access to TextAnimationAllowed flag +bool SdrTextObj::IsAutoFit() const +{ + return GetFitToSize() == drawing::TextFitToSizeType_AUTOFIT; +} + +bool SdrTextObj::IsFitToSize() const +{ + const drawing::TextFitToSizeType eFit = GetFitToSize(); + return (eFit == drawing::TextFitToSizeType_PROPORTIONAL + || eFit == drawing::TextFitToSizeType_ALLLINES); +} + +void SdrTextObj::SetTextAnimationAllowed(bool bNew) +{ + if(mbTextAnimationAllowed != bNew) + { + mbTextAnimationAllowed = bNew; + ActionChanged(); + } +} + +/** called from the SdrObjEditView during text edit when the status of the edit outliner changes */ +void SdrTextObj::onEditOutlinerStatusEvent( EditStatus* pEditStatus ) +{ + const EditStatusFlags nStat = pEditStatus->GetStatusWord(); + const bool bGrowX = bool(nStat & EditStatusFlags::TEXTWIDTHCHANGED); + const bool bGrowY = bool(nStat & EditStatusFlags::TextHeightChanged); + if(!(mbTextFrame && (bGrowX || bGrowY))) + return; + + if ((bGrowX && IsAutoGrowWidth()) || (bGrowY && IsAutoGrowHeight())) + { + AdjustTextFrameWidthAndHeight(); + } + else if ( (IsAutoFit() || IsFitToSize()) && !mbInDownScale) + { + assert(mpEditingOutliner); + mbInDownScale = true; + + // sucks that we cannot disable paints via + // mpEditingOutliner->SetUpdateMode(FALSE) - but EditEngine skips + // formatting as well, then. + ImpAutoFitText(*mpEditingOutliner); + mbInDownScale = false; + } +} + +/* Begin chaining code */ + +// XXX: Make it a method somewhere? +static SdrObject *ImpGetObjByName(SdrObjList const *pObjList, std::u16string_view aObjName) +{ + // scan the whole list + for (const rtl::Reference<SdrObject>& pCurObj : *pObjList) + if (pCurObj->GetName() == aObjName) + return pCurObj.get(); + // not found + return nullptr; +} + +// XXX: Make it a (private) method of SdrTextObj +static void ImpUpdateChainLinks(SdrTextObj *pTextObj, std::u16string_view aNextLinkName) +{ + // XXX: Current implementation constraints text boxes to be on the same page + + // No next link + if (aNextLinkName.empty()) { + pTextObj->SetNextLinkInChain(nullptr); + return; + } + + SdrPage *pPage(pTextObj->getSdrPageFromSdrObject()); + assert(pPage); + SdrTextObj *pNextTextObj = DynCastSdrTextObj + (ImpGetObjByName(pPage, aNextLinkName)); + if (!pNextTextObj) { + SAL_INFO("svx.chaining", "[CHAINING] Can't find object as next link."); + return; + } + + pTextObj->SetNextLinkInChain(pNextTextObj); +} + +bool SdrTextObj::IsChainable() const +{ + // Read it as item + const SfxItemSet& rSet = GetObjectItemSet(); + OUString aNextLinkName = rSet.Get(SDRATTR_TEXT_CHAINNEXTNAME).GetValue(); + + // Update links if any inconsistency is found + bool bNextLinkUnsetYet = !aNextLinkName.isEmpty() && !mpNextInChain; + bool bInconsistentNextLink = mpNextInChain && mpNextInChain->GetName() != aNextLinkName; + // if the link is not set despite there should be one OR if it has changed + if (bNextLinkUnsetYet || bInconsistentNextLink) { + ImpUpdateChainLinks(const_cast<SdrTextObj *>(this), aNextLinkName); + } + + return !aNextLinkName.isEmpty(); // XXX: Should we also check for GetNilChainingEvent? (see old code below) + +/* + // Check that no overflow is going on + if (!GetTextChain() || GetTextChain()->GetNilChainingEvent(this)) + return false; +*/ +} + +void SdrTextObj::onChainingEvent() +{ + if (!mpEditingOutliner) + return; + + // Outliner for text transfer + SdrOutliner &aDrawOutliner = ImpGetDrawOutliner(); + + EditingTextChainFlow aTxtChainFlow(this); + aTxtChainFlow.CheckForFlowEvents(mpEditingOutliner); + + if (aTxtChainFlow.IsOverflow()) { + SAL_INFO("svx.chaining", "[CHAINING] Overflow going on"); + // One outliner is for non-overflowing text, the other for overflowing text + // We remove text directly from the editing outliner + aTxtChainFlow.ExecuteOverflow(mpEditingOutliner, &aDrawOutliner); + } else if (aTxtChainFlow.IsUnderflow()) { + SAL_INFO("svx.chaining", "[CHAINING] Underflow going on"); + // underflow-induced overflow + aTxtChainFlow.ExecuteUnderflow(&aDrawOutliner); + bool bIsOverflowFromUnderflow = aTxtChainFlow.IsOverflow(); + // handle overflow + if (bIsOverflowFromUnderflow) { + SAL_INFO("svx.chaining", "[CHAINING] Overflow going on (underflow induced)"); + // prevents infinite loops when setting text for editing outliner + aTxtChainFlow.ExecuteOverflow(&aDrawOutliner, &aDrawOutliner); + } + } +} + +SdrTextObj* SdrTextObj::GetNextLinkInChain() const +{ + /* + if (GetTextChain()) + return GetTextChain()->GetNextLink(this); + + return NULL; + */ + + return mpNextInChain; +} + +void SdrTextObj::SetNextLinkInChain(SdrTextObj *pNextObj) +{ + // Basically a doubly linked list implementation + + SdrTextObj *pOldNextObj = mpNextInChain; + + // Replace next link + mpNextInChain = pNextObj; + // Deal with old next link's prev link + if (pOldNextObj) { + pOldNextObj->mpPrevInChain = nullptr; + } + + // Deal with new next link's prev link + if (mpNextInChain) { + // If there is a prev already at all and this is not already the current object + if (mpNextInChain->mpPrevInChain && + mpNextInChain->mpPrevInChain != this) + mpNextInChain->mpPrevInChain->mpNextInChain = nullptr; + mpNextInChain->mpPrevInChain = this; + } + + // TODO: Introduce check for circular chains + +} + +SdrTextObj* SdrTextObj::GetPrevLinkInChain() const +{ + /* + if (GetTextChain()) + return GetTextChain()->GetPrevLink(this); + + return NULL; + */ + + return mpPrevInChain; +} + +bool SdrTextObj::GetPreventChainable() const +{ + // Prevent chaining it 1) during dragging && 2) when we are editing next link + return mbIsUnchainableClone || (GetNextLinkInChain() && GetNextLinkInChain()->IsInEditMode()); +} + +rtl::Reference<SdrObject> SdrTextObj::getFullDragClone() const +{ + rtl::Reference<SdrObject> pClone = SdrAttrObj::getFullDragClone(); + SdrTextObj *pTextObjClone = DynCastSdrTextObj(pClone.get()); + if (pTextObjClone != nullptr) { + // Avoid transferring of text for chainable object during dragging + pTextObjClone->mbIsUnchainableClone = true; + } + + return pClone; + } + +/* End chaining code */ + +/** returns the currently active text. */ +SdrText* SdrTextObj::getActiveText() const +{ + if( !mxText ) + return getText( 0 ); + else + return mxText.get(); +} + +/** returns the nth available text. */ +SdrText* SdrTextObj::getText( sal_Int32 nIndex ) const +{ + if( nIndex == 0 ) + { + if( !mxText ) + const_cast< SdrTextObj* >(this)->mxText = new SdrText( *const_cast< SdrTextObj* >(this) ); + return mxText.get(); + } + else + { + return nullptr; + } +} + +/** returns the number of texts available for this object. */ +sal_Int32 SdrTextObj::getTextCount() const +{ + return 1; +} + +/** changes the current active text */ +void SdrTextObj::setActiveText( sal_Int32 /*nIndex*/ ) +{ +} + +/** returns the index of the text that contains the given point or -1 */ +sal_Int32 SdrTextObj::CheckTextHit(const Point& /*rPnt*/) const +{ + return 0; +} + +void SdrTextObj::SetObjectItemNoBroadcast(const SfxPoolItem& rItem) +{ + static_cast< sdr::properties::TextProperties& >(GetProperties()).SetObjectItemNoBroadcast(rItem); +} + + +// The concept of the text object: +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// Attributes/Variations: +// - bool text frame / graphics object with caption +// - bool FontWork (if it is not a text frame and not a ContourTextFrame) +// - bool ContourTextFrame (if it is not a text frame and not Fontwork) +// - long rotation angle (if it is not FontWork) +// - long text frame margins (if it is not FontWork) +// - bool FitToSize (if it is not FontWork) +// - bool AutoGrowingWidth/Height (if it is not FitToSize and not FontWork) +// - long Min/MaxFrameWidth/Height (if AutoGrowingWidth/Height) +// - enum horizontal text anchoring left,center,right,justify/block,Stretch(ni) +// - enum vertical text anchoring top, middle, bottom, block, stretch(ni) +// - enum ticker text (if it is not FontWork) + +// Every derived object is either a text frame (mbTextFrame=true) +// or a drawing object with a caption (mbTextFrame=false). + +// Default anchoring for text frames: +// SDRTEXTHORZADJUST_BLOCK, SDRTEXTVERTADJUST_TOP +// = static Pool defaults +// Default anchoring for drawing objects with a caption: +// SDRTEXTHORZADJUST_CENTER, SDRTEXTVERTADJUST_CENTER +// via "hard" attribution of SdrAttrObj + +// Every object derived from SdrTextObj must return an "UnrotatedSnapRect" +// (->TakeUnrotatedSnapRect()) (the reference point for the rotation is the top +// left of the rectangle (maGeo.nRotationAngle)) which is the basis for anchoring +// text. We then subtract the text frame margins from this rectangle, as a re- +// sult we get the anchoring area (->TakeTextAnchorRect()). Within this area, we +// calculate the anchoring point and the painting area, depending on the hori- +// zontal and vertical adjustment of the text (SdrTextVertAdjust, +// SdrTextHorzAdjust). +// In the case of drawing objects with a caption the painting area might well +// be larger than the anchoring area, for text frames on the other hand, it is +// always of the same or a smaller size (except when there are negative text +// frame margins). + +// FitToSize takes priority over text anchoring and AutoGrowHeight/Width. When +// FitToSize is turned on, the painting area is always equal to the anchoring +// area. Additionally, FitToSize doesn't allow automatic line breaks. + +// ContourTextFrame: +// - long rotation angle +// - long text frame margins (maybe later) +// - bool FitToSize (maybe later) +// - bool AutoGrowingWidth/Height (maybe much later) +// - long Min/MaxFrameWidth/Height (maybe much later) +// - enum horizontal text anchoring (maybe later, for now: left, centered) +// - enum vertical text anchoring (maybe later, for now: top) +// - enum ticker text (maybe later, maybe even with correct clipping) + +// When making changes, check these: +// - Paint +// - HitTest +// - ConvertToPoly +// - Edit +// - Printing, Saving, Painting in neighboring View while editing +// - ModelChanged (e. g. through a neighboring View or rulers) while editing +// - FillColorChanged while editing +// - and many more... + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx new file mode 100644 index 0000000000..1bad74cebb --- /dev/null +++ b/svx/source/svdraw/svdotextdecomposition.cxx @@ -0,0 +1,1838 @@ +/* -*- 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/compatflags.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <svx/sdasitm.hxx> +#include <textchain.hxx> +#include <textchainflow.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtayitm.hxx> +#include <svx/sdtaiitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xbtmpit.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <sdr/primitive2d/sdrtextprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <basegfx/range/b2drange.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/editstat.hxx> +#include <editeng/smallcaps.hxx> +#include <tools/helpers.hxx> +#include <svl/itemset.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <basegfx/color/bcolor.hxx> +#include <vcl/svapp.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/flditem.hxx> +#include <editeng/adjustitem.hxx> +#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svx/unoapi.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <editeng/outlobj.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +// helpers + +namespace +{ + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive(const DrawPortionInfo& rInfo, const OUString& rText, + const drawinglayer::attribute::FontAttribute& rFontAttribute, + const std::vector<double>& rDXArray, + const basegfx::B2DHomMatrix& rNewTransform); + + class impTextBreakupHandler + { + private: + drawinglayer::primitive2d::Primitive2DContainer maTextPortionPrimitives; + drawinglayer::primitive2d::Primitive2DContainer maLinePrimitives; + drawinglayer::primitive2d::Primitive2DContainer maParagraphPrimitives; + + SdrOutliner& mrOutliner; + basegfx::B2DHomMatrix maNewTransformA; + basegfx::B2DHomMatrix maNewTransformB; + + // the visible area for contour text decomposition + basegfx::B2DVector maScale; + + // ClipRange for BlockText decomposition; only text portions completely + // inside are to be accepted, so this is different from geometric clipping + // (which would allow e.g. upper parts of portions to remain). Only used for + // BlockText (see there) + basegfx::B2DRange maClipRange; + + DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo*, void); + DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo*, void); + DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo*, void); + + DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo*, void); + DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo*, void); + DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo*, void); + + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo); + void impFlushTextPortionPrimitivesToLinePrimitives(); + void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara); + void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo); + void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo); + + public: + explicit impTextBreakupHandler(SdrOutliner& rOutliner) + : mrOutliner(rOutliner) + { + } + + void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale) + { + maScale = rScale; + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + void decomposeBlockTextPrimitive( + const basegfx::B2DHomMatrix& rNewTransformA, + const basegfx::B2DHomMatrix& rNewTransformB, + const basegfx::B2DRange& rClipRange) + { + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + maClipRange = rClipRange; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB) + { + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence(); + + void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo); + }; + + class DoCapitalsDrawPortionInfo : public SvxDoCapitals + { + private: + impTextBreakupHandler& m_rHandler; + const DrawPortionInfo& m_rInfo; + SvxFont m_aFont; + public: + DoCapitalsDrawPortionInfo(impTextBreakupHandler& rHandler, const DrawPortionInfo& rInfo) + : SvxDoCapitals(rInfo.maText, rInfo.mnTextStart, rInfo.mnTextLen) + , m_rHandler(rHandler) + , m_rInfo(rInfo) + , m_aFont(rInfo.mrFont) + { + assert(!m_rInfo.mpDXArray.empty()); + + /* turn all these off as they are handled outside subportions for the whole portion */ + m_aFont.SetTransparent(false); + m_aFont.SetUnderline(LINESTYLE_NONE); + m_aFont.SetOverline(LINESTYLE_NONE); + m_aFont.SetStrikeout(STRIKEOUT_NONE); + + m_aFont.SetCaseMap(SvxCaseMap::NotMapped); /* otherwise this would call itself */ + } + virtual void Do( const OUString &rSpanTxt, const sal_Int32 nSpanIdx, + const sal_Int32 nSpanLen, const bool bUpper ) override + { + sal_uInt8 nProp(0); + if (!bUpper) + { + nProp = m_aFont.GetPropr(); + m_aFont.SetProprRel(SMALL_CAPS_PERCENTAGE); + } + + sal_Int32 nStartOffset = nSpanIdx - nIdx; + sal_Int32 nStartX = nStartOffset ? m_rInfo.mpDXArray[nStartOffset - 1] : 0; + + Point aStartPos(m_rInfo.mrStartPos.X() + nStartX, m_rInfo.mrStartPos.Y()); + + std::vector<sal_Int32> aDXArray; + aDXArray.reserve(nSpanLen); + for (sal_Int32 i = 0; i < nSpanLen; ++i) + aDXArray.push_back(m_rInfo.mpDXArray[nStartOffset + i] - nStartX); + + auto aKashidaArray = !m_rInfo.mpKashidaArray.empty() ? + std::span<const sal_Bool>(m_rInfo.mpKashidaArray.data() + nStartOffset, nSpanLen) : + std::span<const sal_Bool>(); + + DrawPortionInfo aInfo(aStartPos, rSpanTxt, + nSpanIdx, nSpanLen, + m_aFont, m_rInfo.mnPara, + aDXArray, aKashidaArray, + nullptr, /* no spelling in subportion, handled outside */ + nullptr, /* no field in subportion, handled outside */ + m_rInfo.mpLocale, m_rInfo.maOverlineColor, m_rInfo.maTextLineColor, + m_rInfo.mnBiDiLevel, false, 0, false, false, false); + + m_rHandler.impCreateTextPortionPrimitive(aInfo); + + if (!bUpper) + m_aFont.SetPropr(nProp); + } + }; + + void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo) + { + if(rInfo.maText.isEmpty() || !rInfo.mnTextLen) + return; + + basegfx::B2DVector aFontScaling; + drawinglayer::attribute::FontAttribute aFontAttribute( + drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontScaling, + rInfo.mrFont, + rInfo.IsRTL(), + false)); + basegfx::B2DHomMatrix aNewTransform; + + // add font scale to new transform + aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY()); + + // look for proportional font scaling, if necessary, scale accordingly + sal_Int8 nPropr(rInfo.mrFont.GetPropr()); + const double fPropFontFactor(nPropr / 100.0); + if (100 != nPropr) + aNewTransform.scale(fPropFontFactor, fPropFontFactor); + + // apply font rotate + if(rInfo.mrFont.GetOrientation()) + { + aNewTransform.rotate(-toRadians(rInfo.mrFont.GetOrientation())); + } + + // look for escapement, if necessary, translate accordingly + if(rInfo.mrFont.GetEscapement()) + { + sal_Int16 nEsc(rInfo.mrFont.GetEscapement()); + + if(DFLT_ESC_AUTO_SUPER == nEsc) + { + nEsc = .8 * (100 - nPropr); + assert (nEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???"); + nEsc = DFLT_ESC_SUPER; + } + else if(DFLT_ESC_AUTO_SUB == nEsc) + { + nEsc = .2 * -(100 - nPropr); + assert (nEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???"); + nEsc = -20; + } + + if(nEsc > MAX_ESC_POS) + { + nEsc = MAX_ESC_POS; + } + else if(nEsc < -MAX_ESC_POS) + { + nEsc = -MAX_ESC_POS; + } + + const double fEscapement(nEsc / -100.0); + aNewTransform.translate(0.0, fEscapement * aFontScaling.getY()); + } + + // apply transformA + aNewTransform *= maNewTransformA; + + // apply local offset + aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()); + + // also apply embedding object's transform + aNewTransform *= maNewTransformB; + + // prepare DXArray content. To make it independent from font size (and such from + // the text transformation), scale it to unit coordinates + ::std::vector< double > aDXArray; + + if (!rInfo.mpDXArray.empty()) + { + aDXArray.reserve(rInfo.mnTextLen); + for(sal_Int32 a=0; a < rInfo.mnTextLen; a++) + { + aDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a])); + } + } + + OUString caseMappedText = rInfo.mrFont.CalcCaseMap(rInfo.maText); + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive(buildTextPortionPrimitive(rInfo, caseMappedText, + aFontAttribute, + aDXArray, aNewTransform)); + + bool bSmallCaps = rInfo.mrFont.IsCapital(); + if (bSmallCaps && rInfo.mpDXArray.empty()) + { + SAL_WARN("svx", "SmallCaps requested with DXArray, abandoning"); + bSmallCaps = false; + } + if (bSmallCaps) + { + // rerun with each sub-portion + DoCapitalsDrawPortionInfo aDoDrawPortionInfo(*this, rInfo); + rInfo.mrFont.DoOnCapitals(aDoDrawPortionInfo); + + // transfer collected primitives from maTextPortionPrimitives to a new container + drawinglayer::primitive2d::Primitive2DContainer aContainer; + aContainer.swap(maTextPortionPrimitives); + + // Take any decoration for the whole formatted portion and keep it to get continuous over/under/strike-through + if (pNewPrimitive->getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + { + const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = + static_cast<const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D*>(pNewPrimitive.get()); + + pTCPP->CreateDecorationGeometryContent( + aContainer, + pTCPP->getTextTransform(), + caseMappedText, + rInfo.mnTextStart, + rInfo.mnTextLen, + aDXArray); + } + + pNewPrimitive = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aContainer)); + } + + const Color aFontColor(rInfo.mrFont.GetColor()); + if (aFontColor.IsTransparent()) + { + // Handle semi-transparent text for both the decorated and simple case here. + pNewPrimitive = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer{ pNewPrimitive }, + (255 - aFontColor.GetAlpha()) / 255.0); + } + + if(rInfo.mbEndOfBullet) + { + // embed in TextHierarchyBulletPrimitive2D + drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive); + drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference } ; + pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence)); + } + + if(rInfo.mpFieldData) + { + pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive.get(), rInfo); + } + + maTextPortionPrimitives.push_back(pNewPrimitive); + + // support for WrongSpellVector. Create WrongSpellPrimitives as needed + if(!rInfo.mpWrongSpellVector || aDXArray.empty()) + return; + + const sal_Int32 nSize(rInfo.mpWrongSpellVector->size()); + const sal_Int32 nDXCount(aDXArray.size()); + const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded + + for(sal_Int32 a(0); a < nSize; a++) + { + const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a]; + + if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart) + { + const sal_Int32 nStart(rCandidate.nStart - rInfo.mnTextStart); + const sal_Int32 nEnd(rCandidate.nEnd - rInfo.mnTextStart); + double fStart(0.0); + double fEnd(0.0); + + if(nStart > 0 && nStart - 1 < nDXCount) + { + fStart = aDXArray[nStart - 1]; + } + + if(nEnd > 0 && nEnd - 1 < nDXCount) + { + fEnd = aDXArray[nEnd - 1]; + } + + if(!basegfx::fTools::equal(fStart, fEnd)) + { + if(rInfo.IsRTL()) + { + // #i98523# + // When the portion is RTL, mirror the redlining using the + // full portion width + const double fTextWidth(aDXArray[aDXArray.size() - 1]); + + fStart = fTextWidth - fStart; + fEnd = fTextWidth - fEnd; + + // tdf#151968 + // if start < end, OutputDevice::DrawWaveLine() will + // think it is a rotated line, so we swap fStart and + // fEnd to avoid this. + std::swap(fStart, fEnd); + } + + // need to take FontScaling out of values; it's already part of + // aNewTransform and would be double applied + const double fFontScaleX(aFontScaling.getX() * fPropFontFactor); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + fStart /= fFontScaleX; + fEnd /= fFontScaleX; + } + + maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D( + aNewTransform, + fStart, + fEnd, + aSpellColor)); + } + } + } + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive( + const DrawPortionInfo& rInfo, const OUString& rText, + const drawinglayer::attribute::FontAttribute& rFontAttribute, + const std::vector<double>& rDXArray, + const basegfx::B2DHomMatrix& rNewTransform) + { + ::std::vector< sal_Bool > aKashidaArray; + + if(!rInfo.mpKashidaArray.empty() && rInfo.mnTextLen) + { + aKashidaArray.reserve(rInfo.mnTextLen); + + for(sal_Int32 a=0; a < rInfo.mnTextLen; a++) + { + aKashidaArray.push_back(rInfo.mpKashidaArray[a]); + } + } + + // create complex text primitive and append + const Color aFontColor(rInfo.mrFont.GetColor()); + const basegfx::BColor aBFontColor(aFontColor.getBColor()); + + const Color aTextFillColor(rInfo.mrFont.GetFillColor()); + + // prepare wordLineMode (for underline and strikeout) + // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)' + // to be split which would not look like the original + const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet); + + // prepare new primitive + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive; + const bool bDecoratedIsNeeded( + LINESTYLE_NONE != rInfo.mrFont.GetOverline() + || LINESTYLE_NONE != rInfo.mrFont.GetUnderline() + || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout() + || FontEmphasisMark::NONE != (rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style) + || FontRelief::NONE != rInfo.mrFont.GetRelief() + || rInfo.mrFont.IsShadow() + || bWordLineMode); + + if(bDecoratedIsNeeded) + { + // TextDecoratedPortionPrimitive2D needed, prepare some more data + // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead + const Color aUnderlineColor(rInfo.maTextLineColor); + const basegfx::BColor aBUnderlineColor((aUnderlineColor == COL_AUTO) ? aBFontColor : aUnderlineColor.getBColor()); + const Color aOverlineColor(rInfo.maOverlineColor); + const basegfx::BColor aBOverlineColor((aOverlineColor == COL_AUTO) ? aBFontColor : aOverlineColor.getBColor()); + + // prepare overline and underline data + const drawinglayer::primitive2d::TextLine eFontOverline( + drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetOverline())); + const drawinglayer::primitive2d::TextLine eFontLineStyle( + drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetUnderline())); + + // check UnderlineAbove + const bool bUnderlineAbove( + drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && rInfo.mrFont.IsUnderlineAbove()); + + // prepare strikeout data + const drawinglayer::primitive2d::TextStrikeout eTextStrikeout( + drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout())); + + // prepare emphasis mark data + drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE); + + switch(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style) + { + case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break; + case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break; + case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break; + case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break; + default: break; + } + + const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosAbove); + const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosBelow); + + // prepare font relief data + drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE); + + switch(rInfo.mrFont.GetRelief()) + { + case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break; + case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break; + default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE + } + + // prepare shadow/outline data + const bool bShadow(rInfo.mrFont.IsShadow()); + + // TextDecoratedPortionPrimitive2D is needed, create one + pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + + // attributes for TextSimplePortionPrimitive2D + rNewTransform, + rText, + rInfo.mnTextStart, + rInfo.mnTextLen, + std::vector(rDXArray), + std::vector(aKashidaArray), + rFontAttribute, + rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(), + aBFontColor, + aTextFillColor, + + // attributes for TextDecoratedPortionPrimitive2D + aBOverlineColor, + aBUnderlineColor, + eFontOverline, + eFontLineStyle, + bUnderlineAbove, + eTextStrikeout, + bWordLineMode, + eTextEmphasisMark, + bEmphasisMarkAbove, + bEmphasisMarkBelow, + eTextRelief, + bShadow); + } + else + { + // TextSimplePortionPrimitive2D is enough + pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + rNewTransform, + rText, + rInfo.mnTextStart, + rInfo.mnTextLen, + std::vector(rDXArray), + std::vector(aKashidaArray), + rFontAttribute, + rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(), + aBFontColor, + rInfo.mbFilled, + rInfo.mnWidthToFill, + aTextFillColor); + } + + return pNewPrimitive; + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) + { + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> xRet = pPrimitive; + if(rInfo.mpFieldData) + { + // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D + // which holds the field type and, if applicable, the URL + const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData); + const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData); + + // embed current primitive to a sequence + drawinglayer::primitive2d::Primitive2DContainer aSequence; + + if(pPrimitive) + { + aSequence.resize(1); + aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive); + } + + if(pURLField) + { + // extended this to hold more of the contents of the original + // SvxURLField since that stuff is still used in HitTest and e.g. Calc + std::vector< std::pair< OUString, OUString>> meValues; + meValues.emplace_back("URL", pURLField->GetURL()); + meValues.emplace_back("Representation", pURLField->GetRepresentation()); + meValues.emplace_back("TargetFrame", pURLField->GetTargetFrame()); + meValues.emplace_back("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat()))); + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues); + } + else if(pPageField) + { + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_PAGE); + } + else + { + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_COMMON); + } + } + + return xRet; + } + + void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives() + { + // only create a line primitive when we had content; there is no need for + // empty line primitives (contrary to paragraphs, see below). + if(!maTextPortionPrimitives.empty()) + { + maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(std::move(maTextPortionPrimitives))); + } + } + + void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara) + { + sal_Int16 nDepth = mrOutliner.GetDepth(nPara); + EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara); + // Pass -1 to signal VclMetafileProcessor2D that there is no active + // bullets/numbering in this paragraph (i.e. this is normal text) + const sal_Int16 nOutlineLevel( eInfo.bVisible ? nDepth : -1); + + // ALWAYS create a paragraph primitive, even when no content was added. This is done to + // have the correct paragraph count even with empty paragraphs. Those paragraphs will + // have an empty sub-PrimitiveSequence. + maParagraphPrimitives.push_back( + new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D( + std::move(maLinePrimitives), + nOutlineLevel)); + } + + void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo) + { + impCreateTextPortionPrimitive(rInfo); + + if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph) + { + impFlushTextPortionPrimitivesToLinePrimitives(); + } + + if(rInfo.mbEndOfParagraph) + { + impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara); + } + } + + void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo) + { + basegfx::B2DHomMatrix aNewTransform; + + // add size to new transform + aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight()); + + // apply transformA + aNewTransform *= maNewTransformA; + + // apply local offset + aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y()); + + // also apply embedding object's transform + aNewTransform *= maNewTransformB; + + // prepare empty GraphicAttr + const GraphicAttr aGraphicAttr; + + // create GraphicPrimitive2D + const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D( + aNewTransform, + rInfo.maBulletGraphicObject, + aGraphicAttr)); + + // embed in TextHierarchyBulletPrimitive2D + drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference }; + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence)); + + // add to output + maTextPortionPrimitives.push_back(pNewPrimitive); + } + + IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo, void) + { + // for contour text, ignore (clip away) all portions which are below + // the visible area given by maScale + if(pInfo && static_cast<double>(pInfo->mrStartPos.Y()) < maScale.getY()) + { + impHandleDrawPortionInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo, void) + { + if(!pInfo) + return; + + // Is clipping wanted? This is text clipping; only accept a portion + // if it's completely in the range + if(!maClipRange.isEmpty()) + { + // Test start position first; this allows to not get the text range at + // all if text is far outside + const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y()); + + if(!maClipRange.isInside(aStartPosition)) + { + return; + } + + // Start position is inside. Get TextBoundRect and TopLeft next + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(pInfo->mrFont); + + const basegfx::B2DRange aTextBoundRect( + aTextLayouterDevice.getTextBoundRect( + pInfo->maText, pInfo->mnTextStart, pInfo->mnTextLen)); + const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition); + + if(!maClipRange.isInside(aTopLeft)) + { + return; + } + + // TopLeft is inside. Get BottomRight and check + const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition); + + if(!maClipRange.isInside(aBottomRight)) + { + return; + } + + // all inside, clip was successful + } + impHandleDrawPortionInfo(*pInfo); + } + + IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawPortionInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + drawinglayer::primitive2d::Primitive2DContainer impTextBreakupHandler::extractPrimitive2DSequence() + { + if(!maTextPortionPrimitives.empty()) + { + // collect non-closed lines + impFlushTextPortionPrimitivesToLinePrimitives(); + } + + if(!maLinePrimitives.empty()) + { + // collect non-closed paragraphs + impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1); + } + + return std::move(maParagraphPrimitives); + } +} // end of anonymous namespace + + +// primitive decompositions + +void SdrTextObj::impDecomposeContourTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare contour polygon, force to non-mirrored for laying out + basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon()); + aPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY()))); + + // prepare outliner + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + const Size aNullSize; + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetPolygon(aPolyPolygon); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject()); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // prepare matrices to apply to newly created primitives + basegfx::B2DHomMatrix aNewTransformA; + + // mirroring. We are now in the polygon sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale); + + // cleanup outliner + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +void SdrTextObj::impDecomposeAutoFitTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D& rSdrAutofitTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrAutofitTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const SfxItemSet& rTextItemSet = rSdrAutofitTextPrimitive.getSdrText()->GetItemSet(); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet); + SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // That color needs to be restored on leaving this method + Color aOriginalBackColor(rOutliner.GetBackgroundColor()); + setSuitableOutlinerBg(rOutliner); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject(); + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical()); + const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame()) + { + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + } + + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pOutlinerParaObject); + ImpAutoFitText(rOutliner,aAnchorTextSize,bVerticalWriting); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + // correct vertical translation using the now known text size + if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFree / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFree); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + aNewTransformB.shearX(fShearX); + aNewTransformB.rotate(fRotate); + aNewTransformB.translate(aTranslate.getX(), aTranslate.getY()); + + basegfx::B2DRange aClipRange; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.SetBackgroundColor(aOriginalBackColor); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + rOutliner.SetControlWord(nOriginalControlWord); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +// Resolves: fdo#35779 set background color of this shape as the editeng background if there +// is one. Check the shape itself, then the host page, then that page's master page. +bool SdrObject::setSuitableOutlinerBg(::Outliner& rOutliner) const +{ + const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet(); + if (drawing::FillStyle_NONE != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + Color aColor(GetDraftFillColor(*pBackgroundFillSet).value_or(rOutliner.GetBackgroundColor())); + rOutliner.SetBackgroundColor(aColor); + return true; + } + return false; +} + +const SfxItemSet* SdrObject::getBackgroundFillSet() const +{ + const SfxItemSet* pBackgroundFillSet = &GetObjectItemSet(); + + if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + SdrPage* pOwnerPage(getSdrPageFromSdrObject()); + if (pOwnerPage) + { + pBackgroundFillSet = &pOwnerPage->getSdrPageProperties().GetItemSet(); + + if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + if (!pOwnerPage->IsMasterPage() && pOwnerPage->TRG_HasMasterPage()) + { + pBackgroundFillSet = &pOwnerPage->TRG_GetMasterPage().getSdrPageProperties().GetItemSet(); + } + } + } + } + return pBackgroundFillSet; +} + +const Graphic* SdrObject::getFillGraphic() const +{ + if(IsGroupObject()) // Doesn't make sense, and GetObjectItemSet() asserts. + return nullptr; + const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet(); + if (drawing::FillStyle_BITMAP != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + return nullptr; + return &pBackgroundFillSet->Get(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic(); +} + +void SdrTextObj::impDecomposeBlockTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const bool bIsCell(rSdrBlockTextPrimitive.getCellText()); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust(); + SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust(); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight()); + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // That color needs to be restored on leaving this method + Color aOriginalBackColor(rOutliner.GetBackgroundColor()); + setSuitableOutlinerBg(rOutliner); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical()); + const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(bIsCell) + { + // cell text is formatted neither like a text object nor like an object + // text, so use a special setup here + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + + // #i106214# To work with an unchangeable PaperSize (CellSize in + // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used. + // #i106214# This was not completely correct; to still measure the real + // text height to allow vertical adjust (and vice versa for VerticalWritintg) + // only one aspect has to be set, but the other one to zero + if(bVerticalWriting) + { + // measure the horizontal text size + rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height())); + } + else + { + // measure the vertical text size + rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0)); + } + + rOutliner.SetPaperSize(aAnchorTextSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject()); + } + else + { + // check if block text is used (only one of them can be true) + const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting); + const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting); + + // set minimal paper size horizontally/vertically if needed + if(bHorizontalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + else if(bVerticalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage()) + { + // #i103454# maximal paper size hor/ver needs to be limited to text + // frame size. If it's block text, still allow the 'other' direction + // to grow to get a correct real text size when using GetPaperSize(). + // When just using aAnchorTextSize as maximum, GetPaperSize() + // would just return aAnchorTextSize again: this means, the wanted + // 'measurement' of the real size of block text would not work + Size aMaxAutoPaperSize(aAnchorTextSize); + + // Usual processing - always grow in one of directions + bool bAllowGrowVertical = !bVerticalWriting; + bool bAllowGrowHorizontal = bVerticalWriting; + + // Compatibility mode for tdf#99729 + if (getSdrModelFromSdrObject().GetCompatibilityFlag( + SdrCompatibilityFlag::AnchoredTextOverflowLegacy)) + { + bAllowGrowVertical = bHorizontalIsBlock; + bAllowGrowHorizontal = bVerticalIsBlock; + } + + if (bAllowGrowVertical) + { + // allow to grow vertical for horizontal texts + aMaxAutoPaperSize.setHeight(1000000); + } + else if (bAllowGrowHorizontal) + { + // allow to grow horizontal for vertical texts + aMaxAutoPaperSize.setWidth(1000000); + } + + rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject()); + } + + rOutliner.SetControlWord(nOriginalControlWord); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // For draw objects containing text correct hor/ver alignment if text is bigger + // than the object itself. Without that correction, the text would always be + // formatted to the left edge (or top edge when vertical) of the draw object. + if(!IsTextFrame() && !bIsCell) + { + if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWriting) + { + // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTHORZADJUST_BLOCK == eHAdj) + { + SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust(); + switch(eAdjust) + { + case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break; + case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break; + case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break; + default: break; + } + } + } + + if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWriting) + { + // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTVERTADJUST_BLOCK == eVAdj) + { + eVAdj = SDRTEXTVERTADJUST_CENTER; + } + } + } + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + const double fFreeVerticalSpace(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + bool bClipVerticalTextOverflow = fFreeVerticalSpace < 0 + && GetObjectItemSet().Get(SDRATTR_TEXT_CLIPVERTOVERFLOW).GetValue(); + // correct vertical translation using the now known text size + if((SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + && !bClipVerticalTextOverflow) + { + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFreeVerticalSpace / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFreeVerticalSpace); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + // Translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(fStartInX, fStartInY)); + + // Apply the camera rotation. It have to be applied after adjustment of + // the text (top, bottom, center, left, right). + if(GetCameraZRotation() != 0) + { + // First find the text rect. + basegfx::B2DRange aTextRectangle(/*x1=*/aTranslate.getX() + aAdjustTranslate.getX(), + /*y1=*/aTranslate.getY() + aAdjustTranslate.getY(), + /*x2=*/aTranslate.getX() + aOutlinerScale.getX() - aAdjustTranslate.getX(), + /*y2=*/aTranslate.getY() + aOutlinerScale.getY() - aAdjustTranslate.getY()); + + // Rotate the text from the center point. + basegfx::B2DVector aTranslateToCenter(aTextRectangle.getWidth() / 2, aTextRectangle.getHeight() / 2); + aNewTransformA.translate(-aTranslateToCenter.getX(), -aTranslateToCenter.getY()); + aNewTransformA.rotate(basegfx::deg2rad(360.0 - GetCameraZRotation() )); + aNewTransformA.translate(aTranslateToCenter.getX(), aTranslateToCenter.getY()); + } + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + + // create ClipRange (if needed) + basegfx::B2DRange aClipRange; + if(bClipVerticalTextOverflow) + aClipRange = {0, 0, std::numeric_limits<double>::max(), aAnchorTextRange.getHeight()}; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.SetBackgroundColor(aOriginalBackColor); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +void SdrTextObj::impDecomposeStretchTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare outliner + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE); + rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight()); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject()); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the laid out text size from outliner + const Size aOutlinerTextSize(rOutliner.CalcTextSize()); + const basegfx::B2DVector aOutlinerScale( + aOutlinerTextSize.Width() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Width(), + aOutlinerTextSize.Height() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Height()); + + // prepare matrices to apply to newly created primitives + basegfx::B2DHomMatrix aNewTransformA; + + // #i101957# Check for vertical text. If used, aNewTransformA + // needs to translate the text initially around object width to orient + // it relative to the topper right instead of the topper left + const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical()); + const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + + if(bVertical) + { + if(bTopToBottom) + aNewTransformA.translate(aScale.getX(), 0.0); + else + aNewTransformA.translate(0.0, aScale.getY()); + } + + // calculate global char stretching scale parameters. Use non-mirrored sizes + // to layout without mirroring + const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX()); + const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY()); + rOutliner.setGlobalScale(fScaleX * 100.0, fScaleY * 100.0, 100.0, 100.0); + + // When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB); + + // cleanup outliner + rOutliner.SetControlWord(nOriginalControlWord); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + + +// timing generators +#define ENDLESS_LOOP (0xffffffff) +#define ENDLESS_TIME (double(0xffffffff)) +#define PIXEL_DPI (96.0) + +void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const +{ + if(SdrTextAniKind::Blink != GetTextAniKind()) + return; + + // get values + const SfxItemSet& rSet = GetObjectItemSet(); + const sal_uInt32 nRepeat(static_cast<sal_uInt32>(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue())); + double fDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue())); + + if(0.0 == fDelay) + { + // use default + fDelay = 250.0; + } + + // prepare loop and add + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0); + aLoop.append(aStart); + drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0); + aLoop.append(aEnd); + rAnimList.append(aLoop); + + // add stopped state if loop is not endless + if(0 != nRepeat) + { + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisibleWhenStopped ? 0.0 : 1.0); + rAnimList.append(aStop); + } +} + +static void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency) +{ + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue()); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + if(bVisibleWhenStarted) + { + // move from center to outside + drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0); + rAnimList.append(aInOut); + } + + // loop. In loop, move through + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0); + aLoop.append(aThrough); + rAnimList.append(aLoop); + + if(0 != nRepeat && bVisibleWhenStopped) + { + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5); + rAnimList.append(aOutIn); + + // add timing for staying at the end + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } +} + +static void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency) +{ + if(basegfx::fTools::more(fRelativeTextLength, 0.5)) + { + // this is the case when fTextLength > fFrameLength, text is bigger than animation frame. + // In that case, correct direction + bForward = !bForward; + } + + const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength); + const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength); + bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue()); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + if(!bVisibleWhenStarted) + { + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5); + rAnimList.append(aOutIn); + } + + // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame, + // so use absolute value + const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0))); + const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath); + const double fHalfInnerPath(fTimeForInnerPath * 0.5); + const sal_uInt32 nDoubleRepeat(nRepeat / 2L); + + if(nDoubleRepeat || 0 == nRepeat) + { + // double forth and back loop + drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition); + aLoop.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition); + aLoop.append(aTime1); + drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5); + aLoop.append(aTime2); + rAnimList.append(aLoop); + } + + if(nRepeat % 2L) + { + // repeat is uneven, so we need one more forth and back to center + drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition); + rAnimList.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5); + rAnimList.append(aTime1); + } + + if(0 == nRepeat) + return; + + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + if(bVisibleWhenStopped) + { + // add timing for staying at the end + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } + else + { + // move from center to outside + drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0); + rAnimList.append(aInOut); + } +} + +static void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency) +{ + // move in from outside, start outside + const double fStartPosition(bForward ? 0.0 : 1.0); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5); + rAnimList.append(aOutIn); + + // loop. In loop, move out and in again + if(nRepeat > 1 || 0 == nRepeat) + { + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1 : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition); + aLoop.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5); + aLoop.append(aTime1); + rAnimList.append(aLoop); + } + + // always visible when stopped, so add timing for staying at the end when not endless + if(0 != nRepeat) + { + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } +} + +void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const +{ + const SdrTextAniKind eAniKind(GetTextAniKind()); + + if(SdrTextAniKind::Scroll != eAniKind && SdrTextAniKind::Alternate != eAniKind && SdrTextAniKind::Slide != eAniKind) + return; + + // get data. Goal is to calculate fTimeFullPath which is the time needed to + // move animation from (0.0) to (1.0) state + const SfxItemSet& rSet = GetObjectItemSet(); + double fAnimationDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue())); + double fSingleStepWidth(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIAMOUNT).GetValue())); + const SdrTextAniDirection eDirection(GetTextAniDirection()); + const bool bForward(SdrTextAniDirection::Right == eDirection || SdrTextAniDirection::Down == eDirection); + + if(basegfx::fTools::equalZero(fAnimationDelay)) + { + // default to 1/20 second + fAnimationDelay = 50.0; + } + + if(basegfx::fTools::less(fSingleStepWidth, 0.0)) + { + // data is in pixels, convert to logic. Imply PIXEL_DPI dpi. + // It makes no sense to keep the view-transformation centered + // definitions, so get rid of them here. + fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI)); + } + + if(basegfx::fTools::equalZero(fSingleStepWidth)) + { + // default to 1 millimeter + fSingleStepWidth = 100.0; + } + + // use the length of the full animation path and the number of steps + // to get the full path time + const double fFullPathLength(fFrameLength + fTextLength); + const double fNumberOfSteps(fFullPathLength / fSingleStepWidth); + double fTimeFullPath(fNumberOfSteps * fAnimationDelay); + + if(fTimeFullPath < fAnimationDelay) + { + fTimeFullPath = fAnimationDelay; + } + + switch(eAniKind) + { + case SdrTextAniKind::Scroll : + { + impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay); + break; + } + case SdrTextAniKind::Alternate : + { + double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength)); + impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay); + break; + } + case SdrTextAniKind::Slide : + { + impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay); + break; + } + default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink + } +} + +void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner &rOutliner) const +{ + if (GetTextChain()->GetNilChainingEvent(this)) + return; + + GetTextChain()->SetNilChainingEvent(this, true); + + TextChainFlow aTxtChainFlow(const_cast<SdrTextObj*>(this)); + bool bIsOverflow; + +#ifdef DBG_UTIL + // Some debug output + size_t nObjCount(getSdrPageFromSdrObject()->GetObjCount()); + for (size_t i = 0; i < nObjCount; i++) + { + SdrTextObj* pCurObj(DynCastSdrTextObj(getSdrPageFromSdrObject()->GetObj(i))); + if(pCurObj == this) + { + SAL_INFO("svx.chaining", "Working on TextBox " << i); + break; + } + } +#endif + + aTxtChainFlow.CheckForFlowEvents(&rOutliner); + + if (aTxtChainFlow.IsUnderflow() && !IsInEditMode()) + { + // underflow-induced overflow + aTxtChainFlow.ExecuteUnderflow(&rOutliner); + bIsOverflow = aTxtChainFlow.IsOverflow(); + } else { + // standard overflow (no underflow before) + bIsOverflow = aTxtChainFlow.IsOverflow(); + } + + if (bIsOverflow && !IsInEditMode()) { + // Initialize Chaining Outliner + SdrOutliner &rChainingOutl(getSdrModelFromSdrObject().GetChainingOutliner(this)); + ImpInitDrawOutliner( rChainingOutl ); + rChainingOutl.SetUpdateLayout(true); + // We must pass the chaining outliner otherwise we would mess up decomposition + aTxtChainFlow.ExecuteOverflow(&rOutliner, &rChainingOutl); + } + + GetTextChain()->SetNilChainingEvent(this, false); +} + +void SdrTextObj::impDecomposeChainedTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrChainedTextPrimitive2D& rSdrChainedTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrChainedTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const SfxItemSet& rTextItemSet = rSdrChainedTextPrimitive.getSdrText()->GetItemSet(); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + + SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet); + SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + + // Text + const OutlinerParaObject* pOutlinerParaObject = rSdrChainedTextPrimitive.getSdrText()->GetOutlinerParaObject(); + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + + const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical()); + const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(IsTextFrame()) + { + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + } + + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + // Sets original text + rOutliner.SetText(*pOutlinerParaObject); + + /* Begin overflow/underflow handling */ + + impHandleChainingEventsDuringDecomposition(rOutliner); + + /* End overflow/underflow handling */ + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + // correct vertical translation using the now known text size + if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFree / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFree); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + aNewTransformB.shearX(fShearX); + aNewTransformB.rotate(fRotate); + aNewTransformB.translate(aTranslate.getX(), aTranslate.getY()); + + basegfx::B2DRange aClipRange; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + rOutliner.SetControlWord(nOriginalControlWord); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +// Direct decomposer for text visualization when you already have a prepared +// Outliner containing all the needed information +void SdrTextObj::impDecomposeBlockTextPrimitiveDirect( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + SdrOutliner& rOutliner, + const basegfx::B2DHomMatrix& rNewTransformA, + const basegfx::B2DHomMatrix& rNewTransformB, + const basegfx::B2DRange& rClipRange) +{ + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, rClipRange); + rTarget.append(aConverter.extractPrimitive2DSequence()); +} + +double SdrTextObj::GetCameraZRotation() const +{ + const css::uno::Any* pAny; + double fTextCameraZRotateAngle = 0.0; + const SfxItemSet& rSet = GetObjectItemSet(); + const SdrCustomShapeGeometryItem& rGeometryItem(rSet.Get(SDRATTR_CUSTOMSHAPE_GEOMETRY)); + + pAny = rGeometryItem.GetPropertyValueByName("TextCameraZRotateAngle"); + + if ( pAny ) + *pAny >>= fTextCameraZRotateAngle; + + return fTextCameraZRotateAngle; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotextpathdecomposition.cxx b/svx/source/svdraw/svdotextpathdecomposition.cxx new file mode 100644 index 0000000000..7537625b2a --- /dev/null +++ b/svx/source/svdraw/svdotextpathdecomposition.cxx @@ -0,0 +1,748 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdoutl.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <sdr/primitive2d/sdrtextprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <algorithm> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <basegfx/color/bcolor.hxx> + +// primitive decomposition helpers +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svx/unoapi.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <sdr/attribute/sdrformtextoutlineattribute.hxx> +#include <utility> + +using namespace com::sun::star; + +// PathTextPortion helper + +namespace +{ + class impPathTextPortion + { + basegfx::B2DVector maOffset; + OUString maText; + sal_Int32 mnTextStart; + sal_Int32 mnTextLength; + sal_Int32 mnParagraph; + SvxFont maFont; + ::std::vector< double > maDblDXArray; // double DXArray, font size independent -> unit coordinate system + ::std::vector< sal_Bool > maKashidaArray; + lang::Locale maLocale; + + bool mbRTL : 1; + + public: + explicit impPathTextPortion(const DrawPortionInfo& rInfo) + : maOffset(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()), + maText(rInfo.maText), + mnTextStart(rInfo.mnTextStart), + mnTextLength(rInfo.mnTextLen), + mnParagraph(rInfo.mnPara), + maFont(rInfo.mrFont), + maKashidaArray(rInfo.mpKashidaArray.begin(), rInfo.mpKashidaArray.end()), + maLocale(rInfo.mpLocale ? *rInfo.mpLocale : lang::Locale()), + mbRTL(!rInfo.mrFont.IsVertical() && rInfo.IsRTL()) + { + if(mnTextLength && !rInfo.mpDXArray.empty()) + { + maDblDXArray.reserve(mnTextLength); + + for(sal_Int32 a=0; a < mnTextLength; a++) + { + maDblDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a])); + } + } + } + + // for ::std::sort + bool operator<(const impPathTextPortion& rComp) const + { + if(mnParagraph < rComp.mnParagraph) + { + return true; + } + + if(maOffset.getX() < rComp.maOffset.getX()) + { + return true; + } + + return (maOffset.getY() < rComp.maOffset.getY()); + } + + const OUString& getText() const { return maText; } + sal_Int32 getTextStart() const { return mnTextStart; } + sal_Int32 getTextLength() const { return mnTextLength; } + sal_Int32 getParagraph() const { return mnParagraph; } + const SvxFont& getFont() const { return maFont; } + bool isRTL() const { return mbRTL; } + const ::std::vector< double >& getDoubleDXArray() const { return maDblDXArray; } + const ::std::vector< sal_Bool >& getKashidaArray() const { return maKashidaArray; } + const lang::Locale& getLocale() const { return maLocale; } + + sal_Int32 getPortionIndex(sal_Int32 nIndex, sal_Int32 nLength) const + { + if(mbRTL) + { + return (mnTextStart + (mnTextLength - (nIndex + nLength))); + } + else + { + return (mnTextStart + nIndex); + } + } + + double getDisplayLength(sal_Int32 nIndex, sal_Int32 nLength) const + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + double fRetval(0.0); + + if(maFont.IsVertical()) + { + fRetval = aTextLayouter.getTextHeight() * static_cast<double>(nLength); + } + else + { + fRetval = aTextLayouter.getTextWidth(maText, getPortionIndex(nIndex, nLength), nLength); + } + + return fRetval; + } + }; +} // end of anonymous namespace + + +// TextBreakup helper + +namespace +{ + class impTextBreakupHandler + { + SdrOutliner& mrOutliner; + ::std::vector< impPathTextPortion > maPathTextPortions; + + DECL_LINK(decompositionPathTextPrimitive, DrawPortionInfo*, void ); + + public: + explicit impTextBreakupHandler(SdrOutliner& rOutliner) + : mrOutliner(rOutliner) + { + } + + const ::std::vector< impPathTextPortion >& decompositionPathTextPrimitive() + { + // strip portions to maPathTextPortions + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decompositionPathTextPrimitive)); + mrOutliner.StripPortions(); + + if(!maPathTextPortions.empty()) + { + // sort portions by paragraph, x and y + ::std::sort(maPathTextPortions.begin(), maPathTextPortions.end()); + } + + return maPathTextPortions; + } + }; + + IMPL_LINK(impTextBreakupHandler, decompositionPathTextPrimitive, DrawPortionInfo*, pInfo, void) + { + maPathTextPortions.emplace_back(*pInfo); + } +} // end of anonymous namespace + + +// TextBreakup one poly and one paragraph helper + +namespace +{ + class impPolygonParagraphHandler + { + const drawinglayer::attribute::SdrFormTextAttribute maSdrFormTextAttribute; // FormText parameters + drawinglayer::primitive2d::Primitive2DContainer& mrDecomposition; // destination primitive list + drawinglayer::primitive2d::Primitive2DContainer& mrShadowDecomposition; // destination primitive list for shadow + uno::Reference<i18n::XBreakIterator> mxBreak; // break iterator + + static double getParagraphTextLength(const ::std::vector< const impPathTextPortion* >& rTextPortions) + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + double fRetval(0.0); + + for(const impPathTextPortion* pCandidate : rTextPortions) + { + if(pCandidate && pCandidate->getTextLength()) + { + aTextLayouter.setFont(pCandidate->getFont()); + fRetval += pCandidate->getDisplayLength(0, pCandidate->getTextLength()); + } + } + + return fRetval; + } + + sal_Int32 getNextGlyphLen(const impPathTextPortion* pCandidate, sal_Int32 nPosition, const lang::Locale& rFontLocale) + { + sal_Int32 nNextGlyphLen(1); + + if(mxBreak.is()) + { + sal_Int32 nDone(0); + nNextGlyphLen = mxBreak->nextCharacters(pCandidate->getText(), nPosition, + rFontLocale, i18n::CharacterIteratorMode::SKIPCELL, 1, nDone) - nPosition; + } + + return nNextGlyphLen; + } + + public: + impPolygonParagraphHandler( + drawinglayer::attribute::SdrFormTextAttribute aSdrFormTextAttribute, + drawinglayer::primitive2d::Primitive2DContainer& rDecomposition, + drawinglayer::primitive2d::Primitive2DContainer& rShadowDecomposition) + : maSdrFormTextAttribute(std::move(aSdrFormTextAttribute)), + mrDecomposition(rDecomposition), + mrShadowDecomposition(rShadowDecomposition) + { + // prepare BreakIterator + uno::Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + mxBreak = i18n::BreakIterator::create(xContext); + } + + void HandlePair(const basegfx::B2DPolygon& rPolygonCandidate, const ::std::vector< const impPathTextPortion* >& rTextPortions) + { + // prepare polygon geometry, take into account as many parameters as possible + basegfx::B2DPolygon aPolygonCandidate(rPolygonCandidate); + const double fPolyLength(basegfx::utils::getLength(aPolygonCandidate)); + double fPolyEnd(fPolyLength); + double fPolyStart(0.0); + double fAutosizeScaleFactor(1.0); + bool bAutosizeScale(false); + + if(maSdrFormTextAttribute.getFormTextMirror()) + { + aPolygonCandidate.flip(); + } + + if(maSdrFormTextAttribute.getFormTextStart() + && (XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust() + || XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust())) + { + if(XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust()) + { + fPolyStart += maSdrFormTextAttribute.getFormTextStart(); + + if(fPolyStart > fPolyEnd) + { + fPolyStart = fPolyEnd; + } + } + else + { + fPolyEnd -= maSdrFormTextAttribute.getFormTextStart(); + + if(fPolyEnd < fPolyStart) + { + fPolyEnd = fPolyStart; + } + } + } + + if(XFormTextAdjust::Left != maSdrFormTextAttribute.getFormTextAdjust()) + { + // calculate total text length of this paragraph, some layout needs to be done + const double fParagraphTextLength(getParagraphTextLength(rTextPortions)); + + // check if text is too long for paragraph. If yes, handle as if left aligned (default), + // but still take care of XFormTextAdjust::AutoSize in that case + const bool bTextTooLong(fParagraphTextLength > (fPolyEnd - fPolyStart)); + + if(XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust()) + { + if(!bTextTooLong) + { + // if right aligned, add difference to polygon start + fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength); + } + } + else if(XFormTextAdjust::Center == maSdrFormTextAttribute.getFormTextAdjust()) + { + if(!bTextTooLong) + { + // if centered, add half of difference to polygon start + fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength) / 2.0; + } + } + else if(XFormTextAdjust::AutoSize == maSdrFormTextAttribute.getFormTextAdjust()) + { + // if scale, prepare scale factor between curve length and text length + if(0.0 != fParagraphTextLength) + { + fAutosizeScaleFactor = (fPolyEnd - fPolyStart) / fParagraphTextLength; + bAutosizeScale = true; + } + } + } + + // handle text portions for this paragraph + for(auto a = rTextPortions.begin(); a != rTextPortions.end() && fPolyStart < fPolyEnd; ++a) + { + const impPathTextPortion* pCandidate = *a; + basegfx::B2DVector aFontScaling; + + if(pCandidate && pCandidate->getTextLength()) + { + const drawinglayer::attribute::FontAttribute aCandidateFontAttribute( + drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontScaling, + pCandidate->getFont(), + pCandidate->isRTL(), + false)); + + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + aTextLayouter.setFont(pCandidate->getFont()); + sal_Int32 nUsedTextLength(0); + + while(nUsedTextLength < pCandidate->getTextLength() && fPolyStart < fPolyEnd) + { + sal_Int32 nNextGlyphLen(getNextGlyphLen(pCandidate, pCandidate->getTextStart() + nUsedTextLength, pCandidate->getLocale())); + + // prepare portion length. Takes RTL sections into account. + double fPortionLength(pCandidate->getDisplayLength(nUsedTextLength, nNextGlyphLen)); + + if(bAutosizeScale) + { + // when autosize scaling, expand portion length + fPortionLength *= fAutosizeScaleFactor; + } + + // create transformation + basegfx::B2DHomMatrix aNewTransformA, aNewTransformB, aNewShadowTransform; + basegfx::B2DPoint aStartPos(basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart, fPolyLength)); + basegfx::B2DPoint aEndPos(aStartPos); + + // add font scaling + aNewTransformA.scale(aFontScaling.getX(), aFontScaling.getY()); + + // prepare scaling of text primitive + if(bAutosizeScale) + { + // when autosize scaling, expand text primitive scaling to it + aNewTransformA.scale(fAutosizeScaleFactor, fAutosizeScaleFactor); + } + + // eventually create shadow primitives from aDecomposition and add to rDecomposition + const bool bShadow(XFormTextShadow::NONE != maSdrFormTextAttribute.getFormTextShadow()); + + if(bShadow) + { + if(XFormTextShadow::Normal == maSdrFormTextAttribute.getFormTextShadow()) + { + aNewShadowTransform.translate( + maSdrFormTextAttribute.getFormTextShdwXVal(), + -maSdrFormTextAttribute.getFormTextShdwYVal()); + } + else // XFormTextShadow::Slant + { + double fScaleValue(maSdrFormTextAttribute.getFormTextShdwYVal() / 100.0); + double fShearValue(-basegfx::deg2rad<10>(maSdrFormTextAttribute.getFormTextShdwXVal())); + + aNewShadowTransform.scale(1.0, fScaleValue); + aNewShadowTransform.shearX(sin(fShearValue)); + aNewShadowTransform.scale(1.0, cos(fShearValue)); + } + } + + switch(maSdrFormTextAttribute.getFormTextStyle()) + { + case XFormTextStyle::Rotate : + { + aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); + const basegfx::B2DVector aDirection(aEndPos - aStartPos); + aNewTransformB.rotate(atan2(aDirection.getY(), aDirection.getX())); + aNewTransformB.translate(aStartPos.getX(), aStartPos.getY()); + + break; + } + case XFormTextStyle::Upright : + { + aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY()); + + break; + } + case XFormTextStyle::SlantX : + { + aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); + const basegfx::B2DVector aDirection(aEndPos - aStartPos); + const double fShearValue(atan2(aDirection.getY(), aDirection.getX())); + const double fSin(sin(fShearValue)); + const double fCos(cos(fShearValue)); + + aNewTransformB.shearX(-fSin); + + // Scale may lead to objects without height since fCos == 0.0 is possible. + // Renderers need to handle that, it's not a forbidden value and does not + // need to be avoided + aNewTransformB.scale(1.0, fCos); + aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY()); + + break; + } + case XFormTextStyle::SlantY : + { + aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); + const basegfx::B2DVector aDirection(aEndPos - aStartPos); + const double fShearValue(atan2(aDirection.getY(), aDirection.getX())); + const double fCos(cos(fShearValue)); + const double fTan(tan(fShearValue)); + + // shear to 'stand' on the curve + aNewTransformB.shearY(fTan); + + // scale in X to make as tight as needed. As with XFT_SLANT_X, this may + // lead to primitives without width which the renderers will handle + aNewTransformA.scale(fCos, 1.0); + + aNewTransformB.translate(aStartPos.getX(), aStartPos.getY()); + + break; + } + default : break; // XFormTextStyle::NONE + } + + // distance from path? + if(maSdrFormTextAttribute.getFormTextDistance()) + { + if(aEndPos.equal(aStartPos)) + { + aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); + } + + // use back vector (aStartPos - aEndPos) here to get mirrored perpendicular as in old stuff + const basegfx::B2DVector aPerpendicular( + basegfx::getNormalizedPerpendicular(aStartPos - aEndPos) * + maSdrFormTextAttribute.getFormTextDistance()); + aNewTransformB.translate(aPerpendicular.getX(), aPerpendicular.getY()); + } + + if(!pCandidate->getText().isEmpty() && nNextGlyphLen) + { + const sal_Int32 nPortionIndex(pCandidate->getPortionIndex(nUsedTextLength, nNextGlyphLen)); + ::std::vector< double > aNewDXArray; + + if(nNextGlyphLen > 1 && !pCandidate->getDoubleDXArray().empty()) + { + // copy DXArray for portion + aNewDXArray.insert( + aNewDXArray.begin(), + pCandidate->getDoubleDXArray().begin() + nPortionIndex, + pCandidate->getDoubleDXArray().begin() + (nPortionIndex + nNextGlyphLen)); + + if(nPortionIndex > 0) + { + // adapt to portion start + double fDXOffset= *(pCandidate->getDoubleDXArray().begin() + (nPortionIndex - 1)); + ::std::transform( + aNewDXArray.begin(), aNewDXArray.end(), + aNewDXArray.begin(), [fDXOffset](double x) { return x - fDXOffset; }); + } + + if(bAutosizeScale) + { + // when autosize scaling, adapt to DXArray, too + ::std::transform( + aNewDXArray.begin(), aNewDXArray.end(), + aNewDXArray.begin(), [fAutosizeScaleFactor](double x) { return x * fAutosizeScaleFactor; }); + } + } + + if(bShadow) + { + // shadow primitive creation + const Color aShadowColor(maSdrFormTextAttribute.getFormTextShdwColor()); + const basegfx::BColor aRGBShadowColor(aShadowColor.getBColor()); + + mrShadowDecomposition.push_back( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aNewTransformB * aNewShadowTransform * aNewTransformA, + pCandidate->getText(), + nPortionIndex, + nNextGlyphLen, + std::vector(aNewDXArray), + std::vector(pCandidate->getKashidaArray()), + aCandidateFontAttribute, + pCandidate->getLocale(), + aRGBShadowColor) ); + } + + { + // primitive creation + const Color aColor(pCandidate->getFont().GetColor()); + const basegfx::BColor aRGBColor(aColor.getBColor()); + + mrDecomposition.push_back( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aNewTransformB * aNewTransformA, + pCandidate->getText(), + nPortionIndex, + nNextGlyphLen, + std::move(aNewDXArray), + std::vector(pCandidate->getKashidaArray()), + aCandidateFontAttribute, + pCandidate->getLocale(), + aRGBColor) ); + } + } + + // consume from portion + nUsedTextLength += nNextGlyphLen; + + // consume from polygon + fPolyStart += fPortionLength; + } + } + } + } + }; +} // end of anonymous namespace + + +// primitive decomposition helpers + +namespace +{ + void impAddPolygonStrokePrimitives( + const basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, + const basegfx::B2DHomMatrix& rTransform, + const drawinglayer::attribute::LineAttribute& rLineAttribute, + const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute, + drawinglayer::primitive2d::Primitive2DContainer& rTarget) + { + for(const auto& rB2DPolyPolygon : rB2DPolyPolyVector) + { + // prepare PolyPolygons + basegfx::B2DPolyPolygon aB2DPolyPolygon = rB2DPolyPolygon; + aB2DPolyPolygon.transform(rTransform); + + for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + // create one primitive per polygon + rTarget.push_back( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + rPolygon, rLineAttribute, rStrokeAttribute) ); + } + } + } + + drawinglayer::primitive2d::Primitive2DContainer impAddPathTextOutlines( + const drawinglayer::primitive2d::Primitive2DContainer& rSource, + const drawinglayer::attribute::SdrFormTextOutlineAttribute& rOutlineAttribute) + { + drawinglayer::primitive2d::Primitive2DContainer aNewPrimitives; + + for(const drawinglayer::primitive2d::Primitive2DReference& a : rSource) + { + const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pTextCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(a.get()); + + if(pTextCandidate) + { + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + basegfx::B2DHomMatrix aPolygonTransform; + + // get text outlines and their object transformation + pTextCandidate->getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform); + + if(!aB2DPolyPolyVector.empty()) + { + // create stroke primitives + drawinglayer::primitive2d::Primitive2DContainer aStrokePrimitives; + impAddPolygonStrokePrimitives( + aB2DPolyPolyVector, + aPolygonTransform, + rOutlineAttribute.getLineAttribute(), + rOutlineAttribute.getStrokeAttribute(), + aStrokePrimitives); + const sal_uInt32 nStrokeCount(aStrokePrimitives.size()); + + if(nStrokeCount) + { + if(rOutlineAttribute.getTransparence()) + { + // create UnifiedTransparencePrimitive2D + aNewPrimitives.push_back( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(aStrokePrimitives), + static_cast<double>(rOutlineAttribute.getTransparence()) / 100.0) ); + } + else + { + // add polygons to rDecomposition as polygonStrokePrimitives + aNewPrimitives.append( std::move(aStrokePrimitives) ); + } + } + } + } + } + + return aNewPrimitives; + } +} // end of anonymous namespace + + +// primitive decomposition + +void SdrTextObj::impDecomposePathTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrPathTextPrimitive2D& rSdrPathTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + drawinglayer::primitive2d::Primitive2DContainer aRetvalA; + drawinglayer::primitive2d::Primitive2DContainer aRetvalB; + + // prepare outliner + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + rOutliner.SetUpdateLayout(true); + rOutliner.Clear(); + rOutliner.SetPaperSize(Size(LONG_MAX,LONG_MAX)); + rOutliner.SetText(rSdrPathTextPrimitive.getOutlinerParaObject()); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now break up to text portions + impTextBreakupHandler aConverter(rOutliner); + const ::std::vector< impPathTextPortion > rPathTextPortions = aConverter.decompositionPathTextPrimitive(); + + if(!rPathTextPortions.empty()) + { + // get FormText and polygon values + const drawinglayer::attribute::SdrFormTextAttribute& rFormTextAttribute = rSdrPathTextPrimitive.getSdrFormTextAttribute(); + const basegfx::B2DPolyPolygon& rPathPolyPolygon(rSdrPathTextPrimitive.getPathPolyPolygon()); + + // get loop count + sal_uInt32 nLoopCount(rPathPolyPolygon.count()); + + if(o3tl::make_unsigned(rOutliner.GetParagraphCount()) < nLoopCount) + { + nLoopCount = rOutliner.GetParagraphCount(); + } + + if(nLoopCount) + { + // prepare common decomposition stuff + drawinglayer::primitive2d::Primitive2DContainer aRegularDecomposition; + drawinglayer::primitive2d::Primitive2DContainer aShadowDecomposition; + impPolygonParagraphHandler aPolygonParagraphHandler( + rFormTextAttribute, + aRegularDecomposition, + aShadowDecomposition); + sal_uInt32 a; + + for(a = 0; a < nLoopCount; a++) + { + // filter text portions for this paragraph + ::std::vector< const impPathTextPortion* > aParagraphTextPortions; + + for(const auto & rCandidate : rPathTextPortions) + { + if(static_cast<sal_uInt32>(rCandidate.getParagraph()) == a) + { + aParagraphTextPortions.push_back(&rCandidate); + } + } + + // handle data pair polygon/ParagraphTextPortions + if(!aParagraphTextPortions.empty()) + { + aPolygonParagraphHandler.HandlePair(rPathPolyPolygon.getB2DPolygon(a), aParagraphTextPortions); + } + } + + const sal_uInt32 nShadowCount(aShadowDecomposition.size()); + const sal_uInt32 nRegularCount(aRegularDecomposition.size()); + + if(nShadowCount) + { + // add shadow primitives to decomposition + + // if necessary, add shadow outlines + if(rFormTextAttribute.getFormTextOutline() + && !rFormTextAttribute.getShadowOutline().isDefault()) + { + aRetvalA = aShadowDecomposition; + const drawinglayer::primitive2d::Primitive2DContainer aOutlines( + impAddPathTextOutlines( + aShadowDecomposition, + rFormTextAttribute.getShadowOutline())); + + aRetvalA.append(aOutlines); + } + else + aRetvalA = std::move(aShadowDecomposition); + } + + if(nRegularCount) + { + // add normal primitives to decomposition + + // if necessary, add outlines + if(rFormTextAttribute.getFormTextOutline() + && !rFormTextAttribute.getOutline().isDefault()) + { + aRetvalB = aRegularDecomposition; + const drawinglayer::primitive2d::Primitive2DContainer aOutlines( + impAddPathTextOutlines( + aRegularDecomposition, + rFormTextAttribute.getOutline())); + + aRetvalB.append(aOutlines); + } + else + aRetvalB = std::move(aRegularDecomposition); + } + } + } + + // clean up outliner + rOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + // concatenate all results + rTarget.append(std::move(aRetvalA)); + rTarget.append(std::move(aRetvalB)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxat.cxx b/svx/source/svdraw/svdotxat.cxx new file mode 100644 index 0000000000..4957a4fe98 --- /dev/null +++ b/svx/source/svdraw/svdotxat.cxx @@ -0,0 +1,458 @@ +/* -*- 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 <comphelper/string.hxx> +#include <o3tl/sorted_vector.hxx> +#include <o3tl/string_view.hxx> +#include <svl/style.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdocapt.hxx> +#include <editeng/editdata.hxx> +#include <svx/sdtfchim.hxx> + + +#include <editeng/outlobj.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editobj.hxx> + +namespace { +// The style family which is appended to the style names is padded to this many characters. +const short PADDING_LENGTH_FOR_STYLE_FAMILY = 5; +// this character will be used to pad the style families when they are appended to the style names +const char PADDING_CHARACTER_FOR_STYLE_FAMILY = ' '; +} + +bool SdrTextObj::AdjustTextFrameWidthAndHeight( tools::Rectangle& rR, bool bHgt, bool bWdt ) const +{ + if (!mbTextFrame) + // Not a text frame. Bail out. + return false; + + if (rR.IsEmpty()) + // Empty rectangle. + return false; + + bool bFitToSize = IsFitToSize(); + if (bFitToSize) + return false; + + bool bWdtGrow = bWdt && IsAutoGrowWidth(); + bool bHgtGrow = bHgt && IsAutoGrowHeight(); + if (!bWdtGrow && !bHgtGrow) + // Not supposed to auto-adjust width or height. + return false; + + SdrTextAniKind eAniKind = GetTextAniKind(); + SdrTextAniDirection eAniDir = GetTextAniDirection(); + + bool bScroll = eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide; + bool bHScroll = bScroll && (eAniDir == SdrTextAniDirection::Left || eAniDir == SdrTextAniDirection::Right); + bool bVScroll = bScroll && (eAniDir == SdrTextAniDirection::Up || eAniDir == SdrTextAniDirection::Down); + + tools::Rectangle aOldRect = rR; + tools::Long nHgt = 0, nMinHgt = 0, nMaxHgt = 0; + tools::Long nWdt = 0, nMinWdt = 0, nMaxWdt = 0; + + Size aNewSize = rR.GetSize(); + aNewSize.AdjustWidth( -1 ); aNewSize.AdjustHeight( -1 ); + + Size aMaxSiz(100000, 100000); + Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize()); + + if (aTmpSiz.Width()) + aMaxSiz.setWidth( aTmpSiz.Width() ); + if (aTmpSiz.Height()) + aMaxSiz.setHeight( aTmpSiz.Height() ); + + if (bWdtGrow) + { + nMinWdt = GetMinTextFrameWidth(); + nMaxWdt = GetMaxTextFrameWidth(); + if (nMaxWdt == 0 || nMaxWdt > aMaxSiz.Width()) + nMaxWdt = aMaxSiz.Width(); + if (nMinWdt <= 0) + nMinWdt = 1; + + aNewSize.setWidth( nMaxWdt ); + } + + if (bHgtGrow) + { + nMinHgt = GetMinTextFrameHeight(); + nMaxHgt = GetMaxTextFrameHeight(); + if (nMaxHgt == 0 || nMaxHgt > aMaxSiz.Height()) + nMaxHgt = aMaxSiz.Height(); + if (nMinHgt <= 0) + nMinHgt = 1; + + aNewSize.setHeight( nMaxHgt ); + } + + tools::Long nHDist = GetTextLeftDistance() + GetTextRightDistance(); + tools::Long nVDist = GetTextUpperDistance() + GetTextLowerDistance(); + aNewSize.AdjustWidth( -nHDist ); + aNewSize.AdjustHeight( -nVDist ); + + if (aNewSize.Width() < 2) + aNewSize.setWidth( 2 ); + if (aNewSize.Height() < 2) + aNewSize.setHeight( 2 ); + + if (!IsInEditMode()) + { + if (bHScroll) + aNewSize.setWidth( 0x0FFFFFFF ); // don't break ticker text + if (bVScroll) + aNewSize.setHeight( 0x0FFFFFFF ); + } + + if (mpEditingOutliner) + { + mpEditingOutliner->SetMaxAutoPaperSize(aNewSize); + if (bWdtGrow) + { + Size aSiz2(mpEditingOutliner->CalcTextSize()); + nWdt = aSiz2.Width() + 1; // a little tolerance + if (bHgtGrow) + nHgt = aSiz2.Height() + 1; // a little tolerance + } + else + { + nHgt = mpEditingOutliner->GetTextHeight() + 1; // a little tolerance + } + } + else + { + Outliner& rOutliner = ImpGetDrawOutliner(); + rOutliner.SetPaperSize(aNewSize); + rOutliner.SetUpdateLayout(true); + // TODO: add the optimization with bPortionInfoChecked etc. here + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if (pOutlinerParaObject) + { + rOutliner.SetText(*pOutlinerParaObject); + rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + + if (bWdtGrow) + { + Size aSiz2(rOutliner.CalcTextSize()); + nWdt = aSiz2.Width() + 1; // a little tolerance + if (bHgtGrow) + nHgt = aSiz2.Height() + 1; // a little tolerance + } + else + { + nHgt = rOutliner.GetTextHeight() + 1; // a little tolerance + } + rOutliner.Clear(); + } + + if (nWdt < nMinWdt) + nWdt = nMinWdt; + if (nWdt > nMaxWdt) + nWdt = nMaxWdt; + nWdt += nHDist; + if (nWdt < 1) + nWdt = 1; // nHDist may be negative + if (nHgt < nMinHgt) + nHgt = nMinHgt; + if (nHgt > nMaxHgt) + nHgt = nMaxHgt; + nHgt += nVDist; + if (nHgt < 1) + nHgt = 1; // nVDist may be negative + tools::Long nWdtGrow = nWdt - (rR.Right() - rR.Left()); + tools::Long nHgtGrow = nHgt - (rR.Bottom() - rR.Top()); + + if (nWdtGrow == 0) + bWdtGrow = false; + if (nHgtGrow == 0) + bHgtGrow = false; + + if (!bWdtGrow && !bHgtGrow) + return false; + + if (bWdtGrow) + { + SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(); + + if (eHAdj == SDRTEXTHORZADJUST_LEFT) + rR.AdjustRight(nWdtGrow ); + else if (eHAdj == SDRTEXTHORZADJUST_RIGHT) + rR.AdjustLeft( -nWdtGrow ); + else + { + tools::Long nWdtGrow2 = nWdtGrow / 2; + rR.AdjustLeft( -nWdtGrow2 ); + rR.SetRight( rR.Left() + nWdt ); + } + } + + if (bHgtGrow) + { + SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(); + + if (eVAdj == SDRTEXTVERTADJUST_TOP) + rR.AdjustBottom(nHgtGrow ); + else if (eVAdj == SDRTEXTVERTADJUST_BOTTOM) + rR.AdjustTop( -nHgtGrow ); + else + { + tools::Long nHgtGrow2 = nHgtGrow / 2; + rR.AdjustTop( -nHgtGrow2 ); + rR.SetBottom( rR.Top() + nHgt ); + } + } + + if (maGeo.m_nRotationAngle) + { + // Object is rotated. + Point aD1(rR.TopLeft()); + aD1 -= aOldRect.TopLeft(); + Point aD2(aD1); + RotatePoint(aD2, Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + aD2 -= aD1; + rR.Move(aD2.X(), aD2.Y()); + } + + return true; +} + +bool SdrTextObj::NbcAdjustTextFrameWidthAndHeight(bool bHgt, bool bWdt) +{ + tools::Rectangle aRectangle(getRectangle()); + bool bRet = AdjustTextFrameWidthAndHeight(aRectangle, bHgt, bWdt); + setRectangle(aRectangle); + if (bRet) + { + SetBoundAndSnapRectsDirty(); + if (auto pRectObj = dynamic_cast<SdrRectObj *>(this)) { // this is a hack + pRectObj->SetXPolyDirty(); + } + if (auto pCaptionObj = dynamic_cast<SdrCaptionObj *>(this)) { // this is a hack + pCaptionObj->ImpRecalcTail(); + } + } + return bRet; +} + +bool SdrTextObj::AdjustTextFrameWidthAndHeight() +{ + tools::Rectangle aNewRect(getRectangle()); + bool bRet = AdjustTextFrameWidthAndHeight(aNewRect); + if (bRet) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + setRectangle(aNewRect); + SetBoundAndSnapRectsDirty(); + if (auto pRectObj = dynamic_cast<SdrRectObj *>(this)) { // this is a hack + pRectObj->SetXPolyDirty(); + } + bool bScPostIt = false; + if (auto pCaptionObj = dynamic_cast<SdrCaptionObj *>(this)) { // this is a hack + pCaptionObj->ImpRecalcTail(); + // tdf#114956, tdf#138549 use GetSpecialTextBoxShadow to recognize + // that this SdrCaption is for a ScPostit + bScPostIt = pCaptionObj->GetSpecialTextBoxShadow(); + } + + // to not slow down EditView visualization on Overlay (see + // TextEditOverlayObject) it is necessary to suppress the + // Invalidates for the deep repaint when the size of the + // TextFrame changed (AdjustTextFrameWidthAndHeight returned + // true). The ObjectChanges are valid, invalidate will be + // done on EndTextEdit anyways + const bool bSuppressChangeWhenEditOnOverlay( + IsInEditMode() && + GetTextEditOutliner() && + GetTextEditOutliner()->hasEditViewCallbacks()); + + if (!bSuppressChangeWhenEditOnOverlay || bScPostIt) + { + SetChanged(); + BroadcastObjectChange(); + } + + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } + return bRet; +} + +void SdrTextObj::ImpSetTextStyleSheetListeners() +{ + SfxStyleSheetBasePool* pStylePool(getSdrModelFromSdrObject().GetStyleSheetPool()); + if (pStylePool==nullptr) + return; + + std::vector<OUString> aStyleNames; + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if (pOutlinerParaObject!=nullptr) + { + // First, we collect all stylesheets contained in the ParaObject in + // the container aStyles. The Family is always appended to the name + // of the stylesheet. + const EditTextObject& rTextObj=pOutlinerParaObject->GetTextObject(); + OUString aStyleName; + SfxStyleFamily eStyleFam; + sal_Int32 nParaCnt=rTextObj.GetParagraphCount(); + + + for(sal_Int32 nParaNum(0); nParaNum < nParaCnt; nParaNum++) + { + rTextObj.GetStyleSheet(nParaNum, aStyleName, eStyleFam); + + if (!aStyleName.isEmpty()) + { + AppendFamilyToStyleName(aStyleName, eStyleFam); + + bool bFnd(false); + sal_uInt32 nNum(aStyleNames.size()); + + while(!bFnd && nNum > 0) + { + // we don't want duplicate stylesheets + nNum--; + bFnd = aStyleName == aStyleNames[nNum]; + } + + if(!bFnd) + { + aStyleNames.push_back(aStyleName); + } + } + } + } + + // now convert the strings in the vector from names to StyleSheet* + o3tl::sorted_vector<SfxStyleSheet*> aStyleSheets; + while (!aStyleNames.empty()) { + OUString aName = aStyleNames.back(); + aStyleNames.pop_back(); + + SfxStyleFamily eFam = ReadFamilyFromStyleName(aName); + SfxStyleSheetBase* pStyleBase = pStylePool->Find(aName,eFam); + SfxStyleSheet* pStyle = dynamic_cast<SfxStyleSheet*>( pStyleBase ); + if (pStyle!=nullptr && pStyle!=GetStyleSheet()) { + aStyleSheets.insert(pStyle); + } + } + // now remove all superfluous stylesheets + sal_uInt16 nNum=GetBroadcasterCount(); + while (nNum>0) { + nNum--; + SfxBroadcaster* pBroadcast=GetBroadcasterJOE(nNum); + SfxStyleSheet* pStyle=dynamic_cast<SfxStyleSheet*>( pBroadcast ); + if (pStyle!=nullptr && pStyle!=GetStyleSheet()) { // special case for stylesheet of the object + if (aStyleSheets.find(pStyle)==aStyleSheets.end()) { + EndListening(*pStyle); + } + } + } + // and finally, merge all stylesheets that are contained in aStyles with previous broadcasters + for(SfxStyleSheet* pStyle : aStyleSheets) { + // let StartListening see for itself if there's already a listener registered + StartListening(*pStyle, DuplicateHandling::Prevent); + } +} + +/** iterates over the paragraphs of a given SdrObject and removes all + hard set character attributes with the which ids contained in the + given vector +*/ +void SdrTextObj::RemoveOutlinerCharacterAttribs( const std::vector<sal_uInt16>& rCharWhichIds ) +{ + sal_Int32 nText = getTextCount(); + + while( --nText >= 0 ) + { + SdrText* pText = getText( nText ); + OutlinerParaObject* pOutlinerParaObject = pText ? pText->GetOutlinerParaObject() : nullptr; + + if(pOutlinerParaObject) + { + Outliner* pOutliner = nullptr; + + if( mpEditingOutliner || (pText == getActiveText()) ) + pOutliner = mpEditingOutliner; + + if(!pOutliner) + { + pOutliner = &ImpGetDrawOutliner(); + pOutliner->SetText(*pOutlinerParaObject); + } + + ESelection aSelAll( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ); + for( const auto& rWhichId : rCharWhichIds ) + { + pOutliner->RemoveAttribs( aSelAll, false, rWhichId ); + } + + if(!mpEditingOutliner || (pText != getActiveText()) ) + { + const sal_Int32 nParaCount = pOutliner->GetParagraphCount(); + std::optional<OutlinerParaObject> pTemp = pOutliner->CreateParaObject(0, nParaCount); + pOutliner->Clear(); + NbcSetOutlinerParaObjectForText(std::move(pTemp), pText); + } + } + } +} + +bool SdrTextObj::HasText() const +{ + if (mpEditingOutliner) + return HasTextImpl(mpEditingOutliner); + + OutlinerParaObject* pOPO = GetOutlinerParaObject(); + + bool bHasText = false; + if( pOPO ) + { + const EditTextObject& rETO = pOPO->GetTextObject(); + sal_Int32 nParaCount = rETO.GetParagraphCount(); + + if( nParaCount > 0 ) + bHasText = (nParaCount > 1) || (!rETO.GetText( 0 ).isEmpty()); + } + + return bHasText; +} + +void SdrTextObj::AppendFamilyToStyleName(OUString& styleName, SfxStyleFamily family) +{ + OUStringBuffer aFam = OUString::number(static_cast<sal_Int32>(family)); + comphelper::string::padToLength(aFam, PADDING_LENGTH_FOR_STYLE_FAMILY , PADDING_CHARACTER_FOR_STYLE_FAMILY); + + styleName += "|" + aFam; +} + +SfxStyleFamily SdrTextObj::ReadFamilyFromStyleName(std::u16string_view styleName) +{ + std::u16string_view familyString = styleName.substr(styleName.size() - PADDING_LENGTH_FOR_STYLE_FAMILY); + familyString = comphelper::string::stripEnd(familyString, PADDING_CHARACTER_FOR_STYLE_FAMILY); + sal_uInt16 nFam = static_cast<sal_uInt16>(o3tl::toInt32(familyString)); + assert(nFam != 0); + return static_cast<SfxStyleFamily>(nFam); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxdr.cxx b/svx/source/svdraw/svdotxdr.cxx new file mode 100644 index 0000000000..b758b75fe5 --- /dev/null +++ b/svx/source/svdraw/svdotxdr.cxx @@ -0,0 +1,253 @@ +/* -*- 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/svdotext.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svddrag.hxx> +#include <svx/svdview.hxx> +#include <svx/svdorect.hxx> +#include <svx/strings.hrc> +#include <svx/svdoashp.hxx> +#include <tools/bigint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> + + +sal_uInt32 SdrTextObj::GetHdlCount() const +{ + return 8; +} + +void SdrTextObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + for(sal_uInt32 nHdlNum=0; nHdlNum<8; ++nHdlNum) + { + Point aPnt; + SdrHdlKind eKind = SdrHdlKind::UpperLeft; + auto aRectangle = getRectangle(); + switch (nHdlNum) { + case 0: aPnt = aRectangle.TopLeft(); eKind=SdrHdlKind::UpperLeft; break; + case 1: aPnt = aRectangle.TopCenter(); eKind=SdrHdlKind::Upper; break; + case 2: aPnt = aRectangle.TopRight(); eKind=SdrHdlKind::UpperRight; break; + case 3: aPnt = aRectangle.LeftCenter(); eKind=SdrHdlKind::Left ; break; + case 4: aPnt = aRectangle.RightCenter(); eKind=SdrHdlKind::Right; break; + case 5: aPnt = aRectangle.BottomLeft(); eKind=SdrHdlKind::LowerLeft; break; + case 6: aPnt = aRectangle.BottomCenter(); eKind=SdrHdlKind::Lower; break; + case 7: aPnt = aRectangle.BottomRight(); eKind=SdrHdlKind::LowerRight; break; + } + if (maGeo.m_nShearAngle) + ShearPoint(aPnt, aRectangle.TopLeft(), maGeo.mfTanShearAngle); + if (maGeo.m_nRotationAngle) + RotatePoint(aPnt, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + std::unique_ptr<SdrHdl> pH(new SdrHdl(aPnt,eKind)); + pH->SetObj(const_cast<SdrTextObj*>(this)); + pH->SetRotationAngle(maGeo.m_nRotationAngle); + rHdlList.AddHdl(std::move(pH)); + } +} + + +bool SdrTextObj::hasSpecialDrag() const +{ + return true; +} + +tools::Rectangle SdrTextObj::ImpDragCalcRect(const SdrDragStat& rDrag) const +{ + tools::Rectangle aTmpRect(getRectangle()); + const SdrHdl* pHdl=rDrag.GetHdl(); + SdrHdlKind eHdl=pHdl==nullptr ? SdrHdlKind::Move : pHdl->GetKind(); + bool bEcke=(eHdl==SdrHdlKind::UpperLeft || eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::LowerLeft || eHdl==SdrHdlKind::LowerRight); + bool bOrtho=rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho(); + bool bBigOrtho=bEcke && bOrtho && rDrag.GetView()->IsBigOrtho(); + Point aPos(rDrag.GetNow()); + // Unrotate: + if (maGeo.m_nRotationAngle) RotatePoint(aPos,aTmpRect.TopLeft(),-maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + // Unshear: + if (maGeo.m_nShearAngle) ShearPoint(aPos,aTmpRect.TopLeft(),-maGeo.mfTanShearAngle); + + bool bLft=(eHdl==SdrHdlKind::UpperLeft || eHdl==SdrHdlKind::Left || eHdl==SdrHdlKind::LowerLeft); + bool bRgt=(eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::Right || eHdl==SdrHdlKind::LowerRight); + bool bTop=(eHdl==SdrHdlKind::UpperRight || eHdl==SdrHdlKind::Upper || eHdl==SdrHdlKind::UpperLeft); + bool bBtm=(eHdl==SdrHdlKind::LowerRight || eHdl==SdrHdlKind::Lower || eHdl==SdrHdlKind::LowerLeft); + if (bLft) aTmpRect.SetLeft(aPos.X() ); + if (bRgt) aTmpRect.SetRight(aPos.X() ); + if (bTop) aTmpRect.SetTop(aPos.Y() ); + if (bBtm) aTmpRect.SetBottom(aPos.Y() ); + if (bOrtho) { // Ortho + tools::Long nWdt0=getRectangle().Right() - getRectangle().Left(); + tools::Long nHgt0=getRectangle().Bottom() - getRectangle().Top(); + tools::Long nXMul=aTmpRect.Right() -aTmpRect.Left(); + tools::Long nYMul=aTmpRect.Bottom()-aTmpRect.Top(); + tools::Long nXDiv=nWdt0; + tools::Long nYDiv=nHgt0; + bool bXNeg=(nXMul<0)!=(nXDiv<0); + bool bYNeg=(nYMul<0)!=(nYDiv<0); + nXMul=std::abs(nXMul); + nYMul=std::abs(nYMul); + nXDiv=std::abs(nXDiv); + nYDiv=std::abs(nYDiv); + Fraction aXFact(nXMul,nXDiv); // fractions for canceling + Fraction aYFact(nYMul,nYDiv); // and for comparing + nXMul=aXFact.GetNumerator(); + nYMul=aYFact.GetNumerator(); + nXDiv=aXFact.GetDenominator(); + nYDiv=aYFact.GetDenominator(); + if (bEcke) { // corner point handles + bool bUseX=(aXFact<aYFact) != bBigOrtho; + if (bUseX) { + tools::Long nNeed=tools::Long(BigInt(nHgt0)*BigInt(nXMul)/BigInt(nXDiv)); + if (bYNeg) nNeed=-nNeed; + if (bTop) aTmpRect.SetTop(aTmpRect.Bottom()-nNeed ); + if (bBtm) aTmpRect.SetBottom(aTmpRect.Top()+nNeed ); + } else { + tools::Long nNeed=tools::Long(BigInt(nWdt0)*BigInt(nYMul)/BigInt(nYDiv)); + if (bXNeg) nNeed=-nNeed; + if (bLft) aTmpRect.SetLeft(aTmpRect.Right()-nNeed ); + if (bRgt) aTmpRect.SetRight(aTmpRect.Left()+nNeed ); + } + } else { // apex handles + if ((bLft || bRgt) && nXDiv!=0) { + tools::Long nHgt0b=getRectangle().Bottom() - getRectangle().Top(); + tools::Long nNeed=tools::Long(BigInt(nHgt0b)*BigInt(nXMul)/BigInt(nXDiv)); + aTmpRect.AdjustTop( -((nNeed-nHgt0b)/2) ); + aTmpRect.SetBottom(aTmpRect.Top()+nNeed ); + } + if ((bTop || bBtm) && nYDiv!=0) { + tools::Long nWdt0b=getRectangle().Right() - getRectangle().Left(); + tools::Long nNeed=tools::Long(BigInt(nWdt0b)*BigInt(nYMul)/BigInt(nYDiv)); + aTmpRect.AdjustLeft( -((nNeed-nWdt0b)/2) ); + aTmpRect.SetRight(aTmpRect.Left()+nNeed ); + } + } + } + if (dynamic_cast<const SdrObjCustomShape*>(this) == nullptr) // not justifying when in CustomShapes, to be able to detect if a shape has to be mirrored + ImpJustifyRect(aTmpRect); + return aTmpRect; +} + + +// drag + +bool SdrTextObj::applySpecialDrag(SdrDragStat& rDrag) +{ + tools::Rectangle aNewRect(ImpDragCalcRect(rDrag)); + + if(aNewRect.TopLeft() != getRectangle().TopLeft() && (maGeo.m_nRotationAngle || maGeo.m_nShearAngle)) + { + Point aNewPos(aNewRect.TopLeft()); + + if (maGeo.m_nShearAngle) + ShearPoint(aNewPos, getRectangle().TopLeft(), maGeo.mfTanShearAngle); + + if (maGeo.m_nRotationAngle) + RotatePoint(aNewPos, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); + + aNewRect.SetPos(aNewPos); + } + + if (aNewRect != getRectangle()) + { + NbcSetLogicRect(aNewRect); + } + + return true; +} + +OUString SdrTextObj::getSpecialDragComment(const SdrDragStat& /*rDrag*/) const +{ + return ImpGetDescriptionStr(STR_DragRectResize); +} + + +// Create + +bool SdrTextObj::BegCreate(SdrDragStat& rStat) +{ + rStat.SetOrtho4Possible(); + tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow()); + aRect1.Normalize(); + rStat.SetActionRect(aRect1); + setRectangle(aRect1); + return true; +} + +bool SdrTextObj::MovCreate(SdrDragStat& rStat) +{ + tools::Rectangle aRect1; + rStat.TakeCreateRect(aRect1); + ImpJustifyRect(aRect1); + rStat.SetActionRect(aRect1); + setRectangle(aRect1); // for ObjName + SetBoundRectDirty(); + m_bSnapRectDirty=true; + if (auto pRectObj = dynamic_cast<SdrRectObj *>(this)) { + pRectObj->SetXPolyDirty(); + } + return true; +} + +bool SdrTextObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + tools::Rectangle aRectangle(getRectangle()); + rStat.TakeCreateRect(aRectangle); + ImpJustifyRect(aRectangle); + setRectangle(aRectangle); + + AdaptTextMinSize(); + + SetBoundAndSnapRectsDirty(); + if (auto pRectObj = dynamic_cast<SdrRectObj *>(this)) { + pRectObj->SetXPolyDirty(); + } + return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2); +} + +void SdrTextObj::BrkCreate(SdrDragStat& /*rStat*/) +{ +} + +bool SdrTextObj::BckCreate(SdrDragStat& /*rStat*/) +{ + return true; +} + +basegfx::B2DPolyPolygon SdrTextObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + tools::Rectangle aRect1; + rDrag.TakeCreateRect(aRect1); + aRect1.Normalize(); + + basegfx::B2DPolyPolygon aRetval; + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aRect1); + aRetval.append(basegfx::utils::createPolygonFromRect(aRange)); + return aRetval; +} + +PointerStyle SdrTextObj::GetCreatePointer() const +{ + if (IsTextFrame()) return PointerStyle::DrawText; + return PointerStyle::Cross; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxed.cxx b/svx/source/svdraw/svdotxed.cxx new file mode 100644 index 0000000000..120bf26b17 --- /dev/null +++ b/svx/source/svdraw/svdotxed.cxx @@ -0,0 +1,360 @@ +/* -*- 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/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editstat.hxx> +#include <svl/itemset.hxx> +#include <editeng/eeitem.hxx> +#include <svx/sdtfchim.hxx> +#include <textchain.hxx> + + +bool SdrTextObj::HasTextEdit() const +{ + // linked text objects may be changed (no automatic reload) + return true; +} + +bool SdrTextObj::BegTextEdit(SdrOutliner& rOutl) +{ + if (mpEditingOutliner!=nullptr) return false; // Textedit might already run in another View! + mpEditingOutliner=&rOutl; + + mbInEditMode = true; + + OutlinerMode nOutlinerMode = OutlinerMode::OutlineObject; + if ( !IsOutlText() ) + nOutlinerMode = OutlinerMode::TextObject; + rOutl.Init( nOutlinerMode ); + rOutl.SetRefDevice(getSdrModelFromSdrObject().GetRefDevice()); + + bool bFitToSize(IsFitToSize()); + bool bContourFrame=IsContourTextFrame(); + ImpSetTextEditParams(); + + if (!bContourFrame) { + EEControlBits nStat=rOutl.GetControlWord(); + nStat|=EEControlBits::AUTOPAGESIZE; + if (bFitToSize || IsAutoFit()) + nStat|=EEControlBits::STRETCHING; + else + nStat&=~EEControlBits::STRETCHING; + rOutl.SetControlWord(nStat); + } + + // disable AUTOPAGESIZE if IsChainable (might be required for overflow check) + if ( IsChainable() ) { + EEControlBits nStat1=rOutl.GetControlWord(); + nStat1 &=~EEControlBits::AUTOPAGESIZE; + rOutl.SetControlWord(nStat1); + } + + + OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject(); + if(pOutlinerParaObject!=nullptr) + { + rOutl.SetText(*GetOutlinerParaObject()); + rOutl.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + + // if necessary, set frame attributes for the first (new) paragraph of the + // outliner + if( !HasTextImpl( &rOutl ) ) + { + // Outliner has no text so we must set some + // empty text so the outliner initialise itself + rOutl.SetText( "", rOutl.GetParagraph( 0 ) ); + + if(GetStyleSheet()) + rOutl.SetStyleSheet( 0, GetStyleSheet()); + + // When setting the "hard" attributes for first paragraph, the Parent + // pOutlAttr (i. e. the template) has to be removed temporarily. Else, + // at SetParaAttribs(), all attributes contained in the parent become + // attributed hard to the paragraph. + const SfxItemSet& rSet = GetObjectItemSet(); + SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END> aFilteredSet(*rSet.GetPool()); + aFilteredSet.Put(rSet); + rOutl.SetParaAttribs(0, aFilteredSet); + } + if (bFitToSize) + { + tools::Rectangle aAnchorRect; + tools::Rectangle aTextRect; + TakeTextRect(rOutl, aTextRect, false, + &aAnchorRect); + Fraction aFitXCorrection(1,1); + ImpSetCharStretching(rOutl,aTextRect.GetSize(),aAnchorRect.GetSize(),aFitXCorrection); + } + else if (IsAutoFit()) + { + ImpAutoFitText(rOutl); + } + + if(pOutlinerParaObject) + { + if (maGeo.m_nRotationAngle || IsFontwork()) + { + // only repaint here, no real objectchange + BroadcastObjectChange(); + } + } + + rOutl.UpdateFields(); + rOutl.ClearModifyFlag(); + + return true; +} + +void SdrTextObj::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const +{ + bool bFitToSize(IsFitToSize()); + Size aPaperMin,aPaperMax; + tools::Rectangle aViewInit; + TakeTextAnchorRect(aViewInit); + if (maGeo.m_nRotationAngle) { + Point aCenter(aViewInit.Center()); + aCenter-=aViewInit.TopLeft(); + Point aCenter0(aCenter); + RotatePoint(aCenter,Point(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle); + aCenter-=aCenter0; + aViewInit.Move(aCenter.X(),aCenter.Y()); + } + Size aAnkSiz(aViewInit.GetSize()); + aAnkSiz.AdjustWidth( -1 ); aAnkSiz.AdjustHeight( -1 ); // because GetSize() adds 1 + Size aMaxSiz(1000000,1000000); + Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize()); + if (aTmpSiz.Width()!=0) aMaxSiz.setWidth(aTmpSiz.Width() ); + if (aTmpSiz.Height()!=0) aMaxSiz.setHeight(aTmpSiz.Height() ); + + // Done earlier since used in else tree below + SdrTextHorzAdjust eHAdj(GetTextHorizontalAdjust()); + SdrTextVertAdjust eVAdj(GetTextVerticalAdjust()); + + if(IsTextFrame()) + { + tools::Long nMinWdt=GetMinTextFrameWidth(); + tools::Long nMinHgt=GetMinTextFrameHeight(); + tools::Long nMaxWdt=GetMaxTextFrameWidth(); + tools::Long nMaxHgt=GetMaxTextFrameHeight(); + if (nMinWdt<1) nMinWdt=1; + if (nMinHgt<1) nMinHgt=1; + if (!bFitToSize) { + if (nMaxWdt==0 || nMaxWdt>aMaxSiz.Width()) nMaxWdt=aMaxSiz.Width(); + if (nMaxHgt==0 || nMaxHgt>aMaxSiz.Height()) nMaxHgt=aMaxSiz.Height(); + + if (!IsAutoGrowWidth() ) + { + nMinWdt = aAnkSiz.Width(); + nMaxWdt = nMinWdt; + } + + if (!IsAutoGrowHeight()) + { + nMinHgt = aAnkSiz.Height(); + nMaxHgt = nMinHgt; + } + + SdrTextAniKind eAniKind=GetTextAniKind(); + SdrTextAniDirection eAniDirection=GetTextAniDirection(); + + bool bInEditMode = IsInEditMode(); + + if (!bInEditMode && (eAniKind==SdrTextAniKind::Scroll || eAniKind==SdrTextAniKind::Alternate || eAniKind==SdrTextAniKind::Slide)) + { + // ticker text uses an unlimited paper size + if (eAniDirection==SdrTextAniDirection::Left || eAniDirection==SdrTextAniDirection::Right) nMaxWdt=1000000; + if (eAniDirection==SdrTextAniDirection::Up || eAniDirection==SdrTextAniDirection::Down) nMaxHgt=1000000; + } + + bool bChainedFrame = IsChainable(); + // Might be required for overflow check working: do limit height to frame if box is chainable. + if (!bChainedFrame) { + // #i119885# Do not limit/force height to geometrical frame (vice versa for vertical writing) + if(IsVerticalWriting()) + { + nMaxWdt = 1000000; + } + else + { + nMaxHgt = 1000000; + } + } + + aPaperMax.setWidth(nMaxWdt ); + aPaperMax.setHeight(nMaxHgt ); + } + else + { + aPaperMax=aMaxSiz; + } + aPaperMin.setWidth(nMinWdt ); + aPaperMin.setHeight(nMinHgt ); + } + else + { + // aPaperMin needs to be set to object's size if full width is activated + // for hor or ver writing respectively + if((SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting()) + || (SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting())) + { + aPaperMin = aAnkSiz; + } + + aPaperMax=aMaxSiz; + } + + if (pViewMin!=nullptr) { + *pViewMin=aViewInit; + + tools::Long nXFree=aAnkSiz.Width()-aPaperMin.Width(); + if (eHAdj==SDRTEXTHORZADJUST_LEFT) pViewMin->AdjustRight( -nXFree ); + else if (eHAdj==SDRTEXTHORZADJUST_RIGHT) pViewMin->AdjustLeft(nXFree ); + else { pViewMin->AdjustLeft(nXFree/2 ); pViewMin->SetRight(pViewMin->Left()+aPaperMin.Width() ); } + + tools::Long nYFree=aAnkSiz.Height()-aPaperMin.Height(); + if (eVAdj==SDRTEXTVERTADJUST_TOP) pViewMin->AdjustBottom( -nYFree ); + else if (eVAdj==SDRTEXTVERTADJUST_BOTTOM) pViewMin->AdjustTop(nYFree ); + else { pViewMin->AdjustTop(nYFree/2 ); pViewMin->SetBottom(pViewMin->Top()+aPaperMin.Height() ); } + } + + // PaperSize should grow automatically in most cases + if(IsVerticalWriting()) + aPaperMin.setWidth( 0 ); + else + aPaperMin.setHeight( 0 ); + + if(eHAdj!=SDRTEXTHORZADJUST_BLOCK || bFitToSize) { + aPaperMin.setWidth(0 ); + } + + // For complete vertical adjustment support, set paper min height to 0, here. + if(SDRTEXTVERTADJUST_BLOCK != eVAdj || bFitToSize) + { + aPaperMin.setHeight( 0 ); + } + + if (pPaperMin!=nullptr) *pPaperMin=aPaperMin; + if (pPaperMax!=nullptr) *pPaperMax=aPaperMax; + if (pViewInit!=nullptr) *pViewInit=aViewInit; +} + +void SdrTextObj::EndTextEdit(SdrOutliner& rOutl) +{ + if(rOutl.IsModified()) + { + + // to make the gray field background vanish again + rOutl.UpdateFields(); + + std::optional<OutlinerParaObject> pNewText = rOutl.CreateParaObject( 0, rOutl.GetParagraphCount() ); + + // need to end edit mode early since SetOutlinerParaObject already + // uses GetCurrentBoundRect() which needs to take the text into account + // to work correct + mbInEditMode = false; + + // We don't want broadcasting if we are merely trying to move to next box (this prevents infinite loops) + if (IsChainable() && GetTextChain()->GetSwitchingToNextBox(this)) { + GetTextChain()->SetSwitchingToNextBox(this, false); + if( getActiveText() ) + { + getActiveText()->SetOutlinerParaObject( std::move(pNewText) ); + } + } else { // If we are not doing in-chaining switching just set the ParaObject + SetOutlinerParaObject(std::move(pNewText)); + } + } + + /* Chaining-related code */ + rOutl.ClearOverflowingParaNum(); + + mpEditingOutliner = nullptr; + rOutl.Clear(); + EEControlBits nStat = rOutl.GetControlWord(); + nStat &= ~EEControlBits::AUTOPAGESIZE; + rOutl.SetControlWord(nStat); + + mbInEditMode = false; +} + +EEAnchorMode SdrTextObj::GetOutlinerViewAnchorMode() const +{ + SdrTextHorzAdjust eH=GetTextHorizontalAdjust(); + SdrTextVertAdjust eV=GetTextVerticalAdjust(); + EEAnchorMode eRet=EEAnchorMode::TopLeft; + if (IsContourTextFrame()) return eRet; + if (eH==SDRTEXTHORZADJUST_LEFT) { + if (eV==SDRTEXTVERTADJUST_TOP) { + eRet=EEAnchorMode::TopLeft; + } else if (eV==SDRTEXTVERTADJUST_BOTTOM) { + eRet=EEAnchorMode::BottomLeft; + } else { + eRet=EEAnchorMode::VCenterLeft; + } + } else if (eH==SDRTEXTHORZADJUST_RIGHT) { + if (eV==SDRTEXTVERTADJUST_TOP) { + eRet=EEAnchorMode::TopRight; + } else if (eV==SDRTEXTVERTADJUST_BOTTOM) { + eRet=EEAnchorMode::BottomRight; + } else { + eRet=EEAnchorMode::VCenterRight; + } + } else { + if (eV==SDRTEXTVERTADJUST_TOP) { + eRet=EEAnchorMode::TopHCenter; + } else if (eV==SDRTEXTVERTADJUST_BOTTOM) { + eRet=EEAnchorMode::BottomHCenter; + } else { + eRet=EEAnchorMode::VCenterHCenter; + } + } + return eRet; +} + +void SdrTextObj::ImpSetTextEditParams() const +{ + if (mpEditingOutliner==nullptr) + return; + + bool bUpdBuf=mpEditingOutliner->SetUpdateLayout(false); + Size aPaperMin; + Size aPaperMax; + tools::Rectangle aEditArea; + TakeTextEditArea(&aPaperMin,&aPaperMax,&aEditArea,nullptr); + bool bContourFrame=IsContourTextFrame(); + mpEditingOutliner->SetMinAutoPaperSize(aPaperMin); + mpEditingOutliner->SetMaxAutoPaperSize(aPaperMax); + mpEditingOutliner->SetPaperSize(Size()); + mpEditingOutliner->SetTextColumns(GetTextColumnsNumber(), GetTextColumnsSpacing()); + if (bContourFrame) { + tools::Rectangle aAnchorRect; + TakeTextAnchorRect(aAnchorRect); + ImpSetContourPolygon(*mpEditingOutliner,aAnchorRect, true); + } + if (bUpdBuf) mpEditingOutliner->SetUpdateLayout(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxfl.cxx b/svx/source/svdraw/svdotxfl.cxx new file mode 100644 index 0000000000..6c61d7c824 --- /dev/null +++ b/svx/source/svdraw/svdotxfl.cxx @@ -0,0 +1,28 @@ +/* -*- 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/svdotext.hxx> + +bool SdrTextObj::CalcFieldValue(const SvxFieldItem& /*rField*/, sal_Int32 /*nPara*/, sal_uInt16 /*nPos*/, + bool /*bEdit*/, std::optional<Color>& /*rpTxtColor*/, std::optional<Color>& /*rpFldColor*/, std::optional<FontLineStyle>& /*rpFldLineStyle*/, OUString& /*rRet*/) const +{ + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxln.cxx b/svx/source/svdraw/svdotxln.cxx new file mode 100644 index 0000000000..44a55fdb4e --- /dev/null +++ b/svx/source/svdraw/svdotxln.cxx @@ -0,0 +1,279 @@ +/* -*- 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 <sal/config.h> + +#include <comphelper/processfactory.hxx> +#include <osl/file.hxx> +#include <osl/thread.h> +#include <unotools/ucbstreamhelper.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/datetime.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <editeng/editdata.hxx> +#include <sfx2/lnkbase.hxx> +#include <sfx2/linkmgr.hxx> +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <tools/tenccvt.hxx> +#include <memory> + +class ImpSdrObjTextLink: public ::sfx2::SvBaseLink +{ + SdrTextObj* pSdrObj; + +public: + explicit ImpSdrObjTextLink( SdrTextObj* pObj1 ) + : ::sfx2::SvBaseLink( ::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE ), + pSdrObj( pObj1 ) + {} + + virtual void Closed() override; + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) override; +}; + +void ImpSdrObjTextLink::Closed() +{ + if (pSdrObj ) + { + // set pLink of the object to NULL, because we are destroying the link instance now + ImpSdrObjTextLinkUserData* pData=pSdrObj->GetLinkUserData(); + if (pData!=nullptr) pData->mpLink = nullptr; + pSdrObj->ReleaseTextLink(); + } + SvBaseLink::Closed(); +} + + +::sfx2::SvBaseLink::UpdateResult ImpSdrObjTextLink::DataChanged( + const OUString& /*rMimeType*/, const css::uno::Any & /*rValue */) +{ + bool bForceReload = false; + SdrModel* pModel(pSdrObj ? &pSdrObj->getSdrModelFromSdrObject() : nullptr); + sfx2::LinkManager* pLinkManager(pModel ? pModel->GetLinkManager() : nullptr); + + if( pLinkManager ) + { + ImpSdrObjTextLinkUserData* pData=pSdrObj->GetLinkUserData(); + if( pData ) + { + OUString aFile; + OUString aFilter; + sfx2::LinkManager::GetDisplayNames( this, nullptr,&aFile, nullptr, &aFilter ); + + if( pData->maFileName != aFile || + pData->maFilterName != aFilter ) + { + pData->maFileName = aFile; + pData->maFilterName = aFilter; + pSdrObj->SetChanged(); + bForceReload = true; + } + } + } + if (pSdrObj ) + pSdrObj->ReloadLinkedText( bForceReload ); + + return SUCCESS; +} + + +ImpSdrObjTextLinkUserData::ImpSdrObjTextLinkUserData(): + SdrObjUserData(SdrInventor::Default,SDRUSERDATA_OBJTEXTLINK), + maFileDate0( DateTime::EMPTY ), + meCharSet(RTL_TEXTENCODING_DONTKNOW) +{ +} + +ImpSdrObjTextLinkUserData::~ImpSdrObjTextLinkUserData() +{ +} + +std::unique_ptr<SdrObjUserData> ImpSdrObjTextLinkUserData::Clone(SdrObject* ) const +{ + ImpSdrObjTextLinkUserData* pData = new ImpSdrObjTextLinkUserData; + pData->maFileName = maFileName; + pData->maFilterName = maFilterName; + pData->maFileDate0 = maFileDate0; + pData->meCharSet = meCharSet; + pData->mpLink = nullptr; + return std::unique_ptr<SdrObjUserData>(pData); +} + + +void SdrTextObj::SetTextLink(const OUString& rFileName, const OUString& rFilterName) +{ + rtl_TextEncoding eCharSet = osl_getThreadTextEncoding(); + + ImpSdrObjTextLinkUserData* pData=GetLinkUserData(); + if (pData!=nullptr) { + ReleaseTextLink(); + } + pData=new ImpSdrObjTextLinkUserData; + pData->maFileName = rFileName; + pData->maFilterName = rFilterName; + pData->meCharSet = eCharSet; + AppendUserData(std::unique_ptr<SdrObjUserData>(pData)); + ImpRegisterLink(); +} + +void SdrTextObj::ReleaseTextLink() +{ + ImpDeregisterLink(); + sal_uInt16 nCount=GetUserDataCount(); + for (sal_uInt16 nNum=nCount; nNum>0;) { + nNum--; + SdrObjUserData* pData=GetUserData(nNum); + if (pData->GetInventor()==SdrInventor::Default && pData->GetId()==SDRUSERDATA_OBJTEXTLINK) { + DeleteUserData(nNum); + } + } +} + +bool SdrTextObj::ReloadLinkedText( bool bForceLoad) +{ + ImpSdrObjTextLinkUserData* pData = GetLinkUserData(); + bool bRet = true; + + if( pData ) + { + DateTime aFileDT( DateTime::EMPTY ); + bool bExists = true; + + try + { + INetURLObject aURL( pData->maFileName ); + DBG_ASSERT( aURL.GetProtocol() != INetProtocol::NotValid, "invalid URL" ); + + ::ucbhelper::Content aCnt( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + css::uno::Any aAny( aCnt.getPropertyValue("DateModified") ); + css::util::DateTime aDateTime; + + aAny >>= aDateTime; + ::utl::typeConvert( aDateTime, aFileDT ); + } + catch( ... ) + { + bExists = false; + } + + if( bExists ) + { + bool bLoad = false; + if( bForceLoad ) + bLoad = true; + else + bLoad = ( aFileDT > pData->maFileDate0 ); + + if( bLoad ) + { + bRet = LoadText( pData->maFileName, pData->meCharSet ); + } + + pData->maFileDate0 = aFileDT; + } + } + + return bRet; +} + +bool SdrTextObj::LoadText(const OUString& rFileName, rtl_TextEncoding eCharSet) +{ + INetURLObject aFileURL( rFileName ); + bool bRet = false; + + if( aFileURL.GetProtocol() == INetProtocol::NotValid ) + { + OUString aFileURLStr; + + if( osl::FileBase::getFileURLFromSystemPath( rFileName, aFileURLStr ) == osl::FileBase::E_None ) + aFileURL = INetURLObject( aFileURLStr ); + else + aFileURL.SetSmartURL( rFileName ); + } + + DBG_ASSERT( aFileURL.GetProtocol() != INetProtocol::NotValid, "invalid URL" ); + + std::unique_ptr<SvStream> pIStm(::utl::UcbStreamHelper::CreateStream( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::READ )); + + if( pIStm ) + { + pIStm->SetStreamCharSet(GetSOLoadTextEncoding(eCharSet)); + + char cRTF[5]; + cRTF[4] = 0; + pIStm->ReadBytes(cRTF, 5); + + bool bRTF = cRTF[0] == '{' && cRTF[1] == '\\' && cRTF[2] == 'r' && cRTF[3] == 't' && cRTF[4] == 'f'; + + pIStm->Seek(0); + + if( !pIStm->GetError() ) + { + SetText( *pIStm, aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), bRTF ? EETextFormat::Rtf : EETextFormat::Text ); + bRet = true; + } + } + + return bRet; +} + +ImpSdrObjTextLinkUserData* SdrTextObj::GetLinkUserData() const +{ + sal_uInt16 nCount=GetUserDataCount(); + for (sal_uInt16 nNum=nCount; nNum>0;) { + nNum--; + SdrObjUserData * pData=GetUserData(nNum); + if (pData->GetInventor() == SdrInventor::Default + && pData->GetId() == SDRUSERDATA_OBJTEXTLINK) + { + return static_cast<ImpSdrObjTextLinkUserData *>(pData); + } + } + return nullptr; +} + +void SdrTextObj::ImpRegisterLink() +{ + ImpSdrObjTextLinkUserData* pData=GetLinkUserData(); + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + if (pLinkManager!=nullptr && pData!=nullptr && pData->mpLink==nullptr) { // don't register twice + pData->mpLink = new ImpSdrObjTextLink(this); + pLinkManager->InsertFileLink(*pData->mpLink,sfx2::SvBaseLinkObjectType::ClientFile,pData->maFileName, + !pData->maFilterName.isEmpty() ? + &pData->maFilterName : nullptr); + } +} + +void SdrTextObj::ImpDeregisterLink() +{ + ImpSdrObjTextLinkUserData* pData=GetLinkUserData(); + if (!pData) + return; + sfx2::LinkManager* pLinkManager(getSdrModelFromSdrObject().GetLinkManager()); + if (pLinkManager!=nullptr && pData->mpLink!=nullptr) { // don't register twice + // when doing Remove, *pLink is deleted implicitly + pLinkManager->Remove( pData->mpLink.get() ); + pData->mpLink=nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdotxtr.cxx b/svx/source/svdraw/svdotxtr.cxx new file mode 100644 index 0000000000..fbe6b0b357 --- /dev/null +++ b/svx/source/svdraw/svdotxtr.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 <svx/svdotext.hxx> +#include <svx/svdtrans.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdmodel.hxx> +#include <sdr/properties/itemsettools.hxx> +#include <svx/sdr/properties/properties.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svl/itemset.hxx> +#include <drawinglayer/processor2d/textaspolygonextractor2d.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/sdshitm.hxx> +#include <unotools/configmgr.hxx> + +using namespace com::sun::star; + +void SdrTextObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + if (maGeo.m_nRotationAngle || maGeo.m_nShearAngle) + { + // Either the rotation or shear angle exists. + tools::Rectangle aSR0(GetSnapRect()); + tools::Long nWdt0=aSR0.Right()-aSR0.Left(); + tools::Long nHgt0=aSR0.Bottom()-aSR0.Top(); + tools::Long nWdt1=rRect.Right()-rRect.Left(); + tools::Long nHgt1=rRect.Bottom()-rRect.Top(); + SdrTextObj::NbcResize(maSnapRect.TopLeft(),Fraction(nWdt1,nWdt0),Fraction(nHgt1,nHgt0)); + SdrTextObj::NbcMove(Size(rRect.Left()-aSR0.Left(),rRect.Top()-aSR0.Top())); + } + else + { + // No rotation or shear. + + setRectangle(rRect); + ImpJustifyRect(maRectangle); + + AdaptTextMinSize(); + + ImpCheckShear(); + SetBoundAndSnapRectsDirty(); + } +} + +const tools::Rectangle& SdrTextObj::GetLogicRect() const +{ + return getRectangle(); +} + +void SdrTextObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + setRectangle(rRect); + ImpJustifyRect(maRectangle); + + AdaptTextMinSize(); + + SetBoundAndSnapRectsDirty(); +} + +Degree100 SdrTextObj::GetRotateAngle() const +{ + return maGeo.m_nRotationAngle; +} + +Degree100 SdrTextObj::GetShearAngle(bool /*bVertical*/) const +{ + return maGeo.m_nShearAngle; +} + +void SdrTextObj::NbcMove(const Size& rSize) +{ + moveRectangle(rSize.Width(), rSize.Height()); + moveOutRectangle(rSize.Width(), rSize.Height()); + maSnapRect.Move(rSize); + SetBoundAndSnapRectsDirty(true); +} + +void SdrTextObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + bool bNotSheared=maGeo.m_nShearAngle==0_deg100; + bool bRotate90=bNotSheared && maGeo.m_nRotationAngle.get() % 9000 ==0; + bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0); + bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0); + if (bXMirr || bYMirr) { + Point aRef1(GetSnapRect().Center()); + if (bXMirr) { + Point aRef2(aRef1); + aRef2.AdjustY( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + if (bYMirr) { + Point aRef2(aRef1); + aRef2.AdjustX( 1 ); + NbcMirrorGluePoints(aRef1,aRef2); + } + } + + if (maGeo.m_nRotationAngle==0_deg100 && maGeo.m_nShearAngle==0_deg100) { + auto aRectangle = getRectangle(); + ResizeRect(aRectangle, rRef, xFact, yFact); + setRectangle(aRectangle); + if (bYMirr) + { + maRectangle.Normalize(); + moveRectangle(aRectangle.Right() - aRectangle.Left(), aRectangle.Bottom() - aRectangle.Top()); + maGeo.m_nRotationAngle=18000_deg100; + maGeo.RecalcSinCos(); + } + } + else + { + tools::Polygon aPol(Rect2Poly(getRectangle(), maGeo)); + + for(sal_uInt16 a(0); a < aPol.GetSize(); a++) + { + ResizePoint(aPol[a], rRef, xFact, yFact); + } + + if(bXMirr != bYMirr) + { + // turn polygon and move it a little + tools::Polygon aPol0(aPol); + + aPol[0] = aPol0[1]; + aPol[1] = aPol0[0]; + aPol[2] = aPol0[3]; + aPol[3] = aPol0[2]; + aPol[4] = aPol0[1]; + } + tools::Rectangle aRectangle = svx::polygonToRectangle(aPol, maGeo); + setRectangle(aRectangle); + } + + if (bRotate90) { + bool bRota90=maGeo.m_nRotationAngle.get() % 9000 ==0; + if (!bRota90) { // there's seems to be a rounding error occurring: correct it + Degree100 a=NormAngle36000(maGeo.m_nRotationAngle); + if (a<4500_deg100) a=0_deg100; + else if (a<13500_deg100) a=9000_deg100; + else if (a<22500_deg100) a=18000_deg100; + else if (a<31500_deg100) a=27000_deg100; + else a=0_deg100; + maGeo.m_nRotationAngle=a; + maGeo.RecalcSinCos(); + } + if (bNotSheared!=(maGeo.m_nShearAngle==0_deg100)) { // correct a rounding error occurring with Shear + maGeo.m_nShearAngle=0_deg100; + maGeo.RecalcTan(); + } + } + + ImpJustifyRect(maRectangle); + + AdaptTextMinSize(); + + if(mbTextFrame && !getSdrModelFromSdrObject().IsPasteResize()) + { + NbcAdjustTextFrameWidthAndHeight(); + } + + ImpCheckShear(); + SetBoundAndSnapRectsDirty(); +} + +void SdrTextObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + SetGlueReallyAbsolute(true); + tools::Long dx = getRectangle().Right() - getRectangle().Left(); + tools::Long dy = getRectangle().Bottom() - getRectangle().Top(); + Point aPoint1(getRectangle().TopLeft()); + RotatePoint(aPoint1, rRef, sn, cs); + Point aPoint2(aPoint1.X() + dx, aPoint1.Y() + dy); + tools::Rectangle aRectangle(aPoint1, aPoint2); + setRectangle(aRectangle); + + if (maGeo.m_nRotationAngle==0_deg100) { + maGeo.m_nRotationAngle=NormAngle36000(nAngle); + maGeo.mfSinRotationAngle=sn; + maGeo.mfCosRotationAngle=cs; + } else { + maGeo.m_nRotationAngle=NormAngle36000(maGeo.m_nRotationAngle+nAngle); + maGeo.RecalcSinCos(); + } + SetBoundAndSnapRectsDirty(); + NbcRotateGluePoints(rRef,nAngle,sn,cs); + SetGlueReallyAbsolute(false); +} + +void SdrTextObj::NbcShear(const Point& rRef, Degree100 /*nAngle*/, double tn, bool bVShear) +{ + SetGlueReallyAbsolute(true); + + // when this is a SdrPathObj, aRect may be uninitialized + tools::Polygon aPol(Rect2Poly(getRectangle().IsEmpty() ? GetSnapRect() : getRectangle(), maGeo)); + + sal_uInt16 nPointCount=aPol.GetSize(); + for (sal_uInt16 i=0; i<nPointCount; i++) { + ShearPoint(aPol[i],rRef,tn,bVShear); + } + tools::Rectangle aRectangle = svx::polygonToRectangle(aPol, maGeo); + setRectangle(aRectangle); + ImpJustifyRect(maRectangle); + + if (mbTextFrame) { + NbcAdjustTextFrameWidthAndHeight(); + } + ImpCheckShear(); + SetBoundAndSnapRectsDirty(); + NbcShearGluePoints(rRef,tn,bVShear); + SetGlueReallyAbsolute(false); +} + +void SdrTextObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + SetGlueReallyAbsolute(true); + bool bNotSheared=maGeo.m_nShearAngle==0_deg100; + bool bRotate90 = false; + if (bNotSheared && + (rRef1.X()==rRef2.X() || rRef1.Y()==rRef2.Y() || + std::abs(rRef1.X()-rRef2.X())==std::abs(rRef1.Y()-rRef2.Y()))) { + bRotate90=maGeo.m_nRotationAngle.get() % 9000 ==0; + } + tools::Polygon aPol(Rect2Poly(getRectangle(),maGeo)); + sal_uInt16 i; + sal_uInt16 nPointCount=aPol.GetSize(); + for (i=0; i<nPointCount; i++) { + MirrorPoint(aPol[i],rRef1,rRef2); + } + // turn polygon and move it a little + tools::Polygon aPol0(aPol); + aPol[0]=aPol0[1]; + aPol[1]=aPol0[0]; + aPol[2]=aPol0[3]; + aPol[3]=aPol0[2]; + aPol[4]=aPol0[1]; + tools::Rectangle aRectangle = svx::polygonToRectangle(aPol, maGeo); + setRectangle(aRectangle); + + if (bRotate90) { + bool bRota90=maGeo.m_nRotationAngle.get() % 9000 ==0; + if (bRotate90 && !bRota90) { // there's seems to be a rounding error occurring: correct it + Degree100 a=NormAngle36000(maGeo.m_nRotationAngle); + if (a<4500_deg100) a=0_deg100; + else if (a<13500_deg100) a=9000_deg100; + else if (a<22500_deg100) a=18000_deg100; + else if (a<31500_deg100) a=27000_deg100; + else a=0_deg100; + maGeo.m_nRotationAngle=a; + maGeo.RecalcSinCos(); + } + } + if (bNotSheared!=(maGeo.m_nShearAngle==0_deg100)) { // correct a rounding error occurring with Shear + maGeo.m_nShearAngle=0_deg100; + maGeo.RecalcTan(); + } + + ImpJustifyRect(maRectangle); + if (mbTextFrame) { + NbcAdjustTextFrameWidthAndHeight(); + } + ImpCheckShear(); + SetBoundAndSnapRectsDirty(); + NbcMirrorGluePoints(rRef1,rRef2); + SetGlueReallyAbsolute(false); +} + + +rtl::Reference<SdrObject> SdrTextObj::ImpConvertContainedTextToSdrPathObjs(bool bToPoly) const +{ + rtl::Reference<SdrObject> pRetval; + + if(!ImpCanConvTextToCurve()) + { + // suppress HelpTexts from PresObj's + return nullptr; + } + + // create an extractor with neutral ViewInformation + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + drawinglayer::processor2d::TextAsPolygonExtractor2D aExtractor(aViewInformation2D); + + // extract text as polygons + GetViewContact().getViewIndependentPrimitive2DContainer(aExtractor); + + // get results + const drawinglayer::processor2d::TextAsPolygonDataNodeVector& rResult = aExtractor.getTarget(); + const sal_uInt32 nResultCount(rResult.size()); + + if(nResultCount) + { + // prepare own target + rtl::Reference<SdrObjGroup> pGroup = new SdrObjGroup(getSdrModelFromSdrObject()); + SdrObjList* pObjectList = pGroup->GetSubList(); + + // process results + for(sal_uInt32 a(0); a < nResultCount; a++) + { + const drawinglayer::processor2d::TextAsPolygonDataNode& rCandidate = rResult[a]; + basegfx::B2DPolyPolygon aPolyPolygon(rCandidate.getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // take care of wanted polygon type + if(bToPoly) + { + if(aPolyPolygon.areControlPointsUsed()) + { + aPolyPolygon = basegfx::utils::adaptiveSubdivideByAngle(aPolyPolygon); + } + } + else + { + if(!aPolyPolygon.areControlPointsUsed()) + { + aPolyPolygon = basegfx::utils::expandToCurve(aPolyPolygon); + } + } + + // create ItemSet with object attributes + SfxItemSet aAttributeSet(GetObjectItemSet()); + rtl::Reference<SdrPathObj> pPathObj; + + // always clear objectshadow; this is included in the extraction + aAttributeSet.Put(makeSdrShadowItem(false)); + + if(rCandidate.getIsFilled()) + { + // set needed items + aAttributeSet.Put(XFillColorItem(OUString(), Color(rCandidate.getBColor()))); + aAttributeSet.Put(XLineStyleItem(drawing::LineStyle_NONE)); + aAttributeSet.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + + // create filled SdrPathObj + pPathObj = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathFill, + aPolyPolygon); + } + else + { + // set needed items + aAttributeSet.Put(XLineColorItem(OUString(), Color(rCandidate.getBColor()))); + aAttributeSet.Put(XLineStyleItem(drawing::LineStyle_SOLID)); + aAttributeSet.Put(XLineWidthItem(0)); + aAttributeSet.Put(XFillStyleItem(drawing::FillStyle_NONE)); + + // create line SdrPathObj + pPathObj = new SdrPathObj( + getSdrModelFromSdrObject(), + SdrObjKind::PathLine, + std::move(aPolyPolygon)); + } + + // copy basic information from original + pPathObj->ImpSetAnchorPos(GetAnchorPos()); + pPathObj->NbcSetLayer(GetLayer()); + pPathObj->NbcSetStyleSheet(GetStyleSheet(), true); + + // apply prepared ItemSet and add to target + pPathObj->SetMergedItemSet(aAttributeSet); + pObjectList->InsertObject(pPathObj.get()); + } + } + + // postprocess; if no result and/or only one object, simplify + if(!pObjectList->GetObjCount()) + { + pGroup.clear(); + } + else if(1 == pObjectList->GetObjCount()) + { + pRetval = pObjectList->RemoveObject(0); + pGroup.clear(); + } + else + { + pRetval = pGroup; + } + } + + return pRetval; +} + + +rtl::Reference<SdrObject> SdrTextObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const +{ + if(bAddText) + { + return ImpConvertContainedTextToSdrPathObjs(!bBezier); + } + + return nullptr; +} + +bool SdrTextObj::ImpCanConvTextToCurve() const +{ + return !IsOutlText() && !utl::ConfigManager::IsFuzzing(); +} + +rtl::Reference<SdrPathObj> SdrTextObj::ImpConvertMakeObj(const basegfx::B2DPolyPolygon& rPolyPolygon, bool bClosed, bool bBezier) const +{ + SdrObjKind ePathKind = bClosed ? SdrObjKind::PathFill : SdrObjKind::PathLine; + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon); + + // #i37011# + if(!bBezier) + { + aB2DPolyPolygon = basegfx::utils::adaptiveSubdivideByAngle(aB2DPolyPolygon); + ePathKind = bClosed ? SdrObjKind::Polygon : SdrObjKind::PolyLine; + } + + rtl::Reference<SdrPathObj> pPathObj(new SdrPathObj( + getSdrModelFromSdrObject(), + ePathKind, + std::move(aB2DPolyPolygon))); + + if(bBezier) + { + // create bezier curves + pPathObj->SetPathPoly(basegfx::utils::expandToCurve(pPathObj->GetPathPoly())); + } + + pPathObj->ImpSetAnchorPos(m_aAnchor); + pPathObj->NbcSetLayer(GetLayer()); + sdr::properties::ItemChangeBroadcaster aC(*pPathObj); + pPathObj->ClearMergedItem(); + pPathObj->SetMergedItemSet(GetObjectItemSet()); + pPathObj->GetProperties().BroadcastItemChange(aC); + pPathObj->NbcSetStyleSheet(GetStyleSheet(), true); + + return pPathObj; +} + +rtl::Reference<SdrObject> SdrTextObj::ImpConvertAddText(rtl::Reference<SdrObject> pObj, bool bBezier) const +{ + if(!ImpCanConvTextToCurve()) + { + return pObj; + } + + rtl::Reference<SdrObject> pText = ImpConvertContainedTextToSdrPathObjs(!bBezier); + + if(!pText) + { + return pObj; + } + + if(!pObj) + { + return pText; + } + + if(pText->IsGroupObject()) + { + // is already group object, add partial shape in front + SdrObjList* pOL=pText->GetSubList(); + pOL->InsertObject(pObj.get(),0); + + return pText; + } + else + { + // not yet a group, create one and add partial and new shapes + rtl::Reference<SdrObjGroup> pGrp(new SdrObjGroup(getSdrModelFromSdrObject())); + SdrObjList* pOL=pGrp->GetSubList(); + pOL->InsertObject(pObj.get()); + pOL->InsertObject(pText.get()); + + return pGrp; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdouno.cxx b/svx/source/svdraw/svdouno.cxx new file mode 100644 index 0000000000..970fa60d37 --- /dev/null +++ b/svx/source/svdraw/svdouno.cxx @@ -0,0 +1,497 @@ +/* -*- 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 <sdr/contact/viewcontactofunocontrol.hxx> +#include <sdr/contact/viewobjectcontactofunocontrol.hxx> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XCloneable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/processfactory.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdmodel.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/svdview.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdviter.hxx> +#include <rtl/ref.hxx> +#include <svx/sdrpagewindow.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/debug.hxx> +#include <o3tl/sorted_vector.hxx> + +using namespace ::com::sun::star; +using namespace sdr::contact; + + +class SdrControlEventListenerImpl : public ::cppu::WeakImplHelper< css::lang::XEventListener > +{ +protected: + SdrUnoObj* pObj; + +public: + explicit SdrControlEventListenerImpl(SdrUnoObj* _pObj) + : pObj(_pObj) + {} + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + void StopListening(const uno::Reference< lang::XComponent >& xComp); + void StartListening(const uno::Reference< lang::XComponent >& xComp); +}; + +// XEventListener +void SAL_CALL SdrControlEventListenerImpl::disposing( const css::lang::EventObject& /*Source*/) +{ + if (pObj) + { + pObj->xUnoControlModel = nullptr; + } +} + +void SdrControlEventListenerImpl::StopListening(const uno::Reference< lang::XComponent >& xComp) +{ + if (xComp.is()) + xComp->removeEventListener(this); +} + +void SdrControlEventListenerImpl::StartListening(const uno::Reference< lang::XComponent >& xComp) +{ + if (xComp.is()) + xComp->addEventListener(this); +} + + +struct SdrUnoObjDataHolder +{ + mutable ::rtl::Reference< SdrControlEventListenerImpl > + pEventListener; +}; + + +namespace +{ + void lcl_ensureControlVisibility( SdrView const * _pView, const SdrUnoObj* _pObject, bool _bVisible ) + { + OSL_PRECOND( _pObject, "lcl_ensureControlVisibility: no object -> no survival!" ); + + SdrPageView* pPageView = _pView ? _pView->GetSdrPageView() : nullptr; + DBG_ASSERT( pPageView, "lcl_ensureControlVisibility: no view found!" ); + if ( !pPageView ) + return; + + ViewContact& rUnoControlContact( _pObject->GetViewContact() ); + + for ( sal_uInt32 i = 0; i < pPageView->PageWindowCount(); ++i ) + { + SdrPageWindow* pPageWindow = pPageView->GetPageWindow( i ); + DBG_ASSERT( pPageWindow, "lcl_ensureControlVisibility: invalid PageViewWindow!" ); + if ( !pPageWindow ) + continue; + + if ( !pPageWindow->HasObjectContact() ) + continue; + + ObjectContact& rPageViewContact( pPageWindow->GetObjectContact() ); + const ViewObjectContact& rViewObjectContact( rUnoControlContact.GetViewObjectContact( rPageViewContact ) ); + const ViewObjectContactOfUnoControl* pUnoControlContact = dynamic_cast< const ViewObjectContactOfUnoControl* >( &rViewObjectContact ); + DBG_ASSERT( pUnoControlContact, "lcl_ensureControlVisibility: wrong ViewObjectContact type!" ); + if ( !pUnoControlContact ) + continue; + + pUnoControlContact->ensureControlVisibility( _bVisible ); + } + } +} + +SdrUnoObj::SdrUnoObj( + SdrModel& rSdrModel, + const OUString& rModelName) +: SdrRectObj(rSdrModel), + m_pImpl( new SdrUnoObjDataHolder ) +{ + osl_atomic_increment(&m_refCount); // prevent deletion during creation + m_bIsUnoObj = true; + + m_pImpl->pEventListener = new SdrControlEventListenerImpl(this); + + // only an owner may create independently + if (!rModelName.isEmpty()) + CreateUnoControlModel(rModelName); + osl_atomic_decrement(&m_refCount); +} + +SdrUnoObj::SdrUnoObj( SdrModel& rSdrModel, SdrUnoObj const & rSource) +: SdrRectObj(rSdrModel, rSource), + m_pImpl( new SdrUnoObjDataHolder ) +{ + m_bIsUnoObj = true; + + m_pImpl->pEventListener = new SdrControlEventListenerImpl(this); + + aUnoControlModelTypeName = rSource.aUnoControlModelTypeName; + aUnoControlTypeName = rSource.aUnoControlTypeName; + + // copy the uno control model + const uno::Reference< awt::XControlModel > xSourceControlModel = rSource.GetUnoControlModel(); + if ( xSourceControlModel.is() ) + { + try + { + uno::Reference< util::XCloneable > xClone( xSourceControlModel, uno::UNO_QUERY_THROW ); + xUnoControlModel.set( xClone->createClone(), uno::UNO_QUERY_THROW ); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + // get service name of the control from the control model + uno::Reference< beans::XPropertySet > xSet(xUnoControlModel, uno::UNO_QUERY); + if (xSet.is()) + { + uno::Any aValue( xSet->getPropertyValue("DefaultControl") ); + OUString aStr; + + if( aValue >>= aStr ) + aUnoControlTypeName = aStr; + } + + uno::Reference< lang::XComponent > xComp(xUnoControlModel, uno::UNO_QUERY); + if (xComp.is()) + m_pImpl->pEventListener->StartListening(xComp); +} + +SdrUnoObj::SdrUnoObj( + SdrModel& rSdrModel, + const OUString& rModelName, + const uno::Reference< lang::XMultiServiceFactory >& rxSFac) +: SdrRectObj(rSdrModel), + m_pImpl( new SdrUnoObjDataHolder ) +{ + m_bIsUnoObj = true; + + m_pImpl->pEventListener = new SdrControlEventListenerImpl(this); + + // only an owner may create independently + if (!rModelName.isEmpty()) + CreateUnoControlModel(rModelName,rxSFac); +} + +SdrUnoObj::~SdrUnoObj() +{ + try + { + // clean up the control model + uno::Reference< lang::XComponent > xComp(xUnoControlModel, uno::UNO_QUERY); + if (xComp.is()) + { + // is the control model owned by its environment? + uno::Reference< container::XChild > xContent(xUnoControlModel, uno::UNO_QUERY); + if (xContent.is() && !xContent->getParent().is()) + xComp->dispose(); + else + m_pImpl->pEventListener->StopListening(xComp); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "SdrUnoObj::~SdrUnoObj" ); + } +} + +void SdrUnoObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bRotateFreeAllowed = false; + rInfo.bRotate90Allowed = false; + rInfo.bMirrorFreeAllowed = false; + rInfo.bMirror45Allowed = false; + rInfo.bMirror90Allowed = false; + rInfo.bTransparenceAllowed = false; + rInfo.bShearAllowed = false; + rInfo.bEdgeRadiusAllowed = false; + rInfo.bNoOrthoDesired = false; + rInfo.bCanConvToPath = false; + rInfo.bCanConvToPoly = false; + rInfo.bCanConvToPathLineToArea = false; + rInfo.bCanConvToPolyLineToArea = false; + rInfo.bCanConvToContour = false; +} + +SdrObjKind SdrUnoObj::GetObjIdentifier() const +{ + return SdrObjKind::UNO; +} + +void SdrUnoObj::SetContextWritingMode( const sal_Int16 _nContextWritingMode ) +{ + try + { + uno::Reference< beans::XPropertySet > xModelProperties( GetUnoControlModel(), uno::UNO_QUERY_THROW ); + xModelProperties->setPropertyValue( "ContextWritingMode", uno::Any( _nContextWritingMode ) ); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +OUString SdrUnoObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulUno)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrUnoObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralUno); +} + +rtl::Reference<SdrObject> SdrUnoObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrUnoObj(rTargetModel, *this); +} + +void SdrUnoObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + SdrRectObj::NbcResize(rRef,xFact,yFact); + + if (maGeo.m_nShearAngle==0_deg100 && maGeo.m_nRotationAngle==0_deg100) + return; + + // small correctors + if (maGeo.m_nRotationAngle>=9000_deg100 && maGeo.m_nRotationAngle<27000_deg100) + { + moveRectangle(getRectangle().Left() - getRectangle().Right(), getRectangle().Top() - getRectangle().Bottom()); + } + + maGeo.m_nRotationAngle = 0_deg100; + maGeo.m_nShearAngle = 0_deg100; + maGeo.mfSinRotationAngle = 0.0; + maGeo.mfCosRotationAngle = 1.0; + maGeo.mfTanShearAngle = 0.0; + SetBoundAndSnapRectsDirty(); +} + + +bool SdrUnoObj::hasSpecialDrag() const +{ + // no special drag; we have no rounding rect and + // do want frame handles + return false; +} + +void SdrUnoObj::NbcSetLayer( SdrLayerID _nLayer ) +{ + if ( GetLayer() == _nLayer ) + { // redundant call -> not interested in doing anything here + SdrRectObj::NbcSetLayer( _nLayer ); + return; + } + + // we need some special handling here in case we're moved from an invisible layer + // to a visible one, or vice versa + // (relative to a layer. Remember that the visibility of a layer is a view attribute + // - the same layer can be visible in one view, and invisible in another view, at the + // same time) + + // collect all views in which our old layer is visible + o3tl::sorted_vector< SdrView* > aPreviouslyVisible; + + { + SdrViewIter::ForAllViews(this, + [&aPreviouslyVisible] (SdrView* pView) + { + aPreviouslyVisible.insert( pView ); + return false; + }); + } + + SdrRectObj::NbcSetLayer( _nLayer ); + + // collect all views in which our new layer is visible + o3tl::sorted_vector< SdrView* > aNewlyVisible; + + SdrViewIter::ForAllViews( this, + [&aPreviouslyVisible, &aNewlyVisible] (SdrView* pView) + { + if ( aPreviouslyVisible.erase(pView) == 0 ) + { + // in pView, we were visible _before_ the layer change, and are + // _not_ visible after the layer change + // => remember this view, as our visibility there changed + aNewlyVisible.insert( pView ); + } + }); + + // now aPreviouslyVisible contains all views where we became invisible + for (const auto& rpView : aPreviouslyVisible) + { + lcl_ensureControlVisibility( rpView, this, false ); + } + + // and aNewlyVisible all views where we became visible + for (const auto& rpView : aNewlyVisible) + { + lcl_ensureControlVisibility( rpView, this, true ); + } +} + +void SdrUnoObj::CreateUnoControlModel(const OUString& rModelName) +{ + DBG_ASSERT(!xUnoControlModel.is(), "model already exists"); + + aUnoControlModelTypeName = rModelName; + + uno::Reference< awt::XControlModel > xModel; + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + if (!aUnoControlModelTypeName.isEmpty() ) + { + xModel.set(xContext->getServiceManager()->createInstanceWithContext( + aUnoControlModelTypeName, xContext), uno::UNO_QUERY); + + if (xModel.is()) + SetChanged(); + } + + SetUnoControlModel(xModel); +} + +void SdrUnoObj::CreateUnoControlModel(const OUString& rModelName, + const uno::Reference< lang::XMultiServiceFactory >& rxSFac) +{ + DBG_ASSERT(!xUnoControlModel.is(), "model already exists"); + + aUnoControlModelTypeName = rModelName; + + uno::Reference< awt::XControlModel > xModel; + if (!aUnoControlModelTypeName.isEmpty() && rxSFac.is() ) + { + xModel.set(rxSFac->createInstance(aUnoControlModelTypeName), uno::UNO_QUERY); + + if (xModel.is()) + SetChanged(); + } + + SetUnoControlModel(xModel); +} + +void SdrUnoObj::SetUnoControlModel( const uno::Reference< awt::XControlModel >& xModel) +{ + if (xUnoControlModel.is()) + { + uno::Reference< lang::XComponent > xComp(xUnoControlModel, uno::UNO_QUERY); + if (xComp.is()) + m_pImpl->pEventListener->StopListening(xComp); + } + + xUnoControlModel = xModel; + + // control model has to contain service name of the control + if (xUnoControlModel.is()) + { + uno::Reference< beans::XPropertySet > xSet(xUnoControlModel, uno::UNO_QUERY); + if (xSet.is()) + { + uno::Any aValue( xSet->getPropertyValue("DefaultControl") ); + OUString aStr; + if( aValue >>= aStr ) + aUnoControlTypeName = aStr; + } + + uno::Reference< lang::XComponent > xComp(xUnoControlModel, uno::UNO_QUERY); + if (xComp.is()) + m_pImpl->pEventListener->StartListening(xComp); + } + + // invalidate all ViewObject contacts + ViewContactOfUnoControl* pVC = nullptr; + if ( impl_getViewContact( pVC ) ) + { + // flushViewObjectContacts() removes all existing VOCs for the local DrawHierarchy. This + // is always allowed since they will be re-created on demand (and with the changed model) + GetViewContact().flushViewObjectContacts(); + } +} + + +uno::Reference< awt::XControl > SdrUnoObj::GetUnoControl(const SdrView& _rView, const OutputDevice& _rOut) const +{ + uno::Reference< awt::XControl > xControl; + + SdrPageView* pPageView = _rView.GetSdrPageView(); + OSL_ENSURE( pPageView && getSdrPageFromSdrObject() == pPageView->GetPage(), "SdrUnoObj::GetUnoControl: This object is not displayed in that particular view!" ); + if ( !pPageView || getSdrPageFromSdrObject() != pPageView->GetPage() ) + return nullptr; + + SdrPageWindow* pPageWindow = pPageView->FindPageWindow( _rOut ); + OSL_ENSURE( pPageWindow, "SdrUnoObj::GetUnoControl: did not find my SdrPageWindow!" ); + if ( !pPageWindow ) + return nullptr; + + ViewObjectContact& rViewObjectContact( GetViewContact().GetViewObjectContact( pPageWindow->GetObjectContact() ) ); + ViewObjectContactOfUnoControl* pUnoContact = dynamic_cast< ViewObjectContactOfUnoControl* >( &rViewObjectContact ); + OSL_ENSURE( pUnoContact, "SdrUnoObj::GetUnoControl: wrong contact type!" ); + if ( pUnoContact ) + xControl = pUnoContact->getControl(); + + return xControl; +} + + +uno::Reference< awt::XControl > SdrUnoObj::GetTemporaryControlForWindow( + const vcl::Window& _rWindow, uno::Reference< awt::XControlContainer >& _inout_ControlContainer ) const +{ + uno::Reference< awt::XControl > xControl; + + ViewContactOfUnoControl* pVC = nullptr; + if ( impl_getViewContact( pVC ) ) + xControl = pVC->getTemporaryControlForWindow( _rWindow, _inout_ControlContainer ); + + return xControl; +} + + +bool SdrUnoObj::impl_getViewContact( ViewContactOfUnoControl*& _out_rpContact ) const +{ + ViewContact& rViewContact( GetViewContact() ); + _out_rpContact = dynamic_cast< ViewContactOfUnoControl* >( &rViewContact ); + DBG_ASSERT( _out_rpContact, "SdrUnoObj::impl_getViewContact: could not find my ViewContact!" ); + return ( _out_rpContact != nullptr ); +} + + +std::unique_ptr<sdr::contact::ViewContact> SdrUnoObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfUnoControl>( *this ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoutl.cxx b/svx/source/svdraw/svdoutl.cxx new file mode 100644 index 0000000000..02bb89e38b --- /dev/null +++ b/svx/source/svdraw/svdoutl.cxx @@ -0,0 +1,120 @@ +/* -*- 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 <optional> +#include <svx/svdoutl.hxx> +#include <editeng/outliner.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdpage.hxx> +#include <editeng/editstat.hxx> +#include <svl/itempool.hxx> +#include <editeng/editview.hxx> + + +SdrOutliner::SdrOutliner( SfxItemPool* pItemPool, OutlinerMode nMode ) +: Outliner( pItemPool, nMode ), + mpVisualizedPage(nullptr) +{ +} + + +SdrOutliner::~SdrOutliner() +{ +} + + +void SdrOutliner::SetTextObj( const SdrTextObj* pObj ) +{ + if( pObj && pObj != mxWeakTextObj.get().get() ) + { + SetUpdateLayout(false); + OutlinerMode nOutlinerMode2 = OutlinerMode::OutlineObject; + if ( !pObj->IsOutlText() ) + nOutlinerMode2 = OutlinerMode::TextObject; + Init( nOutlinerMode2 ); + + setGlobalScale(100.0, 100.0, 100.0, 100.0); + + EEControlBits nStat = GetControlWord(); + nStat &= ~EEControlBits( EEControlBits::STRETCHING | EEControlBits::AUTOPAGESIZE ); + SetControlWord(nStat); + + Size aMaxSize( 100000,100000 ); + SetMinAutoPaperSize( Size() ); + SetMaxAutoPaperSize( aMaxSize ); + SetPaperSize( aMaxSize ); + SetTextColumns(pObj->GetTextColumnsNumber(), pObj->GetTextColumnsSpacing()); + ClearPolygon(); + } + + mxWeakTextObj = const_cast< SdrTextObj* >(pObj); +} + +void SdrOutliner::SetTextObjNoInit( const SdrTextObj* pObj ) +{ + mxWeakTextObj = const_cast< SdrTextObj* >(pObj); +} + +OUString SdrOutliner::CalcFieldValue(const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, + std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, + std::optional<FontLineStyle>& rpFldLineStyle) +{ + bool bOk = false; + OUString aRet; + + if(auto pTextObj = mxWeakTextObj.get()) + bOk = pTextObj->CalcFieldValue(rField, nPara, nPos, false, rpTxtColor, rpFldColor, rpFldLineStyle, aRet); + + if (!bOk) + aRet = Outliner::CalcFieldValue(rField, nPara, nPos, rpTxtColor, rpFldColor, rpFldLineStyle); + + return aRet; +} + +const SdrTextObj* SdrOutliner::GetTextObj() const +{ + return mxWeakTextObj.get().get(); +} + +bool SdrOutliner::hasEditViewCallbacks() const +{ + for (size_t a(0); a < GetViewCount(); a++) + { + OutlinerView* pOutlinerView = GetView(a); + + if (pOutlinerView && pOutlinerView->GetEditView().getEditViewCallbacks()) + { + return true; + } + } + + return false; +} + +std::optional<bool> SdrOutliner::GetCompatFlag(SdrCompatibilityFlag eFlag) const +{ + if( mpVisualizedPage ) + { + return {mpVisualizedPage->getSdrModelFromSdrPage().GetCompatibilityFlag(eFlag)}; + } + return {}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdoutlinercache.cxx b/svx/source/svdraw/svdoutlinercache.cxx new file mode 100644 index 0000000000..0fc6fc9730 --- /dev/null +++ b/svx/source/svdraw/svdoutlinercache.cxx @@ -0,0 +1,95 @@ +/* -*- 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 <svdoutlinercache.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdetc.hxx> + +SdrOutlinerCache::SdrOutlinerCache( SdrModel* pModel ) +: mpModel( pModel ) +{ +} + +std::unique_ptr<SdrOutliner> SdrOutlinerCache::createOutliner( OutlinerMode nOutlinerMode ) +{ + std::unique_ptr<SdrOutliner> pOutliner; + + if( (OutlinerMode::OutlineObject == nOutlinerMode) && !maModeOutline.empty() ) + { + pOutliner = std::move(maModeOutline.back()); + maModeOutline.pop_back(); + } + else if( (OutlinerMode::TextObject == nOutlinerMode) && !maModeText.empty() ) + { + pOutliner = std::move(maModeText.back()); + maModeText.pop_back(); + } + else + { + pOutliner = SdrMakeOutliner(nOutlinerMode, *mpModel); + Outliner& aDrawOutliner = mpModel->GetDrawOutliner(); + pOutliner->SetCalcFieldValueHdl( aDrawOutliner.GetCalcFieldValueHdl() ); + maActiveOutliners.insert(pOutliner.get()); + } + + return pOutliner; +} + +SdrOutlinerCache::~SdrOutlinerCache() +{ +} + +void SdrOutlinerCache::disposeOutliner( std::unique_ptr<SdrOutliner> pOutliner ) +{ + if( !pOutliner ) + return; + + OutlinerMode nOutlMode = pOutliner->GetOutlinerMode(); + + if( OutlinerMode::OutlineObject == nOutlMode ) + { + pOutliner->Clear(); + pOutliner->SetVertical( false ); + + // Deregister on outliner, might be reused from outliner cache + pOutliner->SetNotifyHdl( Link<EENotify&,void>() ); + maModeOutline.emplace_back(std::move(pOutliner)); + } + else if( OutlinerMode::TextObject == nOutlMode ) + { + pOutliner->Clear(); + pOutliner->SetVertical( false ); + + // Deregister on outliner, might be reused from outliner cache + pOutliner->SetNotifyHdl( Link<EENotify&,void>() ); + maModeText.emplace_back(std::move(pOutliner)); + } + else + { + maActiveOutliners.erase(pOutliner.get()); + } +} + +std::vector< SdrOutliner* > SdrOutlinerCache::GetActiveOutliners() const +{ + return std::vector< SdrOutliner* >(maActiveOutliners.begin(), maActiveOutliners.end()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdovirt.cxx b/svx/source/svdraw/svdovirt.cxx new file mode 100644 index 0000000000..b1fe6f5cb9 --- /dev/null +++ b/svx/source/svdraw/svdovirt.cxx @@ -0,0 +1,586 @@ +/* -*- 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 <sdr/properties/emptyproperties.hxx> +#include <svx/svdovirt.hxx> +#include <svx/svdhdl.hxx> +#include <svx/sdr/contact/viewcontactofvirtobj.hxx> +#include <svx/svdograf.hxx> +#include <svx/svddrgv.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +sdr::properties::BaseProperties& SdrVirtObj::GetProperties() const +{ + return mxRefObj->GetProperties(); +} + + +std::unique_ptr<sdr::properties::BaseProperties> SdrVirtObj::CreateObjectSpecificProperties() +{ + return std::make_unique<sdr::properties::EmptyProperties>(*this); +} + +// #i27224# +std::unique_ptr<sdr::contact::ViewContact> SdrVirtObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfVirtObj>(*this); +} + +SdrVirtObj::SdrVirtObj( + SdrModel& rSdrModel, + SdrObject& rNewObj) +: SdrObject(rSdrModel), + mxRefObj(&rNewObj) +{ + m_bVirtObj=true; // this is only a virtual object + mxRefObj->AddReference(*this); + m_bClosedObj = mxRefObj->IsClosedObj(); +} + +SdrVirtObj::SdrVirtObj( + SdrModel& rSdrModel, SdrVirtObj const & rSource) +: SdrObject(rSdrModel, rSource), + mxRefObj(rSource.mxRefObj) +{ + m_bVirtObj=true; // this is only a virtual object + m_bClosedObj = mxRefObj->IsClosedObj(); + + mxRefObj->AddReference(*this); + + aSnapRect = rSource.aSnapRect; + m_aAnchor = rSource.m_aAnchor; +} + +SdrVirtObj::~SdrVirtObj() +{ + mxRefObj->DelReference(*this); +} + +const SdrObject& SdrVirtObj::GetReferencedObj() const +{ + return *mxRefObj; +} + +SdrObject& SdrVirtObj::ReferencedObj() +{ + return *mxRefObj; +} + +void SdrVirtObj::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& /*rHint*/) +{ + m_bClosedObj = mxRefObj->IsClosedObj(); + SetBoundAndSnapRectsDirty(); // TODO: Optimize this. + + // Only a repaint here, rRefObj may have changed and broadcasts + ActionChanged(); +} + +void SdrVirtObj::NbcSetAnchorPos(const Point& rAnchorPos) +{ + m_aAnchor=rAnchorPos; +} + +void SdrVirtObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + mxRefObj->TakeObjInfo(rInfo); +} + +SdrInventor SdrVirtObj::GetObjInventor() const +{ + return mxRefObj->GetObjInventor(); +} + +SdrObjKind SdrVirtObj::GetObjIdentifier() const +{ + return mxRefObj->GetObjIdentifier(); +} + +SdrObjList* SdrVirtObj::GetSubList() const +{ + return mxRefObj->GetSubList(); +} + +void SdrVirtObj::SetName(const OUString& rStr, const bool bSetChanged) +{ + return mxRefObj->SetName(rStr, bSetChanged); +} + +const OUString & SdrVirtObj::GetName() const +{ + return mxRefObj->GetName(); +} + +void SdrVirtObj::SetTitle(const OUString& rStr) +{ + return mxRefObj->SetTitle(rStr); +} + +OUString SdrVirtObj::GetTitle() const +{ + return mxRefObj->GetTitle(); +} + +void SdrVirtObj::SetDescription(const OUString& rStr) +{ + return mxRefObj->SetDescription(rStr); +} + +OUString SdrVirtObj::GetDescription() const +{ + return mxRefObj->GetDescription(); +} + +void SdrVirtObj::SetDecorative(bool const isDecorative) +{ + return mxRefObj->SetDecorative(isDecorative); +} + +bool SdrVirtObj::IsDecorative() const +{ + return mxRefObj->IsDecorative(); +} + +const tools::Rectangle& SdrVirtObj::GetCurrentBoundRect() const +{ + auto aRectangle = mxRefObj->GetCurrentBoundRect(); // TODO: Optimize this. + aRectangle += m_aAnchor; + setOutRectangleConst(aRectangle); + return getOutRectangle(); +} + +const tools::Rectangle& SdrVirtObj::GetLastBoundRect() const +{ + auto aRectangle = mxRefObj->GetLastBoundRect(); // TODO: Optimize this. + aRectangle += m_aAnchor; + setOutRectangleConst(aRectangle); + return getOutRectangle(); +} + +void SdrVirtObj::RecalcBoundRect() +{ + tools::Rectangle aRectangle = mxRefObj->GetCurrentBoundRect(); + aRectangle += m_aAnchor; + setOutRectangle(aRectangle); +} + +rtl::Reference<SdrObject> SdrVirtObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrVirtObj(rTargetModel, *this); + // TTTT not sure if the above works - how could SdrObjFactory::MakeNewObject + // create an object with correct rRefObj (?) OTOH VirtObj probably needs not + // to be cloned ever - only used in Writer for multiple instances e.g. Header/Footer + // return new SdrVirtObj( + // getSdrModelFromSdrObject(), + // rRefObj); // only a further reference +} + +OUString SdrVirtObj::TakeObjNameSingul() const +{ + OUString sName = "[" + mxRefObj->TakeObjNameSingul() + "]"; + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + +OUString SdrVirtObj::TakeObjNamePlural() const +{ + return "[" + mxRefObj->TakeObjNamePlural() + "]"; +} + +bool SdrVirtObj::HasLimitedRotation() const +{ + // RotGrfFlyFrame: If true, this SdrObject supports only limited rotation + return mxRefObj->HasLimitedRotation(); +} + +basegfx::B2DPolyPolygon SdrVirtObj::TakeXorPoly() const +{ + basegfx::B2DPolyPolygon aPolyPolygon(mxRefObj->TakeXorPoly()); + + if(m_aAnchor.X() || m_aAnchor.Y()) + { + aPolyPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(m_aAnchor.X(), m_aAnchor.Y())); + } + + return aPolyPolygon; +} + + +sal_uInt32 SdrVirtObj::GetHdlCount() const +{ + return mxRefObj->GetHdlCount(); +} + +void SdrVirtObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + SdrHdlList tempList(nullptr); + mxRefObj->AddToHdlList(tempList); + for (size_t i=0; i<tempList.GetHdlCount(); ++i) + { + SdrHdl* pHdl = tempList.GetHdl(i); + Point aP(pHdl->GetPos()+m_aAnchor); + pHdl->SetPos(aP); + } + tempList.MoveTo(rHdlList); +} + +void SdrVirtObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const +{ + SdrHdlList tempList(nullptr); + mxRefObj->AddToPlusHdlList(tempList, rHdl); + for (size_t i=0; i<tempList.GetHdlCount(); ++i) + { + SdrHdl* pHdl = tempList.GetHdl(i); + Point aP(pHdl->GetPos()+m_aAnchor); + pHdl->SetPos(aP); + } + tempList.MoveTo(rHdlList); +} + +bool SdrVirtObj::hasSpecialDrag() const +{ + return mxRefObj->hasSpecialDrag(); +} + +bool SdrVirtObj::supportsFullDrag() const +{ + return false; +} + +rtl::Reference<SdrObject> SdrVirtObj::getFullDragClone() const +{ + SdrObject& rReferencedObject = const_cast<SdrVirtObj*>(this)->ReferencedObj(); + return rtl::Reference<SdrObject>(new SdrGrafObj( + getSdrModelFromSdrObject(), + SdrDragView::GetObjGraphic(rReferencedObject), + GetLogicRect())); +} + +bool SdrVirtObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + return mxRefObj->beginSpecialDrag(rDrag); +} + +bool SdrVirtObj::applySpecialDrag(SdrDragStat& rDrag) +{ + return mxRefObj->applySpecialDrag(rDrag); +} + +basegfx::B2DPolyPolygon SdrVirtObj::getSpecialDragPoly(const SdrDragStat& rDrag) const +{ + return mxRefObj->getSpecialDragPoly(rDrag); + // TODO: we don't handle offsets yet! +} + +OUString SdrVirtObj::getSpecialDragComment(const SdrDragStat& rDrag) const +{ + return mxRefObj->getSpecialDragComment(rDrag); +} + + +bool SdrVirtObj::BegCreate(SdrDragStat& rStat) +{ + return mxRefObj->BegCreate(rStat); +} + +bool SdrVirtObj::MovCreate(SdrDragStat& rStat) +{ + return mxRefObj->MovCreate(rStat); +} + +bool SdrVirtObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + return mxRefObj->EndCreate(rStat,eCmd); +} + +bool SdrVirtObj::BckCreate(SdrDragStat& rStat) +{ + return mxRefObj->BckCreate(rStat); +} + +void SdrVirtObj::BrkCreate(SdrDragStat& rStat) +{ + mxRefObj->BrkCreate(rStat); +} + +basegfx::B2DPolyPolygon SdrVirtObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + return mxRefObj->TakeCreatePoly(rDrag); + // TODO: we don't handle offsets yet! +} + + +void SdrVirtObj::NbcMove(const Size& rSiz) +{ + m_aAnchor.Move(rSiz); + SetBoundAndSnapRectsDirty(); +} + +void SdrVirtObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + mxRefObj->NbcResize(rRef-m_aAnchor,xFact,yFact); + SetBoundAndSnapRectsDirty(); +} + +void SdrVirtObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + mxRefObj->NbcRotate(rRef-m_aAnchor,nAngle,sn,cs); + SetBoundAndSnapRectsDirty(); +} + +void SdrVirtObj::NbcMirror(const Point& rRef1, const Point& rRef2) +{ + mxRefObj->NbcMirror(rRef1-m_aAnchor,rRef2-m_aAnchor); + SetBoundAndSnapRectsDirty(); +} + +void SdrVirtObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + mxRefObj->NbcShear(rRef-m_aAnchor,nAngle,tn,bVShear); + SetBoundAndSnapRectsDirty(); +} + + +void SdrVirtObj::Move(const Size& rSiz) +{ + if (!rSiz.IsEmpty()) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + NbcMove(rSiz); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0); + } +} + +void SdrVirtObj::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool bUnsetRelative) +{ + if (xFact.GetNumerator()!=xFact.GetDenominator() || yFact.GetNumerator()!=yFact.GetDenominator()) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + mxRefObj->Resize(rRef-m_aAnchor,xFact,yFact, bUnsetRelative); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrVirtObj::Rotate(const Point& rRef, Degree100 nAngle, double sn, double cs) +{ + if (nAngle) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + mxRefObj->Rotate(rRef-m_aAnchor,nAngle,sn,cs); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + +void SdrVirtObj::Mirror(const Point& rRef1, const Point& rRef2) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + mxRefObj->Mirror(rRef1-m_aAnchor,rRef2-m_aAnchor); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrVirtObj::Shear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear) +{ + if (nAngle) { + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + mxRefObj->Shear(rRef-m_aAnchor,nAngle,tn,bVShear); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } +} + + +void SdrVirtObj::RecalcSnapRect() +{ + aSnapRect=mxRefObj->GetSnapRect(); + aSnapRect+=m_aAnchor; +} + +const tools::Rectangle& SdrVirtObj::GetSnapRect() const +{ + const_cast<SdrVirtObj*>(this)->aSnapRect=mxRefObj->GetSnapRect(); + const_cast<SdrVirtObj*>(this)->aSnapRect+=m_aAnchor; + return aSnapRect; +} + +void SdrVirtObj::SetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + tools::Rectangle aR(rRect); + aR-=m_aAnchor; + mxRefObj->SetSnapRect(aR); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrVirtObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aR(rRect); + aR-=m_aAnchor; + SetBoundAndSnapRectsDirty(); + mxRefObj->NbcSetSnapRect(aR); +} + + +const tools::Rectangle& SdrVirtObj::GetLogicRect() const +{ + const_cast<SdrVirtObj*>(this)->aSnapRect=mxRefObj->GetLogicRect(); // An abuse of aSnapRect! + const_cast<SdrVirtObj*>(this)->aSnapRect+=m_aAnchor; // If there's trouble, we need another Rectangle Member (or a Heap). + return aSnapRect; +} + +void SdrVirtObj::SetLogicRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + tools::Rectangle aR(rRect); + aR-=m_aAnchor; + mxRefObj->SetLogicRect(aR); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + +void SdrVirtObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + tools::Rectangle aR(rRect); + aR-=m_aAnchor; + SetBoundAndSnapRectsDirty(); + mxRefObj->NbcSetLogicRect(aR); +} + + +Degree100 SdrVirtObj::GetRotateAngle() const +{ + return mxRefObj->GetRotateAngle(); +} + +Degree100 SdrVirtObj::GetShearAngle(bool bVertical) const +{ + return mxRefObj->GetShearAngle(bVertical); +} + + +sal_uInt32 SdrVirtObj::GetSnapPointCount() const +{ + return mxRefObj->GetSnapPointCount(); +} + +Point SdrVirtObj::GetSnapPoint(sal_uInt32 i) const +{ + Point aP(mxRefObj->GetSnapPoint(i)); + aP+=m_aAnchor; + return aP; +} + +bool SdrVirtObj::IsPolyObj() const +{ + return mxRefObj->IsPolyObj(); +} + +sal_uInt32 SdrVirtObj::GetPointCount() const +{ + return mxRefObj->GetPointCount(); +} + +Point SdrVirtObj::GetPoint(sal_uInt32 i) const +{ + return mxRefObj->GetPoint(i) + m_aAnchor; +} + +void SdrVirtObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i) +{ + Point aP(rPnt); + aP-=m_aAnchor; + mxRefObj->SetPoint(aP,i); + SetBoundAndSnapRectsDirty(); +} + + +std::unique_ptr<SdrObjGeoData> SdrVirtObj::NewGeoData() const +{ + return mxRefObj->NewGeoData(); +} + +void SdrVirtObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + mxRefObj->SaveGeoData(rGeo); +} + +void SdrVirtObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + mxRefObj->RestoreGeoData(rGeo); + SetBoundAndSnapRectsDirty(); +} + + +std::unique_ptr<SdrObjGeoData> SdrVirtObj::GetGeoData() const +{ + return mxRefObj->GetGeoData(); +} + +void SdrVirtObj::SetGeoData(const SdrObjGeoData& rGeo) +{ + tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect(); + mxRefObj->SetGeoData(rGeo); + SetBoundAndSnapRectsDirty(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); +} + + +void SdrVirtObj::NbcReformatText() +{ + mxRefObj->NbcReformatText(); +} + +bool SdrVirtObj::HasMacro() const +{ + return mxRefObj->HasMacro(); +} + +SdrObject* SdrVirtObj::CheckMacroHit(const SdrObjMacroHitRec& rRec) const +{ + return mxRefObj->CheckMacroHit(rRec); // TODO: positioning offset +} + +PointerStyle SdrVirtObj::GetMacroPointer(const SdrObjMacroHitRec& rRec) const +{ + return mxRefObj->GetMacroPointer(rRec); // TODO: positioning offset +} + +void SdrVirtObj::PaintMacro(OutputDevice& rOut, const tools::Rectangle& rDirtyRect, const SdrObjMacroHitRec& rRec) const +{ + mxRefObj->PaintMacro(rOut,rDirtyRect,rRec); // TODO: positioning offset +} + +bool SdrVirtObj::DoMacro(const SdrObjMacroHitRec& rRec) +{ + return mxRefObj->DoMacro(rRec); // TODO: positioning offset +} + +Point SdrVirtObj::GetOffset() const +{ + // #i73248# default offset of SdrVirtObj is aAnchor + return m_aAnchor; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdpage.cxx b/svx/source/svdraw/svdpage.cxx new file mode 100644 index 0000000000..adc8555bf1 --- /dev/null +++ b/svx/source/svdraw/svdpage.cxx @@ -0,0 +1,1877 @@ +/* -*- 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 <memory> +#include <cassert> +#include <set> +#include <unordered_set> + +#include <svx/svdpage.hxx> +#include <svx/unoshape.hxx> +#include <svx/unopage.hxx> + +#include <o3tl/safeint.hxx> +#include <string.h> + +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/lok.hxx> + +#include <svtools/colorcfg.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svditer.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdundo.hxx> +#include <svx/xfillit0.hxx> +#include <svx/ColorSets.hxx> + +#include <sdr/contact/viewcontactofsdrpage.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/displayinfo.hxx> +#include <algorithm> +#include <clonelist.hxx> +#include <svl/hint.hxx> +#include <rtl/strbuf.hxx> +#include <libxml/xmlwriter.h> +#include <docmodel/theme/Theme.hxx> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace ::com::sun::star; + +////////////////////////////////////////////////////////////////////////////// + +SdrObjList::SdrObjList() +: mbObjOrdNumsDirty(false), + mbRectsDirty(false), + mbIsNavigationOrderDirty(false) +{ +} + +void SdrObjList::impClearSdrObjList(bool bBroadcast) +{ + SdrModel* pSdrModelFromRemovedSdrObject(nullptr); + + while(!maList.empty()) + { + // remove last object from list + rtl::Reference<SdrObject> pObj(maList.back()); + RemoveObjectFromContainer(maList.size()-1); + + // flushViewObjectContacts() is done since SdrObject::Free is not guaranteed + // to delete the object and thus refresh visualisations + pObj->GetViewContact().flushViewObjectContacts(); + + if(bBroadcast) + { + if(nullptr == pSdrModelFromRemovedSdrObject) + { + pSdrModelFromRemovedSdrObject = &pObj->getSdrModelFromSdrObject(); + } + + // sent remove hint (after removal, see RemoveObject()) + // TTTT SdrPage not needed, can be accessed using SdrObject + SdrHint aHint(SdrHintKind::ObjectRemoved, *pObj, getSdrPageFromSdrObjList()); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + pObj->setParentOfSdrObject(nullptr); + } + + if(bBroadcast && nullptr != pSdrModelFromRemovedSdrObject) + { + pSdrModelFromRemovedSdrObject->SetChanged(); + } +} + +void SdrObjList::ClearSdrObjList() +{ + // clear SdrObjects with broadcasting + impClearSdrObjList(true); +} + +SdrObjList::~SdrObjList() +{ + // Clear SdrObjects without broadcasting. + for (auto& rxObj : maList) + rxObj->setParentOfSdrObject(nullptr); +} + +SdrPage* SdrObjList::getSdrPageFromSdrObjList() const +{ + // default is no page and returns zero + return nullptr; +} + +SdrObject* SdrObjList::getSdrObjectFromSdrObjList() const +{ + // default is no SdrObject (SdrObjGroup) + return nullptr; +} + +void SdrObjList::CopyObjects(const SdrObjList& rSrcList) +{ + CloneList aCloneList; + + // clear SdrObjects with broadcasting + ClearSdrObjList(); + + mbObjOrdNumsDirty = false; + mbRectsDirty = false; +#ifdef DBG_UTIL + size_t nCloneErrCnt(0); +#endif + + if(nullptr == getSdrObjectFromSdrObjList() && nullptr == getSdrPageFromSdrObjList()) + { + OSL_ENSURE(false, "SdrObjList which is not part of SdrPage or SdrObject (!)"); + return; + } + + SdrModel& rTargetSdrModel(nullptr == getSdrObjectFromSdrObjList() + ? getSdrPageFromSdrObjList()->getSdrModelFromSdrPage() + : getSdrObjectFromSdrObjList()->getSdrModelFromSdrObject()); + + for (const rtl::Reference<SdrObject>& pSO : rSrcList) + { + rtl::Reference<SdrObject> pDO(pSO->CloneSdrObject(rTargetSdrModel)); + + if(pDO) + { + NbcInsertObject(pDO.get(), SAL_MAX_SIZE); + aCloneList.AddPair(pSO.get(), pDO.get()); + } +#ifdef DBG_UTIL + else + { + nCloneErrCnt++; + } +#endif + } + + // Wires up the connections + aCloneList.CopyConnections(); +#ifdef DBG_UTIL + if (nCloneErrCnt != 0) + { + OStringBuffer aStr("SdrObjList::operator=(): Error when cloning "); + + if(nCloneErrCnt == 1) + { + aStr.append("a drawing object."); + } + else + { + aStr.append(OString::number(static_cast<sal_Int32>(nCloneErrCnt)) + + " drawing objects."); + } + + OSL_FAIL(aStr.getStr()); + } +#endif +} + +void SdrObjList::RecalcObjOrdNums() +{ + size_t no=0; + for (const rtl::Reference<SdrObject>& pObj : maList) + pObj->SetOrdNum(no++); + mbObjOrdNumsDirty=false; +} + +void SdrObjList::RecalcRects() +{ + maSdrObjListOutRect=tools::Rectangle(); + maSdrObjListSnapRect=maSdrObjListOutRect; + for (auto it = begin(), itEnd = end(); it != itEnd; ++it) { + SdrObject* pObj = it->get(); + if (it == begin()) { + maSdrObjListOutRect=pObj->GetCurrentBoundRect(); + maSdrObjListSnapRect=pObj->GetSnapRect(); + } else { + maSdrObjListOutRect.Union(pObj->GetCurrentBoundRect()); + maSdrObjListSnapRect.Union(pObj->GetSnapRect()); + } + } +} + +void SdrObjList::SetSdrObjListRectsDirty() +{ + mbRectsDirty=true; + SdrObject* pParentSdrObject(getSdrObjectFromSdrObjList()); + + if(nullptr != pParentSdrObject) + { + pParentSdrObject->SetBoundAndSnapRectsDirty(); + } +} + +void SdrObjList::impChildInserted(SdrObject const & rChild) +{ + sdr::contact::ViewContact* pParent = rChild.GetViewContact().GetParentContact(); + + if(pParent) + { + pParent->ActionChildInserted(rChild.GetViewContact()); + } +} + +void SdrObjList::NbcInsertObject(SdrObject* pObj, size_t nPos) +{ + DBG_ASSERT(pObj!=nullptr,"SdrObjList::NbcInsertObject(NULL)"); + if (pObj==nullptr) + return; + + DBG_ASSERT(!pObj->IsInserted(),"The object already has the status Inserted."); + const size_t nCount = GetObjCount(); + if (nPos>nCount) nPos=nCount; + InsertObjectIntoContainer(*pObj,nPos); + + if (nPos<nCount) mbObjOrdNumsDirty=true; + pObj->SetOrdNum(nPos); + pObj->setParentOfSdrObject(this); + + // Inform the parent about change to allow invalidations at + // evtl. existing parent visualisations + impChildInserted(*pObj); + + if (!mbRectsDirty) { + mbRectsDirty = true; + } + pObj->InsertedStateChange(); // calls the UserCall (among others) +} + +void SdrObjList::InsertObjectThenMakeNameUnique(SdrObject* pObj) +{ + std::unordered_set<rtl::OUString> aNameSet; + InsertObjectThenMakeNameUnique(pObj, aNameSet); +} + +void SdrObjList::InsertObjectThenMakeNameUnique(SdrObject* pObj, std::unordered_set<OUString>& rNameSet, size_t nPos) +{ + InsertObject(pObj, nPos); + if (pObj->GetName().isEmpty()) + return; + + pObj->MakeNameUnique(rNameSet); + SdrObjList* pSdrObjList = pObj->GetSubList(); // group + if (pSdrObjList) + { + SdrObject* pListObj; + SdrObjListIter aIter(pSdrObjList, SdrIterMode::DeepWithGroups); + while (aIter.IsMore()) + { + pListObj = aIter.Next(); + pListObj->MakeNameUnique(rNameSet); + } + } +} + +void SdrObjList::InsertObject(SdrObject* pObj, size_t nPos) +{ + DBG_ASSERT(pObj!=nullptr,"SdrObjList::InsertObject(NULL)"); + + if(!pObj) + return; + + // if anchor is used, reset it before grouping + if(getSdrObjectFromSdrObjList()) + { + const Point& rAnchorPos = pObj->GetAnchorPos(); + if(rAnchorPos.X() || rAnchorPos.Y()) + pObj->NbcSetAnchorPos(Point()); + } + + // do insert to new group + NbcInsertObject(pObj, nPos); + + // In case the object is inserted into a group and doesn't overlap with + // the group's other members, it needs an own repaint. + SdrObject* pParentSdrObject(getSdrObjectFromSdrObjList()); + + if(pParentSdrObject) + { + // only repaint here + pParentSdrObject->ActionChanged(); + } + + // TODO: We need a different broadcast here! + // Repaint from object number ... (heads-up: GroupObj) + if(pObj->getSdrPageFromSdrObject() && !pObj->getSdrModelFromSdrObject().isLocked()) + { + SdrHint aHint(SdrHintKind::ObjectInserted, *pObj); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + + pObj->getSdrModelFromSdrObject().SetChanged(); +} + +rtl::Reference<SdrObject> SdrObjList::NbcRemoveObject(size_t nObjNum) +{ + if (nObjNum >= maList.size()) + { + OSL_ASSERT(nObjNum<maList.size()); + return nullptr; + } + + const size_t nCount = GetObjCount(); + rtl::Reference<SdrObject> pObj=maList[nObjNum]; + RemoveObjectFromContainer(nObjNum); + + DBG_ASSERT(pObj!=nullptr,"Could not find object to remove."); + if (pObj!=nullptr) + { + // flushViewObjectContacts() clears the VOC's and those invalidate + pObj->GetViewContact().flushViewObjectContacts(); + + DBG_ASSERT(pObj->IsInserted(),"The object does not have the status Inserted."); + + // tdf#121022 Do first remove from SdrObjList - InsertedStateChange + // relies now on IsInserted which uses getParentSdrObjListFromSdrObject + pObj->setParentOfSdrObject(nullptr); + + // calls UserCall, among other + pObj->InsertedStateChange(); + + if (!mbObjOrdNumsDirty) + { + // optimizing for the case that the last object has to be removed + if (nObjNum+1!=nCount) { + mbObjOrdNumsDirty=true; + } + } + SetSdrObjListRectsDirty(); + } + return pObj; +} + +rtl::Reference<SdrObject> SdrObjList::RemoveObject(size_t nObjNum) +{ + if (nObjNum >= maList.size()) + { + OSL_ASSERT(nObjNum<maList.size()); + return nullptr; + } + + const size_t nCount = GetObjCount(); + rtl::Reference<SdrObject> pObj=maList[nObjNum]; + RemoveObjectFromContainer(nObjNum); + + DBG_ASSERT(pObj!=nullptr,"Object to remove not found."); + if(pObj) + { + // flushViewObjectContacts() clears the VOC's and those invalidate + pObj->GetViewContact().flushViewObjectContacts(); + DBG_ASSERT(pObj->IsInserted(),"The object does not have the status Inserted."); + + // TODO: We need a different broadcast here. + if (pObj->getSdrPageFromSdrObject()!=nullptr) + { + SdrHint aHint(SdrHintKind::ObjectRemoved, *pObj); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + + pObj->getSdrModelFromSdrObject().SetChanged(); + + // tdf#121022 Do first remove from SdrObjList - InsertedStateChange + // relies now on IsInserted which uses getParentSdrObjListFromSdrObject + pObj->setParentOfSdrObject(nullptr); + + // calls, among other things, the UserCall + pObj->InsertedStateChange(); + + if (!mbObjOrdNumsDirty) + { + // optimization for the case that the last object is removed + if (nObjNum+1!=nCount) { + mbObjOrdNumsDirty=true; + } + } + + SetSdrObjListRectsDirty(); + SdrObject* pParentSdrObject(getSdrObjectFromSdrObjList()); + + if(pParentSdrObject && !GetObjCount()) + { + // empty group created; it needs to be repainted since it's + // visualization changes + pParentSdrObject->ActionChanged(); + } + } + return pObj; +} + +rtl::Reference<SdrObject> SdrObjList::ReplaceObject(SdrObject* pNewObj, size_t nObjNum) +{ + if (nObjNum >= maList.size()) + { + OSL_ASSERT(nObjNum<maList.size()); + return nullptr; + } + if (pNewObj == nullptr) + { + OSL_ASSERT(pNewObj!=nullptr); + return nullptr; + } + + rtl::Reference<SdrObject> pObj=maList[nObjNum]; + DBG_ASSERT(pObj!=nullptr,"SdrObjList::ReplaceObject: Could not find object to remove."); + if (pObj!=nullptr) { + DBG_ASSERT(pObj->IsInserted(),"SdrObjList::ReplaceObject: the object does not have status Inserted."); + + // TODO: We need a different broadcast here. + if (pObj->getSdrPageFromSdrObject()!=nullptr) + { + SdrHint aHint(SdrHintKind::ObjectRemoved, *pObj); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + + // Change parent and replace in SdrObjList + pObj->setParentOfSdrObject(nullptr); + ReplaceObjectInContainer(*pNewObj,nObjNum); + + // tdf#121022 InsertedStateChange uses the parent + // to detect if pObj is inserted or not, so have to call + // it *after* changing these settings, else an obviously wrong + // 'SdrUserCallType::Inserted' would be sent + pObj->InsertedStateChange(); + + // flushViewObjectContacts() clears the VOC's and those + // trigger the evtl. needed invalidate(s) + pObj->GetViewContact().flushViewObjectContacts(); + + // Setup data at new SdrObject - it already *is* inserted to + // the SdrObjList due to 'ReplaceObjectInContainer' above + pNewObj->SetOrdNum(nObjNum); + pNewObj->setParentOfSdrObject(this); + + // Inform the parent about change to allow invalidations at + // evtl. existing parent visualisations, but also react on + // newly inserted SdrObjects (as e.g. GraphCtrlUserCall does) + impChildInserted(*pNewObj); + + pNewObj->InsertedStateChange(); + + // TODO: We need a different broadcast here. + if (pNewObj->getSdrPageFromSdrObject()!=nullptr) { + SdrHint aHint(SdrHintKind::ObjectInserted, *pNewObj); + pNewObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + + pNewObj->getSdrModelFromSdrObject().SetChanged(); + + SetSdrObjListRectsDirty(); + } + return pObj; +} + +SdrObject* SdrObjList::SetObjectOrdNum(size_t nOldObjNum, size_t nNewObjNum) +{ + if (nOldObjNum >= maList.size() || nNewObjNum >= maList.size()) + { + OSL_ASSERT(nOldObjNum<maList.size()); + OSL_ASSERT(nNewObjNum<maList.size()); + return nullptr; + } + + rtl::Reference<SdrObject> pObj=maList[nOldObjNum]; + if (nOldObjNum==nNewObjNum) return pObj.get(); + DBG_ASSERT(pObj!=nullptr,"SdrObjList::SetObjectOrdNum: Object not found."); + if (pObj!=nullptr) { + DBG_ASSERT(pObj->IsInserted(),"SdrObjList::SetObjectOrdNum: the object does not have status Inserted."); + RemoveObjectFromContainer(nOldObjNum); + InsertObjectIntoContainer(*pObj,nNewObjNum); + + // No need to delete visualisation data since same object + // gets inserted again. Also a single ActionChanged is enough + pObj->ActionChanged(); + + pObj->SetOrdNum(nNewObjNum); + mbObjOrdNumsDirty=true; + + // TODO: We need a different broadcast here. + if (pObj->getSdrPageFromSdrObject()!=nullptr) + pObj->getSdrModelFromSdrObject().Broadcast(SdrHint(SdrHintKind::ObjectChange, *pObj)); + pObj->getSdrModelFromSdrObject().SetChanged(); + } + return pObj.get(); +} + +void SdrObjList::SetExistingObjectOrdNum(SdrObject* pObj, size_t nNewObjNum) +{ + assert(std::find(maList.begin(), maList.end(), pObj) != maList.end() && "This method requires that the child object already be inserted"); + assert(pObj->IsInserted() && "SdrObjList::SetObjectOrdNum: the object does not have status Inserted."); + + // I am deliberately bypassing getOrdNum() because I don't want to unnecessarily + // trigger RecalcObjOrdNums() + const sal_uInt32 nOldOrdNum = pObj->m_nOrdNum; + if (!mbObjOrdNumsDirty && nOldOrdNum == nNewObjNum) + return; + + // Update the navigation positions. + if (HasObjectNavigationOrder()) + { + unotools::WeakReference<SdrObject> aReference (pObj); + auto iObject = ::std::find( + mxNavigationOrder->begin(), + mxNavigationOrder->end(), + aReference); + mxNavigationOrder->erase(iObject); + mbIsNavigationOrderDirty = true; + // The new object does not have a user defined position so append it + // to the list. + pObj->SetNavigationPosition(mxNavigationOrder->size()); + mxNavigationOrder->push_back(pObj); + } + if (nOldOrdNum < maList.size() && maList[nOldOrdNum] == pObj) + maList.erase(maList.begin()+nOldOrdNum); + else + { + auto it = std::find(maList.begin(), maList.end(), pObj); + maList.erase(it); + } + // Insert object into object list. Because the insert() method requires + // a valid iterator as insertion position, we have to use push_back() to + // insert at the end of the list. + if (nNewObjNum >= maList.size()) + maList.push_back(pObj); + else + maList.insert(maList.begin()+nNewObjNum, pObj); + + mbObjOrdNumsDirty=true; + + // No need to delete visualisation data since same object + // gets inserted again. Also a single ActionChanged is enough + pObj->ActionChanged(); + + pObj->SetOrdNum(nNewObjNum); + mbObjOrdNumsDirty=true; + + // TODO: We need a different broadcast here. + if (pObj->getSdrPageFromSdrObject()!=nullptr) + pObj->getSdrModelFromSdrObject().Broadcast(SdrHint(SdrHintKind::ObjectChange, *pObj)); + pObj->getSdrModelFromSdrObject().SetChanged(); +} + +void SdrObjList::sort( std::vector<sal_Int32>& sortOrder) +{ + // no negative indexes and indexes larger than maList size are allowed + auto it = std::find_if( sortOrder.begin(), sortOrder.end(), [this](const sal_Int32& rIt) + { return ( rIt < 0 || o3tl::make_unsigned(rIt) >= maList.size() ); } ); + if ( it != sortOrder.end()) + throw css::lang::IllegalArgumentException("negative index of shape", nullptr, 1); + + // no duplicates + std::vector<bool> aNoDuplicates(sortOrder.size(), false); + for (size_t i = 0; i < sortOrder.size(); ++i ) + { + size_t idx = static_cast<size_t>( sortOrder[i] ); + + if ( aNoDuplicates[idx] ) + throw css::lang::IllegalArgumentException("duplicate index of shape", nullptr, 2); + + aNoDuplicates[idx] = true; + } + + // example sortOrder [2 0 1] + // example maList [T T S T T] ( T T = shape with textbox, S = just a shape ) + // (shapes at positions 0 and 2 have a textbox) + + std::deque<rtl::Reference<SdrObject>> aNewList(maList.size()); + std::set<sal_Int32> aShapesWithTextbox; + std::vector<sal_Int32> aIncrements; + std::vector<sal_Int32> aDuplicates; + + if ( maList.size() > 1) + { + for (size_t i = 1; i< maList.size(); ++i) + { + // if this shape is a textbox, then look at its left neighbour + // (shape this textbox is in) + // and insert the number of textboxes to the left of it + if (maList[i]->IsTextBox()) + aShapesWithTextbox.insert( i - 1 - aShapesWithTextbox.size() ); + } + // example aShapesWithTextbox [0 2] + } + + if (aShapesWithTextbox.size() != maList.size() - sortOrder.size()) + { + throw lang::IllegalArgumentException("mismatch of no. of shapes", nullptr, 0); + } + + for (size_t i = 0; i< sortOrder.size(); ++i) + { + + if (aShapesWithTextbox.count(sortOrder[i]) > 0) + aDuplicates.push_back(sortOrder[i]); + + aDuplicates.push_back(sortOrder[i]); + + // example aDuplicates [2 2 0 0 1] + } + assert(aDuplicates.size() == maList.size()); + + aIncrements.push_back(0); + for (size_t i = 1; i< sortOrder.size(); ++i) + { + if (aShapesWithTextbox.count(i - 1)) + aIncrements.push_back(aIncrements[i-1] + 1 ); + else + aIncrements.push_back(aIncrements[i-1]); + + // example aIncrements [0 1 1] + } + assert(aIncrements.size() == sortOrder.size()); + + std::vector<sal_Int32> aNewSortOrder(maList.size()); + sal_Int32 nPrev = -1; + for (size_t i = 0; i< aDuplicates.size(); ++i) + { + if (nPrev != aDuplicates[i]) + aNewSortOrder[i] = aDuplicates[i] + aIncrements[aDuplicates[i]]; + else + aNewSortOrder[i] = aNewSortOrder[i-1] + 1; + + nPrev = aDuplicates[i]; + + // example aNewSortOrder [3 4 0 1 2] + } + assert(aNewSortOrder.size() == maList.size()); + +#ifndef NDEBUG + { + std::vector<sal_Int32> tmp(aNewSortOrder); + std::sort(tmp.begin(), tmp.end()); + for (size_t i = 0; i < tmp.size(); ++i) + { + assert(size_t(tmp[i]) == i); + } + } +#endif + + SdrModel & rModel(getSdrPageFromSdrObjList()->getSdrModelFromSdrPage()); + bool const isUndo(rModel.IsUndoEnabled()); + if (isUndo) + { + rModel.AddUndo(SdrUndoFactory::CreateUndoSort(*getSdrPageFromSdrObjList(), sortOrder)); + } + + for (size_t i = 0; i < aNewSortOrder.size(); ++i) + { + aNewList[i] = maList[ aNewSortOrder[i] ]; + aNewList[i]->SetOrdNum(i); + } + + std::swap(aNewList, maList); +} + +const tools::Rectangle& SdrObjList::GetAllObjSnapRect() const +{ + if (mbRectsDirty) { + const_cast<SdrObjList*>(this)->RecalcRects(); + const_cast<SdrObjList*>(this)->mbRectsDirty=false; + } + return maSdrObjListSnapRect; +} + +const tools::Rectangle& SdrObjList::GetAllObjBoundRect() const +{ + // #i106183# for deep group hierarchies like in chart2, the invalidates + // through the hierarchy are not correct; use a 2nd hint for the needed + // recalculation. Future versions will have no bool flag at all, but + // just maSdrObjListOutRect in empty state to represent an invalid state, thus + // it's a step in the right direction. + if (mbRectsDirty || maSdrObjListOutRect.IsEmpty()) + { + const_cast<SdrObjList*>(this)->RecalcRects(); + const_cast<SdrObjList*>(this)->mbRectsDirty=false; + } + return maSdrObjListOutRect; +} + +void SdrObjList::NbcReformatAllTextObjects() +{ + size_t nCount=GetObjCount(); + size_t nNum=0; + + while (nNum<nCount) + { + SdrObject* pObj = GetObj(nNum); + + pObj->NbcReformatText(); + nCount=GetObjCount(); // ReformatText may delete an object + nNum++; + } + +} + +void SdrObjList::ReformatAllTextObjects() +{ + NbcReformatAllTextObjects(); +} + +/** steps over all available objects and reformats all + edge objects that are connected to other objects so that + they may reposition themselves. +*/ +void SdrObjList::ReformatAllEdgeObjects() +{ + ImplReformatAllEdgeObjects(*this); +} + +void SdrObjList::ImplReformatAllEdgeObjects(const SdrObjList& rObjList) +{ + // #i120437# go over whole hierarchy, not only over object level null (seen from grouping) + for(size_t nIdx(0), nCount(rObjList.GetObjCount()); nIdx < nCount; ++nIdx) + { + SdrObject* pSdrObject(rObjList.GetObjectForNavigationPosition(nIdx)); + const SdrObjList* pChildren(pSdrObject->getChildrenOfSdrObject()); + const bool bIsGroup(nullptr != pChildren); + if(!bIsGroup) + { + // Check IsVirtualObj because sometimes we get SwDrawVirtObj here + if (pSdrObject->GetObjIdentifier() == SdrObjKind::Edge + && !pSdrObject->IsVirtualObj()) + { + SdrEdgeObj* pSdrEdgeObj = static_cast< SdrEdgeObj* >(pSdrObject); + pSdrEdgeObj->Reformat(); + } + } + else + { + ImplReformatAllEdgeObjects(*pChildren); + } + } +} + +void SdrObjList::BurnInStyleSheetAttributes() +{ + for (const rtl::Reference<SdrObject>& pObj : *this) + pObj->BurnInStyleSheetAttributes(); +} + +size_t SdrObjList::GetObjCount() const +{ + return maList.size(); +} + + +SdrObject* SdrObjList::GetObj(size_t nNum) const +{ + if (nNum < maList.size()) + return maList[nNum].get(); + + return nullptr; +} + +SdrObject* SdrObjList::GetObjByName(std::u16string_view sName) const +{ + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (pObj->GetName() == sName) + return pObj.get(); + } + return nullptr; +} + + +bool SdrObjList::IsReadOnly() const +{ + bool bRet(false); + SdrObject* pParentSdrObject(getSdrObjectFromSdrObjList()); + + if(nullptr != pParentSdrObject) + { + SdrPage* pSdrPage(pParentSdrObject->getSdrPageFromSdrObject()); + + if(nullptr != pSdrPage) + { + bRet = pSdrPage->IsReadOnly(); + } + } + + return bRet; +} + +void SdrObjList::FlattenGroups() +{ + const size_t nObj = GetObjCount(); + for( size_t i = nObj; i>0; ) + UnGroupObj(--i); +} + +void SdrObjList::UnGroupObj( size_t nObjNum ) +{ + // if the given object is no group, this method is a noop + SdrObject* pUngroupObj = GetObj( nObjNum ); + if( pUngroupObj ) + { + SdrObjList* pSrcLst = pUngroupObj->GetSubList(); + if(pSrcLst) + if(auto pUngroupGroup = dynamic_cast<SdrObjGroup*>( pUngroupObj)) + { + // ungroup recursively (has to be head recursion, + // otherwise our indices will get trashed when doing it in + // the loop) + pSrcLst->FlattenGroups(); + + // the position at which we insert the members of rUngroupGroup + size_t nInsertPos( pUngroupGroup->GetOrdNum() ); + + const size_t nCount = pSrcLst->GetObjCount(); + for( size_t i=0; i<nCount; ++i ) + { + rtl::Reference<SdrObject> pObj = pSrcLst->RemoveObject(0); + InsertObject(pObj.get(), nInsertPos); + ++nInsertPos; + } + + RemoveObject(nInsertPos); + } + } +#ifdef DBG_UTIL + else + OSL_FAIL("SdrObjList::UnGroupObj: object index invalid"); +#endif +} + +bool SdrObjList::HasObjectNavigationOrder() const { return bool(mxNavigationOrder); } + +void SdrObjList::SetObjectNavigationPosition ( + SdrObject& rObject, + const sal_uInt32 nNewPosition) +{ + // When the navigation order container has not yet been created then + // create one now. It is initialized with the z-order taken from + // maList. + if (!mxNavigationOrder) + { + mxNavigationOrder.emplace(maList.begin(), maList.end()); + } + OSL_ASSERT(bool(mxNavigationOrder)); + OSL_ASSERT( mxNavigationOrder->size() == maList.size()); + + unotools::WeakReference<SdrObject> aReference (&rObject); + + // Look up the object whose navigation position is to be changed. + auto iObject = ::std::find( + mxNavigationOrder->begin(), + mxNavigationOrder->end(), + aReference); + if (iObject == mxNavigationOrder->end()) + { + // The given object is not a member of the navigation order. + return; + } + + // Move the object to its new position. + const sal_uInt32 nOldPosition = ::std::distance(mxNavigationOrder->begin(), iObject); + if (nOldPosition == nNewPosition) + return; + + mxNavigationOrder->erase(iObject); + sal_uInt32 nInsertPosition (nNewPosition); + // Adapt insertion position for the just erased object. + if (nNewPosition >= nOldPosition) + nInsertPosition -= 1; + if (nInsertPosition >= mxNavigationOrder->size()) + mxNavigationOrder->push_back(aReference); + else + mxNavigationOrder->insert(mxNavigationOrder->begin()+nInsertPosition, aReference); + + mbIsNavigationOrderDirty = true; + + // The navigation order is written out to file so mark the model as modified. + rObject.getSdrModelFromSdrObject().SetChanged(); +} + + +SdrObject* SdrObjList::GetObjectForNavigationPosition (const sal_uInt32 nNavigationPosition) const +{ + if (HasObjectNavigationOrder()) + { + // There is a user defined navigation order. Make sure the object + // index is correct and look up the object in mxNavigationOrder. + if (nNavigationPosition >= mxNavigationOrder->size()) + { + OSL_ASSERT(nNavigationPosition < mxNavigationOrder->size()); + } + else + return (*mxNavigationOrder)[nNavigationPosition].get().get(); + } + else + { + // There is no user defined navigation order. Use the z-order + // instead. + if (nNavigationPosition >= maList.size()) + { + OSL_ASSERT(nNavigationPosition < maList.size()); + } + else + return maList[nNavigationPosition].get(); + } + return nullptr; +} + + +void SdrObjList::ClearObjectNavigationOrder() +{ + mxNavigationOrder.reset(); + mbIsNavigationOrderDirty = true; +} + + +bool SdrObjList::RecalcNavigationPositions() +{ + if (mbIsNavigationOrderDirty) + { + if (mxNavigationOrder) + { + mbIsNavigationOrderDirty = false; + + sal_uInt32 nIndex (0); + for (auto& rpObject : *mxNavigationOrder) + { + rpObject.get()->SetNavigationPosition(nIndex); + ++nIndex; + } + } + } + + return bool(mxNavigationOrder); +} + + +void SdrObjList::SetNavigationOrder (const uno::Reference<container::XIndexAccess>& rxOrder) +{ + if (rxOrder.is()) + { + const sal_Int32 nCount = rxOrder->getCount(); + if (static_cast<sal_uInt32>(nCount) != maList.size()) + return; + + if (!mxNavigationOrder) + mxNavigationOrder = std::vector<unotools::WeakReference<SdrObject>>(nCount); + + for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex) + { + uno::Reference<uno::XInterface> xShape (rxOrder->getByIndex(nIndex), uno::UNO_QUERY); + SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape); + if (pObject == nullptr) + break; + (*mxNavigationOrder)[nIndex] = pObject; + } + + mbIsNavigationOrderDirty = true; + } + else + { + ClearObjectNavigationOrder(); + } +} + + +void SdrObjList::InsertObjectIntoContainer ( + SdrObject& rObject, + const sal_uInt32 nInsertPosition) +{ + OSL_ASSERT(nInsertPosition<=maList.size()); + + // Update the navigation positions. + if (HasObjectNavigationOrder()) + { + // The new object does not have a user defined position so append it + // to the list. + rObject.SetNavigationPosition(mxNavigationOrder->size()); + mxNavigationOrder->push_back(&rObject); + } + + // Insert object into object list. Because the insert() method requires + // a valid iterator as insertion position, we have to use push_back() to + // insert at the end of the list. + if (nInsertPosition >= maList.size()) + maList.push_back(&rObject); + else + maList.insert(maList.begin()+nInsertPosition, &rObject); + mbObjOrdNumsDirty=true; +} + + +void SdrObjList::ReplaceObjectInContainer ( + SdrObject& rNewObject, + const sal_uInt32 nObjectPosition) +{ + if (nObjectPosition >= maList.size()) + { + OSL_ASSERT(nObjectPosition<maList.size()); + return; + } + + // Update the navigation positions. + if (HasObjectNavigationOrder()) + { + // A user defined position of the object that is to be replaced is + // not transferred to the new object so erase the former and append + // the later object from/to the navigation order. + OSL_ASSERT(nObjectPosition < maList.size()); + unotools::WeakReference<SdrObject> aReference (maList[nObjectPosition].get()); + auto iObject = ::std::find( + mxNavigationOrder->begin(), + mxNavigationOrder->end(), + aReference); + if (iObject != mxNavigationOrder->end()) + mxNavigationOrder->erase(iObject); + + mxNavigationOrder->push_back(&rNewObject); + + mbIsNavigationOrderDirty = true; + } + + maList[nObjectPosition] = &rNewObject; + mbObjOrdNumsDirty=true; +} + + +void SdrObjList::RemoveObjectFromContainer ( + const sal_uInt32 nObjectPosition) +{ + if (nObjectPosition >= maList.size()) + { + OSL_ASSERT(nObjectPosition<maList.size()); + return; + } + + // Update the navigation positions. + if (HasObjectNavigationOrder()) + { + unotools::WeakReference<SdrObject> aReference (maList[nObjectPosition]); + auto iObject = ::std::find( + mxNavigationOrder->begin(), + mxNavigationOrder->end(), + aReference); + if (iObject != mxNavigationOrder->end()) + mxNavigationOrder->erase(iObject); + mbIsNavigationOrderDirty = true; + } + + maList.erase(maList.begin()+nObjectPosition); + mbObjOrdNumsDirty=true; +} + +void SdrObjList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrObjList")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*this).name())); + + for (const rtl::Reference<SdrObject>& pObject : *this) + pObject->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + + +void SdrPageGridFrameList::Clear() +{ + sal_uInt16 nCount=GetCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + delete GetObject(i); + } + m_aList.clear(); +} + + +// PageUser section + +void SdrPage::AddPageUser(sdr::PageUser& rNewUser) +{ + maPageUsers.push_back(&rNewUser); +} + +void SdrPage::RemovePageUser(sdr::PageUser& rOldUser) +{ + const sdr::PageUserVector::iterator aFindResult = ::std::find(maPageUsers.begin(), maPageUsers.end(), &rOldUser); + if(aFindResult != maPageUsers.end()) + { + maPageUsers.erase(aFindResult); + } +} + + +// DrawContact section + +std::unique_ptr<sdr::contact::ViewContact> SdrPage::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfSdrPage>(*this); +} + +const sdr::contact::ViewContact& SdrPage::GetViewContact() const +{ + if (!mpViewContact) + const_cast<SdrPage*>(this)->mpViewContact = + const_cast<SdrPage*>(this)->CreateObjectSpecificViewContact(); + + return *mpViewContact; +} + +sdr::contact::ViewContact& SdrPage::GetViewContact() +{ + if (!mpViewContact) + mpViewContact = CreateObjectSpecificViewContact(); + + return *mpViewContact; +} + +void SdrPageProperties::ImpRemoveStyleSheet() +{ + if(mpStyleSheet) + { + EndListening(*mpStyleSheet); + maProperties.SetParent(nullptr); + mpStyleSheet = nullptr; + } +} + +void SdrPageProperties::ImpAddStyleSheet(SfxStyleSheet& rNewStyleSheet) +{ + if(mpStyleSheet != &rNewStyleSheet) + { + ImpRemoveStyleSheet(); + mpStyleSheet = &rNewStyleSheet; + StartListening(rNewStyleSheet); + maProperties.SetParent(&rNewStyleSheet.GetItemSet()); + } +} + +static void ImpPageChange(SdrPage& rSdrPage) +{ + rSdrPage.ActionChanged(); + rSdrPage.getSdrModelFromSdrPage().SetChanged(); + SdrHint aHint(SdrHintKind::PageOrderChange, &rSdrPage); + rSdrPage.getSdrModelFromSdrPage().Broadcast(aHint); +} + +SdrPageProperties::SdrPageProperties(SdrPage& rSdrPage) + : mpSdrPage(&rSdrPage) + , mpStyleSheet(nullptr) + , maProperties( + mpSdrPage->getSdrModelFromSdrPage().GetItemPool(), + svl::Items<XATTR_FILL_FIRST, XATTR_FILL_LAST>) +{ + if (!rSdrPage.IsMasterPage()) + { + maProperties.Put(XFillStyleItem(drawing::FillStyle_NONE)); + } +} + +SdrPageProperties::~SdrPageProperties() +{ + ImpRemoveStyleSheet(); +} + +void SdrPageProperties::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint) +{ + switch(rHint.GetId()) + { + case SfxHintId::DataChanged : + { + // notify change, broadcast + ImpPageChange(*mpSdrPage); + break; + } + case SfxHintId::Dying : + { + // Style needs to be forgotten + ImpRemoveStyleSheet(); + break; + } + default: break; + } +} + +bool SdrPageProperties::isUsedByModel() const +{ + assert(mpSdrPage); + return mpSdrPage->IsInserted(); +} + + +void SdrPageProperties::PutItemSet(const SfxItemSet& rSet) +{ + OSL_ENSURE(!mpSdrPage->IsMasterPage(), "Item set at MasterPage Attributes (!)"); + maProperties.Put(rSet); + ImpPageChange(*mpSdrPage); +} + +void SdrPageProperties::PutItem(const SfxPoolItem& rItem) +{ + OSL_ENSURE(!mpSdrPage->IsMasterPage(), "Item set at MasterPage Attributes (!)"); + maProperties.Put(rItem); + ImpPageChange(*mpSdrPage); +} + +void SdrPageProperties::ClearItem(const sal_uInt16 nWhich) +{ + maProperties.ClearItem(nWhich); + ImpPageChange(*mpSdrPage); +} + +void SdrPageProperties::SetStyleSheet(SfxStyleSheet* pStyleSheet) +{ + if(pStyleSheet) + { + ImpAddStyleSheet(*pStyleSheet); + } + else + { + ImpRemoveStyleSheet(); + } + + ImpPageChange(*mpSdrPage); +} + +void SdrPageProperties::setTheme(std::shared_ptr<model::Theme> const& pTheme) +{ + if (!mpSdrPage) + return; + + // Only set the theme on a master page, else set it on the model + + if (mpSdrPage->IsMasterPage()) + { + if (mpTheme != pTheme) + mpTheme = pTheme; + } + else + { + mpSdrPage->getSdrModelFromSdrPage().setTheme(pTheme); + } +} + +std::shared_ptr<model::Theme> const& SdrPageProperties::getTheme() const +{ + // if set - page theme has priority + if (mpTheme) + return mpTheme; + // else the model theme + else if (mpSdrPage) + return mpSdrPage->getSdrModelFromSdrPage().getTheme(); + // else return empty shared_ptr + return mpTheme; +} + +void SdrPageProperties::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrPageProperties")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + if (mpTheme) + { + mpTheme->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +SdrPage::SdrPage(SdrModel& rModel, bool bMasterPage) +: mrSdrModelFromSdrPage(rModel), + mnWidth(10), + mnHeight(10), + mnBorderLeft(0), + mnBorderUpper(0), + mnBorderRight(0), + mnBorderLower(0), + mpLayerAdmin(new SdrLayerAdmin(&rModel.GetLayerAdmin())), + m_nPageNum(0), + mbMaster(bMasterPage), + mbInserted(false), + mbObjectsNotPersistent(false), + mbPageBorderOnlyLeftRight(false) +{ + mpSdrPageProperties.reset(new SdrPageProperties(*this)); +} + +SdrPage::~SdrPage() +{ + if( mxUnoPage.is() ) try + { + uno::Reference< lang::XComponent > xPageComponent( mxUnoPage, uno::UNO_QUERY_THROW ); + mxUnoPage.clear(); + xPageComponent->dispose(); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // tell all the registered PageUsers that the page is in destruction + // This causes some (all?) PageUsers to remove themselves from the list + // of page users. Therefore we have to use a copy of the list for the + // iteration. + sdr::PageUserVector aListCopy (maPageUsers.begin(), maPageUsers.end()); + for(sdr::PageUser* pPageUser : aListCopy) + { + DBG_ASSERT(pPageUser, "SdrPage::~SdrPage: corrupt PageUser list (!)"); + pPageUser->PageInDestruction(*this); + } + + // Clear the vector. This means that user do not need to call RemovePageUser() + // when they get called from PageInDestruction(). + maPageUsers.clear(); + + mpLayerAdmin.reset(); + + TRG_ClearMasterPage(); + + mpViewContact.reset(); + mpSdrPageProperties.reset(); +} + +void SdrPage::lateInit(const SdrPage& rSrcPage) +{ + assert(!mpViewContact); + assert(!mxUnoPage.is()); + + // copy all the local parameters to make this instance + // a valid copy of source page before copying and inserting + // the contained objects + mbMaster = rSrcPage.mbMaster; + mbPageBorderOnlyLeftRight = rSrcPage.mbPageBorderOnlyLeftRight; + mnWidth = rSrcPage.mnWidth; + mnHeight = rSrcPage.mnHeight; + mnBorderLeft = rSrcPage.mnBorderLeft; + mnBorderUpper = rSrcPage.mnBorderUpper; + mnBorderRight = rSrcPage.mnBorderRight; + mnBorderLower = rSrcPage.mnBorderLower; + mbBackgroundFullSize = rSrcPage.mbBackgroundFullSize; + m_nPageNum = rSrcPage.m_nPageNum; + + if(rSrcPage.TRG_HasMasterPage()) + { + TRG_SetMasterPage(rSrcPage.TRG_GetMasterPage()); + TRG_SetMasterPageVisibleLayers(rSrcPage.TRG_GetMasterPageVisibleLayers()); + } + else + { + TRG_ClearMasterPage(); + } + + mbObjectsNotPersistent = rSrcPage.mbObjectsNotPersistent; + + { + mpSdrPageProperties.reset(new SdrPageProperties(*this)); + + if(!IsMasterPage()) + { + mpSdrPageProperties->PutItemSet(rSrcPage.getSdrPageProperties().GetItemSet()); + } + + mpSdrPageProperties->SetStyleSheet(rSrcPage.getSdrPageProperties().GetStyleSheet()); + } + + // Now copy the contained objects + if(0 != rSrcPage.GetObjCount()) + { + CopyObjects(rSrcPage); + } +} + +rtl::Reference<SdrPage> SdrPage::CloneSdrPage(SdrModel& rTargetModel) const +{ + rtl::Reference<SdrPage> pClonedPage(new SdrPage(rTargetModel)); + pClonedPage->lateInit(*this); + return pClonedPage; +} + +void SdrPage::SetSize(const Size& aSiz) +{ + bool bChanged(false); + + if(aSiz.Width() != mnWidth) + { + mnWidth = aSiz.Width(); + bChanged = true; + } + + if(aSiz.Height() != mnHeight) + { + mnHeight = aSiz.Height(); + bChanged = true; + } + + if(bChanged) + { + SetChanged(); + } +} + +Size SdrPage::GetSize() const +{ + return Size(mnWidth,mnHeight); +} + +tools::Long SdrPage::GetWidth() const +{ + return mnWidth; +} + +void SdrPage::SetOrientation(Orientation eOri) +{ + // square: handle like portrait format + Size aSiz(GetSize()); + if (aSiz.Width()!=aSiz.Height()) { + if ((eOri==Orientation::Portrait) == (aSiz.Width()>aSiz.Height())) { + // coverity[swapped_arguments : FALSE] - this is in the correct order + SetSize(Size(aSiz.Height(),aSiz.Width())); + } + } +} + +Orientation SdrPage::GetOrientation() const +{ + // square: handle like portrait format + Orientation eRet=Orientation::Portrait; + Size aSiz(GetSize()); + if (aSiz.Width()>aSiz.Height()) eRet=Orientation::Landscape; + return eRet; +} + +tools::Long SdrPage::GetHeight() const +{ + return mnHeight; +} + +void SdrPage::SetBorder(sal_Int32 nLft, sal_Int32 nUpp, sal_Int32 nRgt, sal_Int32 nLwr) +{ + bool bChanged(false); + + if(mnBorderLeft != nLft) + { + mnBorderLeft = nLft; + bChanged = true; + } + + if(mnBorderUpper != nUpp) + { + mnBorderUpper = nUpp; + bChanged = true; + } + + if(mnBorderRight != nRgt) + { + mnBorderRight = nRgt; + bChanged = true; + } + + if(mnBorderLower != nLwr) + { + mnBorderLower = nLwr; + bChanged = true; + } + + if(bChanged) + { + SetChanged(); + } +} + +void SdrPage::SetLeftBorder(sal_Int32 nBorder) +{ + if(mnBorderLeft != nBorder) + { + mnBorderLeft = nBorder; + SetChanged(); + } +} + +void SdrPage::SetUpperBorder(sal_Int32 nBorder) +{ + if(mnBorderUpper != nBorder) + { + mnBorderUpper = nBorder; + SetChanged(); + } +} + +void SdrPage::SetRightBorder(sal_Int32 nBorder) +{ + if(mnBorderRight != nBorder) + { + mnBorderRight=nBorder; + SetChanged(); + } +} + +void SdrPage::SetLowerBorder(sal_Int32 nBorder) +{ + if(mnBorderLower != nBorder) + { + mnBorderLower=nBorder; + SetChanged(); + } +} + +sal_Int32 SdrPage::GetLeftBorder() const +{ + return mnBorderLeft; +} + +sal_Int32 SdrPage::GetUpperBorder() const +{ + return mnBorderUpper; +} + +sal_Int32 SdrPage::GetRightBorder() const +{ + return mnBorderRight; +} + +sal_Int32 SdrPage::GetLowerBorder() const +{ + return mnBorderLower; +} + +void SdrPage::SetBackgroundFullSize(bool const bIn) +{ + if (bIn != mbBackgroundFullSize) + { + mbBackgroundFullSize = bIn; + SetChanged(); + } +} + +bool SdrPage::IsBackgroundFullSize() const +{ + return mbBackgroundFullSize; +} + +// #i68775# React on PageNum changes (from Model in most cases) +void SdrPage::SetPageNum(sal_uInt16 nNew) +{ + if(nNew != m_nPageNum) + { + // change + m_nPageNum = nNew; + + // notify visualisations, also notifies e.g. buffered MasterPages + ActionChanged(); + } +} + +sal_uInt16 SdrPage::GetPageNum() const +{ + if (!mbInserted) + return 0; + + if (mbMaster) { + if (getSdrModelFromSdrPage().IsMPgNumsDirty()) + getSdrModelFromSdrPage().RecalcPageNums(true); + } else { + if (getSdrModelFromSdrPage().IsPagNumsDirty()) + getSdrModelFromSdrPage().RecalcPageNums(false); + } + return m_nPageNum; +} + +void SdrPage::SetChanged() +{ + // For test purposes, use the new ViewContact for change + // notification now. + ActionChanged(); + getSdrModelFromSdrPage().SetChanged(); +} + +SdrPage* SdrPage::getSdrPageFromSdrObjList() const +{ + return const_cast< SdrPage* >(this); +} + +// MasterPage interface + +void SdrPage::TRG_SetMasterPage(SdrPage& rNew) +{ + if(mpMasterPageDescriptor && &(mpMasterPageDescriptor->GetUsedPage()) == &rNew) + return; + + if(mpMasterPageDescriptor) + TRG_ClearMasterPage(); + + mpMasterPageDescriptor.reset(new sdr::MasterPageDescriptor(*this, rNew)); + GetViewContact().ActionChanged(); +} + +void SdrPage::TRG_ClearMasterPage() +{ + if(mpMasterPageDescriptor) + { + SetChanged(); + + // the flushViewObjectContacts() will do needed invalidates by deleting the involved VOCs + mpMasterPageDescriptor->GetUsedPage().GetViewContact().flushViewObjectContacts(); + + mpMasterPageDescriptor.reset(); + } +} + +SdrPage& SdrPage::TRG_GetMasterPage() const +{ + DBG_ASSERT(mpMasterPageDescriptor != nullptr, "TRG_GetMasterPage(): No MasterPage available. Use TRG_HasMasterPage() before access (!)"); + return mpMasterPageDescriptor->GetUsedPage(); +} + +const SdrLayerIDSet& SdrPage::TRG_GetMasterPageVisibleLayers() const +{ + DBG_ASSERT(mpMasterPageDescriptor != nullptr, "TRG_GetMasterPageVisibleLayers(): No MasterPage available. Use TRG_HasMasterPage() before access (!)"); + return mpMasterPageDescriptor->GetVisibleLayers(); +} + +void SdrPage::TRG_SetMasterPageVisibleLayers(const SdrLayerIDSet& rNew) +{ + DBG_ASSERT(mpMasterPageDescriptor != nullptr, "TRG_SetMasterPageVisibleLayers(): No MasterPage available. Use TRG_HasMasterPage() before access (!)"); + mpMasterPageDescriptor->SetVisibleLayers(rNew); +} + +sdr::contact::ViewContact& SdrPage::TRG_GetMasterPageDescriptorViewContact() const +{ + DBG_ASSERT(mpMasterPageDescriptor != nullptr, "TRG_GetMasterPageDescriptorViewContact(): No MasterPage available. Use TRG_HasMasterPage() before access (!)"); + return mpMasterPageDescriptor->GetViewContact(); +} + +// used from SdrModel::RemoveMasterPage +void SdrPage::TRG_ImpMasterPageRemoved(const SdrPage& rRemovedPage) +{ + if(TRG_HasMasterPage()) + { + if(&TRG_GetMasterPage() == &rRemovedPage) + { + TRG_ClearMasterPage(); + } + } +} + +void SdrPage::MakePageObjectsNamesUnique() +{ + std::unordered_set<OUString> aNameSet; + for (const rtl::Reference<SdrObject>& pObj : *this) + { + if (!pObj->GetName().isEmpty()) + { + pObj->MakeNameUnique(aNameSet); + SdrObjList* pSdrObjList = pObj->GetSubList(); // group + if (pSdrObjList) + { + SdrObject* pListObj; + SdrObjListIter aIter(pSdrObjList, SdrIterMode::DeepWithGroups); + while (aIter.IsMore()) + { + pListObj = aIter.Next(); + pListObj->MakeNameUnique(aNameSet); + } + } + } + } +} + +const SdrPageGridFrameList* SdrPage::GetGridFrameList(const SdrPageView* /*pPV*/, const tools::Rectangle* /*pRect*/) const +{ + return nullptr; +} + +const SdrLayerAdmin& SdrPage::GetLayerAdmin() const +{ + return *mpLayerAdmin; +} + +SdrLayerAdmin& SdrPage::GetLayerAdmin() +{ + return *mpLayerAdmin; +} + +OUString SdrPage::GetLayoutName() const +{ + return OUString(); +} + +void SdrPage::SetInserted( bool bIns ) +{ + if( mbInserted == bIns ) + return; + + mbInserted = bIns; + + // #i120437# go over whole hierarchy, not only over object level null (seen from grouping) + SdrObjListIter aIter(this, SdrIterMode::DeepNoGroups); + + while ( aIter.IsMore() ) + { + SdrObject* pObj = aIter.Next(); + if ( auto pOleObj = dynamic_cast<SdrOle2Obj* >(pObj) ) + { + if( mbInserted ) + pOleObj->Connect(); + else + pOleObj->Disconnect(); + } + } +} + +void SdrPage::SetUnoPage(uno::Reference<drawing::XDrawPage> const& xNewPage) +{ + mxUnoPage = xNewPage; +} + +uno::Reference< uno::XInterface > const & SdrPage::getUnoPage() +{ + if( !mxUnoPage.is() ) + { + // create one + mxUnoPage = createUnoPage(); + } + + return mxUnoPage; +} + +uno::Reference< uno::XInterface > SdrPage::createUnoPage() +{ + return cppu::getXWeak(new SvxDrawPage(this)); +} + +SfxStyleSheet* SdrPage::GetTextStyleSheetForObject( SdrObject* pObj ) const +{ + return pObj->GetStyleSheet(); +} + +/** returns an averaged background color of this page */ +// #i75566# GetBackgroundColor -> GetPageBackgroundColor and bScreenDisplay hint value +Color SdrPage::GetPageBackgroundColor( SdrPageView const * pView, bool bScreenDisplay ) const +{ + Color aColor; + + if(bScreenDisplay && (!pView || pView->GetApplicationDocumentColor() == COL_AUTO)) + { + svtools::ColorConfig aColorConfig; + aColor = aColorConfig.GetColorValue( svtools::DOCCOLOR ).nColor; + } + else + { + aColor = pView->GetApplicationDocumentColor(); + } + + const SfxItemSet* pBackgroundFill = &getSdrPageProperties().GetItemSet(); + + if(!IsMasterPage() && TRG_HasMasterPage()) + { + if(drawing::FillStyle_NONE == pBackgroundFill->Get(XATTR_FILLSTYLE).GetValue()) + { + pBackgroundFill = &TRG_GetMasterPage().getSdrPageProperties().GetItemSet(); + } + } + + if (auto oColor = GetDraftFillColor(*pBackgroundFill)) + aColor = *oColor; + + return aColor; +} + +/** *deprecated, use GetBackgroundColor with SdrPageView */ +Color SdrPage::GetPageBackgroundColor() const +// #i75566# GetBackgroundColor -> GetPageBackgroundColor +{ + return GetPageBackgroundColor( nullptr ); +} + +/** this method returns true if the object from the ViewObjectContact should + be visible on this page while rendering. + bEdit selects if visibility test is for an editing view or a final render, + like printing. +*/ +bool SdrPage::checkVisibility( + const sdr::contact::ViewObjectContact& /*rOriginal*/, + const sdr::contact::DisplayInfo& /*rDisplayInfo*/, + bool /*bEdit*/) +{ + // this will be handled in the application if needed + return true; +} + +void SdrPage::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrPage")); + SdrObjList::dumpAsXml(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("width")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%s", + BAD_CAST(OString::number(mnWidth).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("height")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%s", + BAD_CAST(OString::number(mnHeight).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + if (mpSdrPageProperties) + { + mpSdrPageProperties->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +// DrawContact support: Methods for handling Page changes +void SdrPage::ActionChanged() +{ + // Do necessary ViewContact actions + GetViewContact().ActionChanged(); + + // #i48535# also handle MasterPage change + if(TRG_HasMasterPage()) + { + TRG_GetMasterPageDescriptorViewContact().ActionChanged(); + } +} + +SdrPageProperties& SdrPage::getSdrPageProperties() +{ + return *mpSdrPageProperties; +} + +const SdrPageProperties& SdrPage::getSdrPageProperties() const +{ + return *mpSdrPageProperties; +} + +const SdrPageProperties* SdrPage::getCorrectSdrPageProperties() const +{ + if(mpMasterPageDescriptor) + { + return mpMasterPageDescriptor->getCorrectSdrPageProperties(); + } + else + { + return &getSdrPageProperties(); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdpagv.cxx b/svx/source/svdraw/svdpagv.cxx new file mode 100644 index 0000000000..86210865e7 --- /dev/null +++ b/svx/source/svdraw/svdpagv.cxx @@ -0,0 +1,895 @@ +/* -*- 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/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdview.hxx> + +#include <svx/svdobj.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdtypes.hxx> + +#include <svx/sdr/contact/viewobjectcontactredirector.hxx> + +#include <algorithm> + +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/scopeguard.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +// interface to SdrPageWindow + +SdrPageWindow* SdrPageView::FindPageWindow(const SdrPaintWindow& rPaintWindow) const +{ + for(auto & a : maPageWindows) + { + if(&(a->GetPaintWindow()) == &rPaintWindow) + { + return a.get(); + } + } + + return nullptr; +} + +const SdrPageWindow* SdrPageView::FindPatchedPageWindow( const OutputDevice& _rOutDev ) const +{ + for ( auto const & pPageWindow : maPageWindows ) + { + const SdrPaintWindow& rPaintWindow( pPageWindow->GetOriginalPaintWindow() ? *pPageWindow->GetOriginalPaintWindow() : pPageWindow->GetPaintWindow() ); + if ( &rPaintWindow.GetOutputDevice() == &_rOutDev ) + { + return pPageWindow.get(); + } + } + + return nullptr; +} + +SdrPageWindow* SdrPageView::FindPageWindow(const OutputDevice& rOutDev) const +{ + for ( auto const & pPageWindow : maPageWindows ) + { + if(&(pPageWindow->GetPaintWindow().GetOutputDevice()) == &rOutDev) + { + return pPageWindow.get(); + } + } + + return nullptr; +} + +SdrPageWindow* SdrPageView::GetPageWindow(sal_uInt32 nIndex) const +{ + return maPageWindows[nIndex].get(); +} + +SdrPageView::SdrPageView(SdrPage* pPage1, SdrView& rNewView) +: mrView(rNewView), + // col_auto color lets the view takes the default SvxColorConfig entry + maDocumentColor( COL_AUTO ), + maBackgroundColor( COL_AUTO ), // #i48367# also react on autocolor + mpPreparedPageWindow(nullptr) // #i72752# +{ + mpPage = pPage1; + + if(mpPage) + { + maPageOrigin.setX(mpPage->GetLeftBorder() ); + maPageOrigin.setY(mpPage->GetUpperBorder() ); + } + // For example, in the case of charts, there is a LayerAdmin, but it has no valid values. Therefore + // a solution like pLayerAdmin->getVisibleLayersODF(aLayerVisi) is not possible. So use the + // generic SetAll() for now. + aLayerVisi.SetAll(); + aLayerPrn.SetAll(); + + mbHasMarked = false; + mbVisible = false; + pCurrentList = nullptr; + pCurrentGroup = nullptr; + SetCurrentGroupAndList(nullptr, mpPage); + + for(sal_uInt32 a(0); a < rNewView.PaintWindowCount(); a++) + { + AddPaintWindowToPageView(*rNewView.GetPaintWindow(a)); + } +} + +SdrPageView::~SdrPageView() +{ +} + +void SdrPageView::AddPaintWindowToPageView(SdrPaintWindow& rPaintWindow) +{ + if(!FindPageWindow(rPaintWindow)) + { + maPageWindows.emplace_back(new SdrPageWindow(*this, rPaintWindow)); + } +} + +void SdrPageView::RemovePaintWindowFromPageView(SdrPaintWindow& rPaintWindow) +{ + auto it = std::find_if(maPageWindows.begin(), maPageWindows.end(), + [&rPaintWindow](const std::unique_ptr<SdrPageWindow>& rpWindow) { + return &(rpWindow->GetPaintWindow()) == &rPaintWindow; + }); + if (it != maPageWindows.end()) + maPageWindows.erase(it); +} + +css::uno::Reference< css::awt::XControlContainer > SdrPageView::GetControlContainer( const OutputDevice& _rDevice ) const +{ + css::uno::Reference< css::awt::XControlContainer > xReturn; + const SdrPageWindow* pCandidate = FindPatchedPageWindow( _rDevice ); + + if ( pCandidate ) + xReturn = pCandidate->GetControlContainer(); + + return xReturn; +} + +void SdrPageView::ModelHasChanged() +{ + if (GetCurrentGroup()!=nullptr) CheckCurrentGroup(); +} + +bool SdrPageView::IsReadOnly() const +{ + return (nullptr == GetPage() || GetView().GetModel().IsReadOnly() || GetPage()->IsReadOnly() || GetObjList()->IsReadOnly()); +} + +void SdrPageView::Show() +{ + if(!IsVisible()) + { + mbVisible = true; + + for(sal_uInt32 a(0); a < GetView().PaintWindowCount(); a++) + { + AddPaintWindowToPageView(*GetView().GetPaintWindow(a)); + } + } +} + +void SdrPageView::Hide() +{ + if(IsVisible()) + { + if (!comphelper::LibreOfficeKit::isActive()) + { + InvalidateAllWin(); + } + mbVisible = false; + maPageWindows.clear(); + } +} + +tools::Rectangle SdrPageView::GetPageRect() const +{ + if (GetPage()==nullptr) return tools::Rectangle(); + return tools::Rectangle(Point(),Size(GetPage()->GetWidth()+1,GetPage()->GetHeight()+1)); +} + +void SdrPageView::InvalidateAllWin() +{ + if(IsVisible() && GetPage()) + { + tools::Rectangle aRect(Point(0,0),Size(GetPage()->GetWidth()+1,GetPage()->GetHeight()+1)); + aRect.Union(GetPage()->GetAllObjBoundRect()); + GetView().InvalidateAllWin(aRect); + } +} + + +void SdrPageView::PrePaint() +{ + const sal_uInt32 nCount(PageWindowCount()); + + for(sal_uInt32 a(0); a < nCount; a++) + { + SdrPageWindow* pCandidate = GetPageWindow(a); + + if(pCandidate) + { + pCandidate->PrePaint(); + } + } +} + +void SdrPageView::CompleteRedraw( + SdrPaintWindow& rPaintWindow, const vcl::Region& rReg, sdr::contact::ViewObjectContactRedirector* pRedirector ) +{ + if(!GetPage()) + return; + + SdrPageWindow* pPageWindow = FindPageWindow(rPaintWindow); + std::unique_ptr<SdrPageWindow> pTempPageWindow; + + if(!pPageWindow) + { + // create temp PageWindow + pTempPageWindow.reset(new SdrPageWindow(*this, rPaintWindow)); + pPageWindow = pTempPageWindow.get(); + } + + // do the redraw + pPageWindow->PrepareRedraw(rReg); + pPageWindow->RedrawAll(pRedirector); +} + + +// #i74769# use SdrPaintWindow directly + +void SdrPageView::setPreparedPageWindow(SdrPageWindow* pKnownTarget) +{ + // #i72752# remember prepared SdrPageWindow + mpPreparedPageWindow = pKnownTarget; +} + +void SdrPageView::DrawLayer(SdrLayerID nID, OutputDevice* pGivenTarget, + sdr::contact::ViewObjectContactRedirector* pRedirector, + const tools::Rectangle& rRect, basegfx::B2IRectangle const*const pPageFrame) +{ + if(!GetPage()) + return; + + if(pGivenTarget) + { + SdrPageWindow* pKnownTarget = FindPageWindow(*pGivenTarget); + + if(pKnownTarget) + { + // paint known target + pKnownTarget->RedrawLayer(&nID, pRedirector, pPageFrame); + } + else + { + // #i72752# DrawLayer() uses an OutputDevice different from BeginDrawLayer. This happens + // e.g. when SW paints a single text line in text edit mode. Try to use it + SdrPageWindow* pPreparedTarget = mpPreparedPageWindow; + + if(pPreparedTarget) + { + // if we have a prepared target, do not use a new SdrPageWindow since this + // works but is expensive. Just use a temporary PaintWindow + SdrPaintWindow aTemporaryPaintWindow(mrView, *pGivenTarget); + + // Copy existing paint region to use the same as prepared in BeginDrawLayer + SdrPaintWindow& rExistingPaintWindow = pPreparedTarget->GetPaintWindow(); + const vcl::Region& rExistingRegion = rExistingPaintWindow.GetRedrawRegion(); + bool bUseRect(false); + if (!rRect.IsEmpty()) + { + vcl::Region r(rExistingRegion); + r.Intersect(rRect); + // fdo#74435: FIXME: visibility check broken if empty + if (!r.IsEmpty()) + bUseRect = true; + } + if (!bUseRect) + aTemporaryPaintWindow.SetRedrawRegion(rExistingRegion); + else + aTemporaryPaintWindow.SetRedrawRegion(vcl::Region(rRect)); + + // patch the ExistingPageWindow + auto pPreviousWindow = pPreparedTarget->patchPaintWindow(aTemporaryPaintWindow); + // unpatch window when leaving the scope + const ::comphelper::ScopeGuard aGuard( + [&pPreviousWindow, &pPreparedTarget]() { pPreparedTarget->unpatchPaintWindow(pPreviousWindow); } ); + // redraw the layer + pPreparedTarget->RedrawLayer(&nID, pRedirector, pPageFrame); + } + else + { + OSL_FAIL("SdrPageView::DrawLayer: Creating temporary SdrPageWindow (ObjectContact), this should never be needed (!)"); + + // None of the known OutputDevices is the target of this paint, use + // a temporary SdrPageWindow for this Redraw. + SdrPaintWindow aTemporaryPaintWindow(mrView, *pGivenTarget); + SdrPageWindow aTemporaryPageWindow(*this, aTemporaryPaintWindow); + + // #i72752# + // Copy existing paint region if other PageWindows exist, this was created by + // PrepareRedraw() from BeginDrawLayer(). Needs to be used e.g. when suddenly SW + // paints into an unknown device other than the view was created for (e.g. VirtualDevice) + if(PageWindowCount()) + { + SdrPageWindow* pExistingPageWindow = GetPageWindow(0); + SdrPaintWindow& rExistingPaintWindow = pExistingPageWindow->GetPaintWindow(); + const vcl::Region& rExistingRegion = rExistingPaintWindow.GetRedrawRegion(); + aTemporaryPaintWindow.SetRedrawRegion(rExistingRegion); + } + + aTemporaryPageWindow.RedrawLayer(&nID, pRedirector, nullptr); + } + } + } + else + { + // paint in all known windows + for(sal_uInt32 a(0); a < PageWindowCount(); a++) + { + SdrPageWindow* pTarget = GetPageWindow(a); + pTarget->RedrawLayer(&nID, pRedirector, nullptr); + } + } +} + +void SdrPageView::SetDesignMode( bool _bDesignMode ) const +{ + for ( sal_uInt32 i = 0; i < PageWindowCount(); ++i ) + { + const SdrPageWindow& rPageViewWindow = *GetPageWindow(i); + rPageViewWindow.SetDesignMode( _bDesignMode ); + } +} + + +void SdrPageView::DrawPageViewGrid(OutputDevice& rOut, const tools::Rectangle& rRect, Color aColor) +{ + if (GetPage()==nullptr) + return; + + tools::Long nx1=GetView().maGridBig.Width(); + tools::Long nx2=GetView().maGridFin.Width(); + tools::Long ny1=GetView().maGridBig.Height(); + tools::Long ny2=GetView().maGridFin.Height(); + + if (nx1==0) nx1=nx2; + if (nx2==0) nx2=nx1; + if (ny1==0) ny1=ny2; + if (ny2==0) ny2=ny1; + if (nx1==0) { nx1=ny1; nx2=ny2; } + if (ny1==0) { ny1=nx1; ny2=nx2; } + if (nx1<0) nx1=-nx1; + if (nx2<0) nx2=-nx2; + if (ny1<0) ny1=-ny1; + if (ny2<0) ny2=-ny2; + + if (nx1==0) + return; + + // no more global output size, use window size instead to decide grid sizes + tools::Long nScreenWdt = rOut.GetOutputSizePixel().Width(); + + tools::Long nMinDotPix=2; + tools::Long nMinLinPix=4; + + if (nScreenWdt>=1600) + { + nMinDotPix=4; + nMinLinPix=8; + } + else if (nScreenWdt>=1024) + { + nMinDotPix=3; + nMinLinPix=6; + } + else + { // e. g. 640x480 + nMinDotPix=2; + nMinLinPix=4; + } + Size aMinDotDist(rOut.PixelToLogic(Size(nMinDotPix,nMinDotPix))); + Size aMinLinDist(rOut.PixelToLogic(Size(nMinLinPix,nMinLinPix))); + bool bHoriSolid=nx2<aMinDotDist.Width(); + bool bVertSolid=ny2<aMinDotDist.Height(); + // enlarge line offset (minimum 4 pixels) + // enlarge by: *2 *5 *10 *20 *50 *100 ... + int nTgl=0; + tools::Long nVal0=nx1; + while (nx1<aMinLinDist.Width()) + { + tools::Long a=nx1; + + if (nTgl==0) nx1*=2; + if (nTgl==1) nx1=nVal0*5; // => nx1*=2.5 + if (nTgl==2) nx1*=2; + + nVal0=a; + nTgl++; if (nTgl>=3) nTgl=0; + } + nTgl=0; + nVal0=ny1; + while (ny1<aMinLinDist.Height()) + { + tools::Long a=ny1; + + if (nTgl==0) ny1*=2; + if (nTgl==1) ny1=nVal0*5; // => ny1*=2.5 + if (nTgl==2) ny1*=2; + + nVal0=a; + nTgl++; + + if (nTgl>=3) nTgl=0; + } + + bool bHoriFine=nx2<nx1; + bool bVertFine=ny2<ny1; + bool bHoriLines=bHoriSolid || bHoriFine || !bVertFine; + bool bVertLines=bVertSolid || bVertFine; + + Color aOriginalLineColor( rOut.GetLineColor() ); + rOut.SetLineColor( aColor ); + + bool bMap0=rOut.IsMapModeEnabled(); + + tools::Long nWrX=0; + tools::Long nWrY=0; + Point aOrg(maPageOrigin); + tools::Long x1 = 0; + tools::Long x2 = 0; + if (GetPage()->GetWidth() < 0) // ScDrawPage of RTL sheet + { + x1 = GetPage()->GetWidth() + GetPage()->GetLeftBorder() + 1; + x2 = - GetPage()->GetRightBorder() - 1; + } + else + { + x1 = GetPage()->GetLeftBorder() + 1; + x2 = GetPage()->GetWidth() - GetPage()->GetRightBorder() - 1; + } + tools::Long y1 = GetPage()->GetUpperBorder() + 1; + tools::Long y2 = GetPage()->GetHeight() - GetPage()->GetLowerBorder() - 1; + const SdrPageGridFrameList* pFrames=GetPage()->GetGridFrameList(this,nullptr); + + sal_uInt16 nGridPaintCnt=1; + if (pFrames!=nullptr) nGridPaintCnt=pFrames->GetCount(); + for (sal_uInt16 nGridPaintNum=0; nGridPaintNum<nGridPaintCnt; nGridPaintNum++) { + if (pFrames!=nullptr) { + const SdrPageGridFrame& rGF=(*pFrames)[nGridPaintNum]; + nWrX=rGF.GetPaperRect().Left(); + nWrY=rGF.GetPaperRect().Top(); + x1=rGF.GetUserArea().Left(); + x2=rGF.GetUserArea().Right(); + y1=rGF.GetUserArea().Top(); + y2=rGF.GetUserArea().Bottom(); + aOrg=rGF.GetUserArea().TopLeft(); + aOrg-=rGF.GetPaperRect().TopLeft(); + } + if (!rRect.IsEmpty()) { + Size a1PixSiz(rOut.PixelToLogic(Size(1,1))); + tools::Long nX1Pix=a1PixSiz.Width(); // add 1 pixel of tolerance + tools::Long nY1Pix=a1PixSiz.Height(); + if (x1<rRect.Left() -nX1Pix) x1=rRect.Left() -nX1Pix; + if (x2>rRect.Right() +nX1Pix) x2=rRect.Right() +nX1Pix; + if (y1<rRect.Top() -nY1Pix) y1=rRect.Top() -nY1Pix; + if (y2>rRect.Bottom()+nY1Pix) y2=rRect.Bottom()+nY1Pix; + } + + tools::Long xBigOrg=aOrg.X()+nWrX; + while (xBigOrg>=x1) xBigOrg-=nx1; + while (xBigOrg<x1) xBigOrg+=nx1; + tools::Long xFinOrg=xBigOrg; + while (xFinOrg>=x1) xFinOrg-=nx2; + while (xFinOrg<x1) xFinOrg+=nx2; + + tools::Long yBigOrg=aOrg.Y()+nWrY; + while (yBigOrg>=y1) yBigOrg-=ny1; + while (yBigOrg<y1) yBigOrg+=ny1; + tools::Long yFinOrg=yBigOrg; + while (yFinOrg>=y1) yFinOrg-=ny2; + while (yFinOrg<y1) yFinOrg+=ny2; + + if( x1 <= x2 && y1 <= y2 ) + { + if( bHoriLines ) + { + DrawGridFlags nGridFlags = ( bHoriSolid ? DrawGridFlags::HorzLines : DrawGridFlags::Dots ); + sal_uInt16 nSteps = sal_uInt16(nx1 / nx2); + sal_uInt32 nRestPerStepMul1000 = nSteps ? ( ((nx1 * 1000)/ nSteps) - (nx2 * 1000) ) : 0; + sal_uInt32 nStepOffset = 0; + sal_uInt16 nPointOffset = 0; + + for(sal_uInt16 a=0;a<nSteps;a++) + { + // draw + rOut.DrawGrid( + tools::Rectangle( xFinOrg + (a * nx2) + nPointOffset, yBigOrg, x2, y2 ), + Size( nx1, ny1 ), nGridFlags ); + + // do a step + nStepOffset += nRestPerStepMul1000; + while(nStepOffset >= 1000) + { + nStepOffset -= 1000; + nPointOffset++; + } + } + } + + if( bVertLines ) + { + DrawGridFlags nGridFlags = ( bVertSolid ? DrawGridFlags::VertLines : DrawGridFlags::Dots ); + sal_uInt16 nSteps = sal_uInt16(ny1 / ny2); + sal_uInt32 nRestPerStepMul1000 = nSteps ? ( ((ny1 * 1000L)/ nSteps) - (ny2 * 1000L) ) : 0; + sal_uInt32 nStepOffset = 0; + sal_uInt16 nPointOffset = 0; + + for(sal_uInt16 a=0;a<nSteps;a++) + { + // draw + rOut.DrawGrid( + tools::Rectangle( xBigOrg, yFinOrg + (a * ny2) + nPointOffset, x2, y2 ), + Size( nx1, ny1 ), nGridFlags ); + + // do a step + nStepOffset += nRestPerStepMul1000; + while(nStepOffset >= 1000) + { + nStepOffset -= 1000; + nPointOffset++; + } + } + } + } + } + + rOut.EnableMapMode(bMap0); + rOut.SetLineColor(aOriginalLineColor); +} + +void SdrPageView::AdjHdl() +{ + GetView().AdjustMarkHdl(); +} + +void SdrPageView::SetLayer(const OUString& rName, SdrLayerIDSet& rBS, bool bJa) +{ + if(!GetPage()) + return; + + SdrLayerID nID = GetPage()->GetLayerAdmin().GetLayerID(rName); + + if(SDRLAYER_NOTFOUND != nID) + rBS.Set(nID, bJa); +} + +bool SdrPageView::IsLayer(const OUString& rName, const SdrLayerIDSet& rBS) const +{ + if(!GetPage()) + return false; + + bool bRet(false); + + if (!rName.isEmpty()) + { + SdrLayerID nId = GetPage()->GetLayerAdmin().GetLayerID(rName); + + if(SDRLAYER_NOTFOUND != nId) + { + bRet = rBS.IsSet(nId); + } + } + + return bRet; +} + +bool SdrPageView::IsObjMarkable(SdrObject const * pObj) const +{ + if (!pObj) + return false; + if (pObj->IsMarkProtect()) + return false; // excluded from selection? + if (!pObj->IsVisible()) + return false; // only visible are selectable + if (!pObj->IsInserted()) + return false; // Obj deleted? + if (auto pObjGroup = dynamic_cast<const SdrObjGroup*>(pObj)) + { + // If object is a Group object, visibility may depend on + // multiple layers. If one object is markable, Group is markable. + SdrObjList* pObjList = pObjGroup->GetSubList(); + + if (pObjList && pObjList->GetObjCount()) + { + for (const rtl::Reference<SdrObject>& pCandidate : *pObjList) + { + // call recursively + if (IsObjMarkable(pCandidate.get())) + return true; + } + return false; + } + else + { + // #i43302# + // Allow empty groups to be selected to be able to delete them + return true; + } + } + if (!pObj->Is3DObj() && pObj->getSdrPageFromSdrObject() != GetPage()) + { + // Obj suddenly in different Page + return false; + } + + // the layer has to be visible and must not be locked + SdrLayerID nL = pObj->GetLayer(); + if (!aLayerVisi.IsSet(nL)) + return false; + if (aLayerLock.IsSet(nL)) + return false; + return true; +} + +void SdrPageView::SetPageOrigin(const Point& rOrg) +{ + if (rOrg != maPageOrigin) + { + maPageOrigin = rOrg; + if (GetView().IsGridVisible()) + { + InvalidateAllWin(); + } + } +} + +void SdrPageView::ImpInvalidateHelpLineArea(sal_uInt16 nNum) const +{ + if (!(GetView().IsHlplVisible() && nNum<aHelpLines.GetCount())) return; + + const SdrHelpLine& rHL=aHelpLines[nNum]; + + for(sal_uInt32 a(0); a < GetView().PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = GetView().GetPaintWindow(a); + + if(pCandidate->OutputToWindow()) + { + OutputDevice& rOutDev = pCandidate->GetOutputDevice(); + tools::Rectangle aR(rHL.GetBoundRect(rOutDev)); + Size aSiz(rOutDev.PixelToLogic(Size(1,1))); + aR.AdjustLeft( -(aSiz.Width()) ); + aR.AdjustRight(aSiz.Width() ); + aR.AdjustTop( -(aSiz.Height()) ); + aR.AdjustBottom(aSiz.Height() ); + const_cast<SdrView&>(GetView()).InvalidateOneWin(rOutDev, aR); + } + } +} + +void SdrPageView::SetHelpLines(const SdrHelpLineList& rHLL) +{ + aHelpLines=rHLL; + InvalidateAllWin(); +} + +void SdrPageView::SetHelpLine(sal_uInt16 nNum, const SdrHelpLine& rNewHelpLine) +{ + if (nNum >= aHelpLines.GetCount() || aHelpLines[nNum] == rNewHelpLine) + return; + + bool bNeedRedraw = true; + if (aHelpLines[nNum].GetKind()==rNewHelpLine.GetKind()) { + switch (rNewHelpLine.GetKind()) { + case SdrHelpLineKind::Vertical : if (aHelpLines[nNum].GetPos().X()==rNewHelpLine.GetPos().X()) bNeedRedraw = false; break; + case SdrHelpLineKind::Horizontal: if (aHelpLines[nNum].GetPos().Y()==rNewHelpLine.GetPos().Y()) bNeedRedraw = false; break; + default: break; + } // switch + } + if (bNeedRedraw) ImpInvalidateHelpLineArea(nNum); + aHelpLines[nNum]=rNewHelpLine; + if (bNeedRedraw) ImpInvalidateHelpLineArea(nNum); +} + +void SdrPageView::DeleteHelpLine(sal_uInt16 nNum) +{ + if (nNum<aHelpLines.GetCount()) { + ImpInvalidateHelpLineArea(nNum); + aHelpLines.Delete(nNum); + } +} + +void SdrPageView::InsertHelpLine(const SdrHelpLine& rHL) +{ + sal_uInt16 nNum = aHelpLines.GetCount(); + aHelpLines.Insert(rHL,nNum); + if (GetView().IsHlplVisible()) + ImpInvalidateHelpLineArea(nNum); +} + +// set current group and list +void SdrPageView::SetCurrentGroupAndList(SdrObject* pNewGroup, SdrObjList* pNewList) +{ + if(pCurrentGroup != pNewGroup) + { + pCurrentGroup = pNewGroup; + } + if(pCurrentList != pNewList) + { + pCurrentList = pNewList; + } +} + +bool SdrPageView::EnterGroup(SdrObject* pObj) +{ + if(!pObj || !pObj->IsGroupObject()) + return false; + + // Don't allow enter Diagrams + if(nullptr != pObj && pObj->isDiagram()) + return false; + + const bool bGlueInvalidate(GetView().ImpIsGlueVisible()); + + if (bGlueInvalidate) + { + GetView().GlueInvalidate(); + } + + // deselect all + GetView().UnmarkAll(); + + // set current group and list + SdrObjList* pNewObjList = pObj->GetSubList(); + SetCurrentGroupAndList(pObj, pNewObjList); + + // select contained object if only one object is contained, + // else select nothing and let the user decide what to do next + if(pNewObjList && pNewObjList->GetObjCount() == 1) + { + SdrObject* pFirstObject = pNewObjList->GetObj(0); + + if(GetView().GetSdrPageView()) + { + GetView().MarkObj(pFirstObject, GetView().GetSdrPageView()); + } + } + + // build new handles + GetView().AdjustMarkHdl(); + + // invalidate only when view wants to visualize group entering + InvalidateAllWin(); + + if (bGlueInvalidate) + { + GetView().GlueInvalidate(); + } + + return true; +} + +void SdrPageView::LeaveOneGroup() +{ + SdrObject* pLastGroup = GetCurrentGroup(); + if (!pLastGroup) + return; + + bool bGlueInvalidate = GetView().ImpIsGlueVisible(); + + if(bGlueInvalidate) + GetView().GlueInvalidate(); + + SdrObject* pParentGroup = pLastGroup->getParentSdrObjectFromSdrObject(); + SdrObjList* pParentList = GetPage(); + + if(pParentGroup) + pParentList = pParentGroup->GetSubList(); + + // deselect everything + GetView().UnmarkAll(); + + // allocations, pCurrentGroup and pCurrentList need to be set + SetCurrentGroupAndList(pParentGroup, pParentList); + + // select the group we just left + if (GetView().GetSdrPageView()) + GetView().MarkObj(pLastGroup, GetView().GetSdrPageView()); + + GetView().AdjustMarkHdl(); + + // invalidate only if view wants to visualize group entering + InvalidateAllWin(); + + if(bGlueInvalidate) + GetView().GlueInvalidate(); +} + +void SdrPageView::LeaveAllGroup() +{ + SdrObject* pLastGroup = GetCurrentGroup(); + if (!pLastGroup) + return; + + bool bGlueInvalidate = GetView().ImpIsGlueVisible(); + + if(bGlueInvalidate) + GetView().GlueInvalidate(); + + // deselect everything + GetView().UnmarkAll(); + + // allocations, pCurrentGroup and pCurrentList always need to be set + SetCurrentGroupAndList(nullptr, GetPage()); + + // find and select uppermost group + while (pLastGroup->getParentSdrObjectFromSdrObject()) + pLastGroup = pLastGroup->getParentSdrObjectFromSdrObject(); + + if (GetView().GetSdrPageView()) + GetView().MarkObj(pLastGroup, GetView().GetSdrPageView()); + + GetView().AdjustMarkHdl(); + + // invalidate only when view wants to visualize group entering + InvalidateAllWin(); + + if(bGlueInvalidate) + GetView().GlueInvalidate(); +} + +sal_uInt16 SdrPageView::GetEnteredLevel() const +{ + sal_uInt16 nCount=0; + SdrObject* pGrp=GetCurrentGroup(); + while (pGrp!=nullptr) { + nCount++; + pGrp=pGrp->getParentSdrObjectFromSdrObject(); + } + return nCount; +} + +void SdrPageView::CheckCurrentGroup() +{ + SdrObject* pGrp(GetCurrentGroup()); + + while(nullptr != pGrp && + (!pGrp->IsInserted() || nullptr == pGrp->getParentSdrObjListFromSdrObject() || nullptr == pGrp->getSdrPageFromSdrObject())) + { + // anything outside of the borders? + pGrp = pGrp->getParentSdrObjectFromSdrObject(); + } + + if(pGrp != GetCurrentGroup()) + { + if(nullptr != pGrp) + { + EnterGroup(pGrp); + } + else + { + LeaveAllGroup(); + } + } +} + +// Set background color for svx at SdrPageViews +void SdrPageView::SetApplicationBackgroundColor(Color aBackgroundColor) +{ + maBackgroundColor = aBackgroundColor; +} + + +// Set document color for svx at SdrPageViews +void SdrPageView::SetApplicationDocumentColor(Color aDocumentColor) +{ + maDocumentColor = aDocumentColor; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdpdf.cxx b/svx/source/svdraw/svdpdf.cxx new file mode 100644 index 0000000000..7509991ba3 --- /dev/null +++ b/svx/source/svdraw/svdpdf.cxx @@ -0,0 +1,1046 @@ +/* -*- 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 <svdpdf.hxx> + +#include <tools/UnitConversion.hxx> +#include <vcl/graph.hxx> +#include <vcl/vectorgraphicdata.hxx> + +#include <math.h> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlncapit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/xflclit.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <vcl/metric.hxx> +#include <editeng/charscaleitem.hxx> +#include <svx/sdtditm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtfsitm.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdetc.hxx> +#include <svl/itemset.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <tools/helpers.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/xlinjoit.hxx> +#include <svx/xlndsit.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/xflbstit.hxx> +#include <svx/xlineit0.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <svx/svditer.hxx> +#include <svx/svdogrp.hxx> +#include <vcl/dibtools.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +ImpSdrPdfImport::ImpSdrPdfImport(SdrModel& rModel, SdrLayerID nLay, const tools::Rectangle& rRect, + Graphic const& rGraphic) + : mpVD(VclPtr<VirtualDevice>::Create()) + , maScaleRect(rRect) + , mnMapScalingOfs(0) + , mpModel(&rModel) + , mnLayer(nLay) + , mnLineWidth(0) + , maDash(css::drawing::DashStyle_RECT, 0, 0, 0, 0, 0) + , mbMov(false) + , mbSize(false) + , maOfs(0, 0) + , mfScaleX(1.0) + , mfScaleY(1.0) + , maScaleX(1.0) + , maScaleY(1.0) + , mbFntDirty(true) + , mbLastObjWasPolyWithoutLine(false) + , mbNoLine(false) + , mbNoFill(false) + , mnPageCount(0) + , mdPageHeightPts(0) + , mpPDFium(vcl::pdf::PDFiumLibrary::get()) +{ + mpVD->EnableOutput(false); + mpVD->SetLineColor(); + mpVD->SetFillColor(); + maOldLineColor.SetRed(mpVD->GetLineColor().GetRed() + 1); + mpLineAttr = std::make_unique<SfxItemSetFixed<XATTR_LINE_FIRST, XATTR_LINE_LAST>>( + rModel.GetItemPool()); + mpFillAttr = std::make_unique<SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST>>( + rModel.GetItemPool()); + mpTextAttr + = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(rModel.GetItemPool()); + + checkClip(); + + // Load the buffer using pdfium. + auto const& rVectorGraphicData = rGraphic.getVectorGraphicData(); + auto* pData = rVectorGraphicData->getBinaryDataContainer().getData(); + sal_Int32 nSize = rVectorGraphicData->getBinaryDataContainer().getSize(); + mpPdfDocument = mpPDFium ? mpPDFium->openDocument(pData, nSize, OString()) : nullptr; + if (!mpPdfDocument) + return; + + mnPageCount = mpPdfDocument->getPageCount(); +} + +ImpSdrPdfImport::~ImpSdrPdfImport() = default; + +void ImpSdrPdfImport::DoObjects(SvdProgressInfo* pProgrInfo, sal_uInt32* pActionsToReport, + int nPageIndex) +{ + const int nPageCount = mpPdfDocument->getPageCount(); + if (!(nPageCount > 0 && nPageIndex >= 0 && nPageIndex < nPageCount)) + return; + + // Render next page. + auto pPdfPage = mpPdfDocument->openPage(nPageIndex); + if (!pPdfPage) + return; + + basegfx::B2DSize dPageSize = mpPdfDocument->getPageSize(nPageIndex); + + SetupPageScale(dPageSize.getWidth(), dPageSize.getHeight()); + + // Load the page text to extract it when we get text elements. + auto pTextPage = pPdfPage->getTextPage(); + + const int nPageObjectCount = pPdfPage->getObjectCount(); + if (pProgrInfo) + pProgrInfo->SetActionCount(nPageObjectCount); + + for (int nPageObjectIndex = 0; nPageObjectIndex < nPageObjectCount; ++nPageObjectIndex) + { + auto pPageObject = pPdfPage->getObject(nPageObjectIndex); + ImportPdfObject(pPageObject, pTextPage, nPageObjectIndex); + if (pProgrInfo && pActionsToReport) + { + (*pActionsToReport)++; + + if (*pActionsToReport >= 16) + { + if (!pProgrInfo->ReportActions(*pActionsToReport)) + break; + + *pActionsToReport = 0; + } + } + } +} + +void ImpSdrPdfImport::SetupPageScale(const double dPageWidth, const double dPageHeight) +{ + mfScaleX = mfScaleY = 1.0; + + // Store the page dimensions in Points. + mdPageHeightPts = dPageHeight; + + Size aPageSize(convertPointToMm100(dPageWidth), convertPointToMm100(dPageHeight)); + + if (aPageSize.Width() && aPageSize.Height() && (!maScaleRect.IsEmpty())) + { + maOfs = maScaleRect.TopLeft(); + + if (aPageSize.Width() != (maScaleRect.GetWidth() - 1)) + { + mfScaleX = static_cast<double>(maScaleRect.GetWidth() - 1) + / static_cast<double>(aPageSize.Width()); + } + + if (aPageSize.Height() != (maScaleRect.GetHeight() - 1)) + { + mfScaleY = static_cast<double>(maScaleRect.GetHeight() - 1) + / static_cast<double>(aPageSize.Height()); + } + } + + mbMov = maOfs.X() != 0 || maOfs.Y() != 0; + mbSize = false; + maScaleX = Fraction(1, 1); + maScaleY = Fraction(1, 1); + + if (aPageSize.Width() != (maScaleRect.GetWidth() - 1)) + { + maScaleX = Fraction(maScaleRect.GetWidth() - 1, aPageSize.Width()); + mbSize = true; + } + + if (aPageSize.Height() != (maScaleRect.GetHeight() - 1)) + { + maScaleY = Fraction(maScaleRect.GetHeight() - 1, aPageSize.Height()); + mbSize = true; + } +} + +size_t ImpSdrPdfImport::DoImport(SdrObjList& rOL, size_t nInsPos, int nPageNumber, + SvdProgressInfo* pProgrInfo) +{ + sal_uInt32 nActionsToReport(0); + + // execute + DoObjects(pProgrInfo, &nActionsToReport, nPageNumber); + + if (pProgrInfo) + { + pProgrInfo->ReportActions(nActionsToReport); + nActionsToReport = 0; + } + + // MapMode scaling + MapScaling(); + + // To calculate the progress meter, we use GetActionSize()*3. + // However, maTmpList has a lower entry count limit than GetActionSize(), + // so the actions that were assumed were too much have to be re-added. + // nActionsToReport = (rMtf.GetActionSize() - maTmpList.size()) * 2; + + // announce all currently unannounced rescales + if (pProgrInfo) + { + pProgrInfo->ReportRescales(nActionsToReport); + pProgrInfo->SetInsertCount(maTmpList.size()); + } + + nActionsToReport = 0; + + // insert all objects cached in aTmpList now into rOL from nInsPos + nInsPos = std::min(nInsPos, rOL.GetObjCount()); + + for (rtl::Reference<SdrObject>& pObj : maTmpList) + { + rOL.NbcInsertObject(pObj.get(), nInsPos); + nInsPos++; + + if (pProgrInfo) + { + nActionsToReport++; + + if (nActionsToReport >= 32) // update all 32 actions + { + pProgrInfo->ReportInserts(nActionsToReport); + nActionsToReport = 0; + } + } + } + + // report all remaining inserts for the last time + if (pProgrInfo) + { + pProgrInfo->ReportInserts(nActionsToReport); + } + + return maTmpList.size(); +} + +void ImpSdrPdfImport::SetAttributes(SdrObject* pObj, bool bForceTextAttr) +{ + mbNoLine = false; + mbNoFill = false; + bool bLine(!bForceTextAttr); + bool bFill(!pObj || (pObj->IsClosedObj() && !bForceTextAttr)); + bool bText(bForceTextAttr || (pObj && pObj->GetOutlinerParaObject())); + + if (bLine) + { + if (mnLineWidth) + { + mpLineAttr->Put(XLineWidthItem(mnLineWidth)); + } + else + { + mpLineAttr->Put(XLineWidthItem(0)); + } + + maOldLineColor = mpVD->GetLineColor(); + + if (mpVD->IsLineColor()) + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_SOLID)); //TODO support dashed lines. + mpLineAttr->Put(XLineColorItem(OUString(), mpVD->GetLineColor())); + } + else + { + mpLineAttr->Put(XLineStyleItem(drawing::LineStyle_NONE)); + } + + mpLineAttr->Put(XLineJointItem(css::drawing::LineJoint_NONE)); + + // Add LineCap support + mpLineAttr->Put(XLineCapItem(gaLineCap)); + + if (((maDash.GetDots() && maDash.GetDotLen()) + || (maDash.GetDashes() && maDash.GetDashLen())) + && maDash.GetDistance()) + { + mpLineAttr->Put(XLineDashItem(OUString(), maDash)); + } + else + { + mpLineAttr->Put(XLineDashItem(OUString(), XDash(css::drawing::DashStyle_RECT))); + } + } + else + { + mbNoLine = true; + } + + if (bFill) + { + if (mpVD->IsFillColor()) + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_SOLID)); + mpFillAttr->Put(XFillColorItem(OUString(), mpVD->GetFillColor())); + } + else + { + mpFillAttr->Put(XFillStyleItem(drawing::FillStyle_NONE)); + } + } + else + { + mbNoFill = true; + } + + if (bText && mbFntDirty) + { + vcl::Font aFnt(mpVD->GetFont()); + const sal_uInt32 nHeight(FRound(aFnt.GetFontSize().Height() * mfScaleY)); + + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO)); + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CJK)); + mpTextAttr->Put(SvxFontItem(aFnt.GetFamilyType(), aFnt.GetFamilyName(), aFnt.GetStyleName(), + aFnt.GetPitch(), aFnt.GetCharSet(), EE_CHAR_FONTINFO_CTL)); + mpTextAttr->Put(SvxPostureItem(aFnt.GetItalic(), EE_CHAR_ITALIC)); + mpTextAttr->Put(SvxWeightItem(aFnt.GetWeight(), EE_CHAR_WEIGHT)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CJK)); + mpTextAttr->Put(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CTL)); + mpTextAttr->Put(SvxCharScaleWidthItem(100, EE_CHAR_FONTWIDTH)); + mpTextAttr->Put(SvxUnderlineItem(aFnt.GetUnderline(), EE_CHAR_UNDERLINE)); + mpTextAttr->Put(SvxOverlineItem(aFnt.GetOverline(), EE_CHAR_OVERLINE)); + mpTextAttr->Put(SvxCrossedOutItem(aFnt.GetStrikeout(), EE_CHAR_STRIKEOUT)); + mpTextAttr->Put(SvxShadowedItem(aFnt.IsShadow(), EE_CHAR_SHADOW)); + + // #i118485# Setting this item leads to problems (written #i118498# for this) + // mpTextAttr->Put(SvxAutoKernItem(aFnt.IsKerning(), EE_CHAR_KERNING)); + + mpTextAttr->Put(SvxWordLineModeItem(aFnt.IsWordLineMode(), EE_CHAR_WLM)); + mpTextAttr->Put(SvxContourItem(aFnt.IsOutline(), EE_CHAR_OUTLINE)); + mpTextAttr->Put(SvxColorItem(mpVD->GetTextColor(), EE_CHAR_COLOR)); + //... svxfont textitem svditext + mbFntDirty = false; + } + + if (!pObj) + return; + + pObj->SetLayer(mnLayer); + + if (bLine) + { + pObj->SetMergedItemSet(*mpLineAttr); + } + + if (bFill) + { + pObj->SetMergedItemSet(*mpFillAttr); + } + + if (bText) + { + pObj->SetMergedItemSet(*mpTextAttr); + pObj->SetMergedItem(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); + } +} + +void ImpSdrPdfImport::InsertObj(SdrObject* pObj1, bool bScale) +{ + rtl::Reference<SdrObject> pObj = pObj1; + if (bScale && !maScaleRect.IsEmpty()) + { + if (mbSize) + { + pObj->NbcResize(Point(), maScaleX, maScaleY); + } + + if (mbMov) + { + pObj->NbcMove(Size(maOfs.X(), maOfs.Y())); + } + } + + if (isClip()) + { + const basegfx::B2DPolyPolygon aPoly(pObj->TakeXorPoly()); + const basegfx::B2DRange aOldRange(aPoly.getB2DRange()); + const SdrLayerID aOldLayer(pObj->GetLayer()); + const SfxItemSet aOldItemSet(pObj->GetMergedItemSet()); + const SdrGrafObj* pSdrGrafObj = dynamic_cast<SdrGrafObj*>(pObj.get()); + const SdrTextObj* pSdrTextObj = DynCastSdrTextObj(pObj.get()); + + if (pSdrTextObj && pSdrTextObj->HasText()) + { + // all text objects are created from ImportText and have no line or fill attributes, so + // it is okay to concentrate on the text itself + while (true) + { + const basegfx::B2DPolyPolygon aTextContour(pSdrTextObj->TakeContour()); + const basegfx::B2DRange aTextRange(aTextContour.getB2DRange()); + const basegfx::B2DRange aClipRange(maClip.getB2DRange()); + + // no overlap -> completely outside + if (!aClipRange.overlaps(aTextRange)) + { + pObj.clear(); + break; + } + + // when the clip is a rectangle fast check for inside is possible + if (basegfx::utils::isRectangle(maClip) && aClipRange.isInside(aTextRange)) + { + // completely inside ClipRect + break; + } + + // here text needs to be clipped; to do so, convert to SdrObjects with polygons + // and add these recursively. Delete original object, do not add in this run + rtl::Reference<SdrObject> pConverted = pSdrTextObj->ConvertToPolyObj(true, true); + pObj.clear(); + if (pConverted) + { + // recursively add created conversion; per definition this shall not + // contain further SdrTextObjs. Visit only non-group objects + SdrObjListIter aIter(*pConverted, SdrIterMode::DeepNoGroups); + + // work with clones; the created conversion may contain group objects + // and when working with the original objects the loop itself could + // break and the cleanup later would be pretty complicated (only delete group + // objects, are these empty, ...?) + while (aIter.IsMore()) + { + SdrObject* pCandidate = aIter.Next(); + OSL_ENSURE(pCandidate && dynamic_cast<SdrObjGroup*>(pCandidate) == nullptr, + "SdrObjListIter with SdrIterMode::DeepNoGroups error (!)"); + rtl::Reference<SdrObject> pNewClone( + pCandidate->CloneSdrObject(pCandidate->getSdrModelFromSdrObject())); + + if (pNewClone) + { + InsertObj(pNewClone.get(), false); + } + else + { + OSL_ENSURE(false, "SdrObject::Clone() failed (!)"); + } + } + } + + break; + } + } + else + { + BitmapEx aBitmapEx; + + if (pSdrGrafObj) + { + aBitmapEx = pSdrGrafObj->GetGraphic().GetBitmapEx(); + } + + pObj.clear(); + + if (!aOldRange.isEmpty()) + { + // clip against ClipRegion + const basegfx::B2DPolyPolygon aNewPoly(basegfx::utils::clipPolyPolygonOnPolyPolygon( + aPoly, maClip, true, !aPoly.isClosed())); + const basegfx::B2DRange aNewRange(aNewPoly.getB2DRange()); + + if (!aNewRange.isEmpty()) + { + pObj = new SdrPathObj( + *mpModel, aNewPoly.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine, + aNewPoly); + + pObj->SetLayer(aOldLayer); + pObj->SetMergedItemSet(aOldItemSet); + + if (!aBitmapEx.IsEmpty()) + { + // aNewRange is inside of aOldRange and defines which part of aBitmapEx is used + const double fScaleX(aBitmapEx.GetSizePixel().Width() + / (aOldRange.getWidth() ? aOldRange.getWidth() : 1.0)); + const double fScaleY( + aBitmapEx.GetSizePixel().Height() + / (aOldRange.getHeight() ? aOldRange.getHeight() : 1.0)); + basegfx::B2DRange aPixel(aNewRange); + basegfx::B2DHomMatrix aTrans; + + aTrans.translate(-aOldRange.getMinX(), -aOldRange.getMinY()); + aTrans.scale(fScaleX, fScaleY); + aPixel.transform(aTrans); + + const Size aOrigSizePixel(aBitmapEx.GetSizePixel()); + const Point aClipTopLeft( + basegfx::fround(floor(std::max(0.0, aPixel.getMinX()))), + basegfx::fround(floor(std::max(0.0, aPixel.getMinY())))); + const Size aClipSize( + basegfx::fround(ceil(std::min( + static_cast<double>(aOrigSizePixel.Width()), aPixel.getWidth()))), + basegfx::fround( + ceil(std::min(static_cast<double>(aOrigSizePixel.Height()), + aPixel.getHeight())))); + const BitmapEx aClippedBitmap(aBitmapEx, aClipTopLeft, aClipSize); + + pObj->SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + pObj->SetMergedItem(XFillBitmapItem(OUString(), Graphic(aClippedBitmap))); + pObj->SetMergedItem(XFillBmpTileItem(false)); + pObj->SetMergedItem(XFillBmpStretchItem(true)); + } + } + } + } + } + + if (!pObj) + return; + + // #i111954# check object for visibility + // used are SdrPathObj, SdrRectObj, SdrCircObj, SdrGrafObj + bool bVisible(false); + + if (pObj->HasLineStyle()) + { + bVisible = true; + } + + if (!bVisible && pObj->HasFillStyle()) + { + bVisible = true; + } + + if (!bVisible) + { + SdrTextObj* pTextObj = DynCastSdrTextObj(pObj.get()); + + if (pTextObj && pTextObj->HasText()) + { + bVisible = true; + } + } + + if (!bVisible) + { + SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(pObj.get()); + + if (pGrafObj) + { + // this may be refined to check if the graphic really is visible. It + // is here to ensure that graphic objects without fill, line and text + // get created + bVisible = true; + } + } + + if (bVisible) + { + maTmpList.push_back(pObj); + + if (dynamic_cast<SdrPathObj*>(pObj.get())) + { + const bool bClosed(pObj->IsClosedObj()); + + mbLastObjWasPolyWithoutLine = mbNoLine && bClosed; + } + else + { + mbLastObjWasPolyWithoutLine = false; + } + } +} + +bool ImpSdrPdfImport::CheckLastPolyLineAndFillMerge(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // #i73407# reformulation to use new B2DPolygon classes + if (mbLastObjWasPolyWithoutLine) + { + SdrObject* pTmpObj = !maTmpList.empty() ? maTmpList[maTmpList.size() - 1].get() : nullptr; + SdrPathObj* pLastPoly = dynamic_cast<SdrPathObj*>(pTmpObj); + + if (pLastPoly) + { + if (pLastPoly->GetPathPoly() == rPolyPolygon) + { + SetAttributes(nullptr); + + if (!mbNoLine && mbNoFill) + { + pLastPoly->SetMergedItemSet(*mpLineAttr); + + return true; + } + } + } + } + + return false; +} + +void ImpSdrPdfImport::checkClip() +{ + if (mpVD->IsClipRegion()) + { + maClip = mpVD->GetClipRegion().GetAsB2DPolyPolygon(); + + if (isClip()) + { + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + + maClip.transform(aTransform); + } + } +} + +bool ImpSdrPdfImport::isClip() const { return !maClip.getB2DRange().isEmpty(); } +void ImpSdrPdfImport::ImportPdfObject( + std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, int nPageObjectIndex) +{ + if (!pPageObject) + return; + + const vcl::pdf::PDFPageObjectType ePageObjectType = pPageObject->getType(); + switch (ePageObjectType) + { + case vcl::pdf::PDFPageObjectType::Text: + ImportText(pPageObject, pTextPage, nPageObjectIndex); + break; + case vcl::pdf::PDFPageObjectType::Path: + ImportPath(pPageObject, nPageObjectIndex); + break; + case vcl::pdf::PDFPageObjectType::Image: + ImportImage(pPageObject, nPageObjectIndex); + break; + case vcl::pdf::PDFPageObjectType::Shading: + SAL_WARN("sd.filter", "Got page object SHADING: " << nPageObjectIndex); + break; + case vcl::pdf::PDFPageObjectType::Form: + ImportForm(pPageObject, pTextPage, nPageObjectIndex); + break; + default: + SAL_WARN("sd.filter", "Unknown PDF page object #" << nPageObjectIndex << " of type: " + << static_cast<int>(ePageObjectType)); + break; + } +} + +void ImpSdrPdfImport::ImportForm(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, + int /*nPageObjectIndex*/) +{ + // Get the form matrix to perform correct translation/scaling of the form sub-objects. + const basegfx::B2DHomMatrix aOldMatrix = maCurrentMatrix; + + maCurrentMatrix = pPageObject->getMatrix(); + + const int nCount = pPageObject->getFormObjectCount(); + for (int nIndex = 0; nIndex < nCount; ++nIndex) + { + auto pFormObject = pPageObject->getFormObject(nIndex); + + ImportPdfObject(pFormObject, pTextPage, -1); + } + + // Restore the old one. + maCurrentMatrix = aOldMatrix; +} + +void ImpSdrPdfImport::ImportText(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + std::unique_ptr<vcl::pdf::PDFiumTextPage> const& pTextPage, + int /*nPageObjectIndex*/) +{ + basegfx::B2DRectangle aTextRect = pPageObject->getBounds(); + basegfx::B2DHomMatrix aMatrix = pPageObject->getMatrix(); + + basegfx::B2DHomMatrix aTextMatrix(maCurrentMatrix); + + aTextRect *= aTextMatrix; + const tools::Rectangle aRect = PointsToLogic(aTextRect.getMinX(), aTextRect.getMaxX(), + aTextRect.getMinY(), aTextRect.getMaxY()); + + OUString sText = pPageObject->getText(pTextPage); + + const double dFontSize = pPageObject->getFontSize(); + double dFontSizeH = fabs(std::hypot(aMatrix.a(), aMatrix.c()) * dFontSize); + double dFontSizeV = fabs(std::hypot(aMatrix.b(), aMatrix.d()) * dFontSize); + + dFontSizeH = convertPointToMm100(dFontSizeH); + dFontSizeV = convertPointToMm100(dFontSizeV); + + const Size aFontSize(dFontSizeH, dFontSizeV); + vcl::Font aFnt = mpVD->GetFont(); + if (aFontSize != aFnt.GetFontSize()) + { + aFnt.SetFontSize(aFontSize); + mpVD->SetFont(aFnt); + mbFntDirty = true; + } + + OUString sFontName = pPageObject->getFontName(); + if (!sFontName.isEmpty() && sFontName != aFnt.GetFamilyName()) + { + aFnt.SetFamilyName(sFontName); + mpVD->SetFont(aFnt); + mbFntDirty = true; + } + + Color aTextColor(COL_TRANSPARENT); + bool bFill = false; + bool bUse = true; + switch (pPageObject->getTextRenderMode()) + { + case vcl::pdf::PDFTextRenderMode::Fill: + case vcl::pdf::PDFTextRenderMode::FillClip: + case vcl::pdf::PDFTextRenderMode::FillStroke: + case vcl::pdf::PDFTextRenderMode::FillStrokeClip: + bFill = true; + break; + case vcl::pdf::PDFTextRenderMode::Stroke: + case vcl::pdf::PDFTextRenderMode::StrokeClip: + case vcl::pdf::PDFTextRenderMode::Unknown: + break; + case vcl::pdf::PDFTextRenderMode::Invisible: + case vcl::pdf::PDFTextRenderMode::Clip: + bUse = false; + break; + } + if (bUse) + { + Color aColor = bFill ? pPageObject->getFillColor() : pPageObject->getStrokeColor(); + if (aColor != COL_TRANSPARENT) + aTextColor = aColor.GetRGBColor(); + } + + if (aTextColor != mpVD->GetTextColor()) + { + mpVD->SetTextColor(aTextColor); + mbFntDirty = true; + } + + InsertTextObject(aRect.TopLeft(), aRect.GetSize(), sText); +} + +void ImpSdrPdfImport::InsertTextObject(const Point& rPos, const Size& rSize, const OUString& rStr) +{ + // calc text box size, add 5% to make it fit safely + + FontMetric aFontMetric(mpVD->GetFontMetric()); + vcl::Font aFont(mpVD->GetFont()); + TextAlign eAlignment(aFont.GetAlignment()); + + // sal_Int32 nTextWidth = static_cast<sal_Int32>(mpVD->GetTextWidth(rStr) * mfScaleX); + sal_Int32 nTextHeight = static_cast<sal_Int32>(mpVD->GetTextHeight() * mfScaleY); + + Point aPosition(FRound(rPos.X() * mfScaleX + maOfs.X()), + FRound(rPos.Y() * mfScaleY + maOfs.Y())); + Size aSize(FRound(rSize.Width() * mfScaleX), FRound(rSize.Height() * mfScaleY)); + + if (eAlignment == ALIGN_BASELINE) + aPosition.AdjustY(-FRound(aFontMetric.GetAscent() * mfScaleY)); + else if (eAlignment == ALIGN_BOTTOM) + aPosition.AdjustY(-nTextHeight); + + tools::Rectangle aTextRect(aPosition, aSize); + rtl::Reference<SdrRectObj> pText = new SdrRectObj(*mpModel, SdrObjKind::Text, aTextRect); + + pText->SetMergedItem(makeSdrTextUpperDistItem(0)); + pText->SetMergedItem(makeSdrTextLowerDistItem(0)); + pText->SetMergedItem(makeSdrTextRightDistItem(0)); + pText->SetMergedItem(makeSdrTextLeftDistItem(0)); + + if (aFont.GetAverageFontWidth()) + { + pText->ClearMergedItem(SDRATTR_TEXT_AUTOGROWWIDTH); + pText->SetMergedItem(makeSdrTextAutoGrowHeightItem(false)); + // don't let the margins eat the space needed for the text + pText->SetMergedItem(SdrTextFitToSizeTypeItem(drawing::TextFitToSizeType_ALLLINES)); + } + else + { + pText->SetMergedItem(makeSdrTextAutoGrowWidthItem(true)); + } + + pText->SetLayer(mnLayer); + pText->NbcSetText(rStr); + SetAttributes(pText.get(), true); + pText->SetSnapRect(aTextRect); + + if (!aFont.IsTransparent()) + { + SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST> aAttr(*mpFillAttr->GetPool()); + aAttr.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + aAttr.Put(XFillColorItem(OUString(), aFont.GetFillColor())); + pText->SetMergedItemSet(aAttr); + } + Degree100 nAngle = to<Degree100>(aFont.GetOrientation()); + if (nAngle) + pText->SdrAttrObj::NbcRotate(aPosition, nAngle); + InsertObj(pText.get(), false); +} + +void ImpSdrPdfImport::MapScaling() +{ + const size_t nCount(maTmpList.size()); + const MapMode& rMap = mpVD->GetMapMode(); + Point aMapOrg(rMap.GetOrigin()); + bool bMov2(aMapOrg.X() != 0 || aMapOrg.Y() != 0); + + if (bMov2) + { + for (size_t i = mnMapScalingOfs; i < nCount; i++) + { + SdrObject* pObj = maTmpList[i].get(); + + pObj->NbcMove(Size(aMapOrg.X(), aMapOrg.Y())); + } + } + + mnMapScalingOfs = nCount; +} + +void ImpSdrPdfImport::ImportImage(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + int /*nPageObjectIndex*/) +{ + std::unique_ptr<vcl::pdf::PDFiumBitmap> bitmap = pPageObject->getImageBitmap(); + if (!bitmap) + { + SAL_WARN("sd.filter", "Failed to get IMAGE"); + return; + } + + const vcl::pdf::PDFBitmapType format = bitmap->getFormat(); + if (format == vcl::pdf::PDFBitmapType::Unknown) + { + SAL_WARN("sd.filter", "Failed to get IMAGE format"); + return; + } + + const unsigned char* pBuf = bitmap->getBuffer(); + const int nWidth = bitmap->getWidth(); + const int nHeight = bitmap->getHeight(); + const int nStride = bitmap->getStride(); + BitmapEx aBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP); + + switch (format) + { + case vcl::pdf::PDFBitmapType::BGR: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N24BitTcBgr, nHeight, nStride); + break; + case vcl::pdf::PDFBitmapType::BGRx: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N32BitTcRgba, nHeight, nStride); + break; + case vcl::pdf::PDFBitmapType::BGRA: + ReadRawDIB(aBitmap, pBuf, ScanlineFormat::N32BitTcBgra, nHeight, nStride); + break; + default: + SAL_WARN("sd.filter", "Got IMAGE width: " << nWidth << ", height: " << nHeight + << ", stride: " << nStride + << ", format: " << static_cast<int>(format)); + break; + } + + basegfx::B2DRectangle aBounds = pPageObject->getBounds(); + float left = aBounds.getMinX(); + // Upside down. + float bottom = aBounds.getMinY(); + float right = aBounds.getMaxX(); + // Upside down. + float top = aBounds.getMaxY(); + tools::Rectangle aRect = PointsToLogic(left, right, top, bottom); + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + + rtl::Reference<SdrGrafObj> pGraf = new SdrGrafObj(*mpModel, Graphic(aBitmap), aRect); + + // This action is not creating line and fill, set directly, do not use SetAttributes(..) + pGraf->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE)); + pGraf->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE)); + InsertObj(pGraf.get()); +} + +void ImpSdrPdfImport::ImportPath(std::unique_ptr<vcl::pdf::PDFiumPageObject> const& pPageObject, + int /*nPageObjectIndex*/) +{ + auto aPathMatrix = pPageObject->getMatrix(); + + aPathMatrix *= maCurrentMatrix; + + basegfx::B2DPolyPolygon aPolyPoly; + basegfx::B2DPolygon aPoly; + std::vector<basegfx::B2DPoint> aBezier; + + const int nSegments = pPageObject->getPathSegmentCount(); + for (int nSegmentIndex = 0; nSegmentIndex < nSegments; ++nSegmentIndex) + { + auto pPathSegment = pPageObject->getPathSegment(nSegmentIndex); + if (pPathSegment != nullptr) + { + basegfx::B2DPoint aB2DPoint = pPathSegment->getPoint(); + aB2DPoint *= aPathMatrix; + + const bool bClose = pPathSegment->isClosed(); + if (bClose) + aPoly.setClosed(bClose); // TODO: Review + + Point aPoint = PointsToLogic(aB2DPoint.getX(), aB2DPoint.getY()); + aB2DPoint.setX(aPoint.X()); + aB2DPoint.setY(aPoint.Y()); + + const vcl::pdf::PDFSegmentType eSegmentType = pPathSegment->getType(); + switch (eSegmentType) + { + case vcl::pdf::PDFSegmentType::Lineto: + aPoly.append(aB2DPoint); + break; + + case vcl::pdf::PDFSegmentType::Bezierto: + aBezier.emplace_back(aB2DPoint.getX(), aB2DPoint.getY()); + if (aBezier.size() == 3) + { + aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]); + aBezier.clear(); + } + break; + + case vcl::pdf::PDFSegmentType::Moveto: + // New Poly. + if (aPoly.count() > 0) + { + aPolyPoly.append(aPoly, 1); + aPoly.clear(); + } + + aPoly.append(aB2DPoint); + break; + + case vcl::pdf::PDFSegmentType::Unknown: + default: + SAL_WARN("sd.filter", "Unknown path segment type in PDF: " + << static_cast<int>(eSegmentType)); + break; + } + } + } + + if (aBezier.size() == 3) + { + aPoly.appendBezierSegment(aBezier[0], aBezier[1], aBezier[2]); + aBezier.clear(); + } + + if (aPoly.count() > 0) + { + aPolyPoly.append(aPoly, 1); + aPoly.clear(); + } + + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix(mfScaleX, mfScaleY, maOfs.X(), maOfs.Y())); + aPolyPoly.transform(aTransform); + + float fWidth = pPageObject->getStrokeWidth(); + const double dWidth = 0.5 * fabs(std::hypot(aPathMatrix.a(), aPathMatrix.c()) * fWidth); + mnLineWidth = convertPointToMm100(dWidth); + + vcl::pdf::PDFFillMode nFillMode = vcl::pdf::PDFFillMode::Alternate; + bool bStroke = true; // Assume we have to draw, unless told otherwise. + if (pPageObject->getDrawMode(nFillMode, bStroke)) + { + if (nFillMode == vcl::pdf::PDFFillMode::Alternate) + mpVD->SetDrawMode(DrawModeFlags::Default); + else if (nFillMode == vcl::pdf::PDFFillMode::Winding) + mpVD->SetDrawMode(DrawModeFlags::Default); + else + mpVD->SetDrawMode(DrawModeFlags::NoFill); + } + + mpVD->SetFillColor(pPageObject->getFillColor()); + + if (bStroke) + { + mpVD->SetLineColor(pPageObject->getStrokeColor()); + } + else + mpVD->SetLineColor(COL_TRANSPARENT); + + if (!mbLastObjWasPolyWithoutLine || !CheckLastPolyLineAndFillMerge(aPolyPoly)) + { + rtl::Reference<SdrPathObj> pPath + = new SdrPathObj(*mpModel, SdrObjKind::Polygon, std::move(aPolyPoly)); + SetAttributes(pPath.get()); + InsertObj(pPath.get(), false); + } +} + +Point ImpSdrPdfImport::PointsToLogic(double x, double y) const +{ + y = correctVertOrigin(y); + + Point aPos(convertPointToMm100(x), convertPointToMm100(y)); + return aPos; +} + +tools::Rectangle ImpSdrPdfImport::PointsToLogic(double left, double right, double top, + double bottom) const +{ + top = correctVertOrigin(top); + bottom = correctVertOrigin(bottom); + + Point aPos(convertPointToMm100(left), convertPointToMm100(top)); + Size aSize(convertPointToMm100(right - left), convertPointToMm100(bottom - top)); + + return tools::Rectangle(aPos, aSize); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdpntv.cxx b/svx/source/svdraw/svdpntv.cxx new file mode 100644 index 0000000000..4584e7f831 --- /dev/null +++ b/svx/source/svdraw/svdpntv.cxx @@ -0,0 +1,1218 @@ +/* -*- 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 <memory> +#include <svx/svdpntv.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svdmodel.hxx> + +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svl/hint.hxx> + +#include <svx/svdview.hxx> +#include <svx/svdglue.hxx> +#include <svx/svdobj.hxx> +#include <sxlayitm.hxx> +#include <svl/itemiter.hxx> +#include <editeng/eeitem.hxx> +#include <svl/whiter.hxx> +#include <svl/style.hxx> +#include <svx/sdrpagewindow.hxx> +#include <vcl/svapp.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/sdr/animation/objectanimator.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/converters.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <comphelper/lok.hxx> +#include <svx/svdviter.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +// interface to SdrPaintWindow + +SdrPaintWindow* SdrPaintView::FindPaintWindow(const OutputDevice& rOut) const +{ + // back to loop - there is more to test than a std::find_if and a lambda can do + for(auto& candidate : maPaintWindows) + { + if(&(candidate->GetOutputDevice()) == &rOut) + { + return candidate.get(); + } + + // check for patched to allow finding in that state, too + if(nullptr != candidate->getPatched() && &(candidate->getPatched()->GetOutputDevice()) == &rOut) + { + return candidate->getPatched(); + } + } + + return nullptr; +} + +SdrPaintWindow* SdrPaintView::GetPaintWindow(sal_uInt32 nIndex) const +{ + return maPaintWindows[nIndex].get(); +} + +void SdrPaintView::DeletePaintWindow(const SdrPaintWindow& rOld) +{ + auto aFindResult = ::std::find_if(maPaintWindows.begin(), maPaintWindows.end(), + [&](const std::unique_ptr<SdrPaintWindow>& p) { return p.get() == &rOld; }); + + if(aFindResult != maPaintWindows.end()) + { + maPaintWindows.erase(aFindResult); + } +} + +OutputDevice* SdrPaintView::GetFirstOutputDevice() const +{ + if(PaintWindowCount()) + { + return &(GetPaintWindow(0)->GetOutputDevice()); + } + + return nullptr; +} + + +SvxViewChangedHint::SvxViewChangedHint() : SfxHint(SfxHintId::SvxViewChanged) +{ +} + + +BitmapEx convertMetafileToBitmapEx( + const GDIMetaFile& rMtf, + const basegfx::B2DRange& rTargetRange, + const sal_uInt32 nMaximumQuadraticPixels) +{ + BitmapEx aBitmapEx; + + if(rMtf.GetActionSize()) + { + const drawinglayer::primitive2d::Primitive2DReference aMtf( + new drawinglayer::primitive2d::MetafilePrimitive2D( + basegfx::utils::createScaleTranslateB2DHomMatrix( + rTargetRange.getRange(), + rTargetRange.getMinimum()), + rMtf)); + aBitmapEx = drawinglayer::convertPrimitive2DContainerToBitmapEx( + drawinglayer::primitive2d::Primitive2DContainer { aMtf }, + rTargetRange, + nMaximumQuadraticPixels); + } + + return aBitmapEx; +} + +SdrPaintView::SdrPaintView(SdrModel& rSdrModel, OutputDevice* pOut) + : mrModel(rSdrModel) + , mpActualOutDev(nullptr) + , mpDragWin(nullptr) + , mpDefaultStyleSheet(nullptr) + , maDefaultAttr(rSdrModel.GetItemPool()) + , maComeBackIdle( "svx::SdrPaintView aComeBackIdle" ) + , meAnimationMode(SdrAnimationMode::Animate) + , mnHitTolPix(2) + , mnMinMovPix(3) + , mnHitTolLog(0) + , mnMinMovLog(0) + , mbPageVisible(true) + , mbPageShadowVisible(true) + , mbPageBorderVisible(true) + , mbBordVisible(true) + , mbGridVisible(true) + , mbGridFront(false) + , mbHlplVisible(true) + , mbHlplFront(true) + , mbGlueVisible(false) + , mbGlueVisible2(false) + , mbGlueVisible3(false) + , mbGlueVisible4(false) + , mbSomeObjChgdFlag(false) + , mbSwapAsynchron(false) + , mbPrintPreview(false) + , mbAnimationPause(false) + , mbBufferedOutputAllowed(false) + , mbBufferedOverlayAllowed(false) + , mbPageDecorationAllowed(true) + , mbMasterPageVisualizationAllowed(true) + , mbPreviewRenderer(false) + , mbHideOle(false) + , mbHideChart(false) + , mbHideDraw(false) + , mbHideFormControl(false) + , mbPaintTextEdit(true) + , maGridColor(COL_BLACK) +{ + maComeBackIdle.SetPriority(TaskPriority::REPAINT); + maComeBackIdle.SetInvokeHandler(LINK(this,SdrPaintView,ImpComeBackHdl)); + + SetDefaultStyleSheet(GetModel().GetDefaultStyleSheet(), true); + + if (pOut) + AddDeviceToPaintView(*pOut, nullptr); + + maColorConfig.AddListener(this); + onChangeColorConfig(); +} + +SdrPaintView::~SdrPaintView() +{ + if (mpDefaultStyleSheet) + EndListening(*mpDefaultStyleSheet); + + maColorConfig.RemoveListener(this); + ClearPageView(); + + // delete existing SdrPaintWindows + maPaintWindows.clear(); +} + + +void SdrPaintView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + //If the stylesheet has been destroyed + if (&rBC == mpDefaultStyleSheet) + { + if (rHint.GetId() == SfxHintId::Dying) + mpDefaultStyleSheet = nullptr; + return; + } + + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + SdrHintKind eKind = pSdrHint->GetKind(); + if (eKind==SdrHintKind::ObjectChange || eKind==SdrHintKind::ObjectInserted || eKind==SdrHintKind::ObjectRemoved) + { + bool bObjChg = !mbSomeObjChgdFlag; // if true, evaluate for ComeBack timer + if (bObjChg) + { + mbSomeObjChgdFlag=true; + maComeBackIdle.Start(); + } + } + + if (eKind==SdrHintKind::PageOrderChange) + { + const SdrPage* pPg=pSdrHint->GetPage(); + if (pPg && !pPg->IsInserted()) + { + if(mpPageView && mpPageView->GetPage() == pPg) + { + HideSdrPage(); + } + } + } +} + +void SdrPaintView::ConfigurationChanged( ::utl::ConfigurationBroadcaster* , ConfigurationHints eHint) +{ + if (eHint == ConfigurationHints::OnlyCurrentDocumentColorScheme) + return; + onChangeColorConfig(); + InvalidateAllWin(); +} + +IMPL_LINK_NOARG(SdrPaintView, ImpComeBackHdl, Timer *, void) +{ + if (mbSomeObjChgdFlag) { + mbSomeObjChgdFlag=false; + ModelHasChanged(); + } +} + +void SdrPaintView::FlushComeBackTimer() const +{ + if (mbSomeObjChgdFlag) { + // casting to nonconst + const_cast<SdrPaintView*>(this)->ImpComeBackHdl(&const_cast<SdrPaintView*>(this)->maComeBackIdle); + const_cast<SdrPaintView*>(this)->maComeBackIdle.Stop(); + } +} + +void SdrPaintView::ModelHasChanged() +{ + // broadcast to all PageViews + if(mpPageView && !mpPageView->GetPage()->IsInserted()) + { + HideSdrPage(); + } + + // test mpPageView here again, HideSdrPage() may have invalidated it. + if(mpPageView) + { + mpPageView->ModelHasChanged(); + } +} + + +bool SdrPaintView::IsAction() const +{ + return false; +} + +void SdrPaintView::MovAction(const Point&) +{ +} + +void SdrPaintView::EndAction() +{ +} + +void SdrPaintView::BckAction() +{ +} + +void SdrPaintView::BrkAction() +{ +} + +void SdrPaintView::TakeActionRect(tools::Rectangle&) const +{ +} + + +// info about TextEdit. Default is false. +bool SdrPaintView::IsTextEdit() const +{ + return false; +} + +sal_uInt16 SdrPaintView::ImpGetMinMovLogic(short nMinMov, const OutputDevice* pOut) const +{ + if (nMinMov>=0) return sal_uInt16(nMinMov); + if (pOut==nullptr) + { + pOut = GetFirstOutputDevice(); + } + if (pOut!=nullptr) { + return short(-pOut->PixelToLogic(Size(nMinMov,0)).Width()); + } else { + return 0; + } +} + +sal_uInt16 SdrPaintView::ImpGetHitTolLogic(short nHitTol, const OutputDevice* pOut) const +{ + if (nHitTol>=0) return sal_uInt16(nHitTol); + if (pOut==nullptr) + { + pOut = GetFirstOutputDevice(); + } + if (pOut!=nullptr) { + return short(-pOut->PixelToLogic(Size(nHitTol,0)).Width()); + } else { + return 0; + } +} + +void SdrPaintView::TheresNewMapMode() +{ + if (mpActualOutDev) { + mnHitTolLog=static_cast<sal_uInt16>(mpActualOutDev->PixelToLogic(Size(mnHitTolPix,0)).Width()); + mnMinMovLog=static_cast<sal_uInt16>(mpActualOutDev->PixelToLogic(Size(mnMinMovPix,0)).Width()); + } +} + +void SdrPaintView::SetActualWin(const OutputDevice* pWin) +{ + mpActualOutDev = const_cast<OutputDevice *>(pWin); + TheresNewMapMode(); +} + + +void SdrPaintView::ClearPageView() +{ + BrkAction(); + + if(mpPageView) + { + InvalidateAllWin(); + mpPageView.reset(); + } +} + +SdrPageView* SdrPaintView::ShowSdrPage(SdrPage* pPage) +{ + if(pPage && (!mpPageView || mpPageView->GetPage() != pPage)) + { + if(mpPageView) + { + InvalidateAllWin(); + mpPageView.reset(); + } + + if (SdrView *pView = dynamic_cast<SdrView*>(this)) + { + mpPageView.reset(new SdrPageView(pPage, *pView)); + mpPageView->Show(); + } + } + + return mpPageView.get(); +} + +void SdrPaintView::HideSdrPage() +{ + if(mpPageView) + { + mpPageView->Hide(); + mpPageView.reset(); + } +} + +void SdrPaintView::AddDeviceToPaintView(OutputDevice& rNewDev, vcl::Window *pWindow) +{ + SdrPaintWindow* pNewPaintWindow = new SdrPaintWindow(*this, rNewDev, pWindow); + maPaintWindows.emplace_back(pNewPaintWindow); + + if(mpPageView) + { + mpPageView->AddPaintWindowToPageView(*pNewPaintWindow); + } +} + +void SdrPaintView::DeleteDeviceFromPaintView(OutputDevice& rOldDev) +{ + SdrPaintWindow* pCandidate = FindPaintWindow(rOldDev); + + if(pCandidate) + { + if(mpPageView) + { + mpPageView->RemovePaintWindowFromPageView(*pCandidate); + } + + DeletePaintWindow(*pCandidate); + } +} + +void SdrPaintView::SetLayerVisible(const OUString& rName, bool bShow) +{ + if(mpPageView) + { + mpPageView->SetLayerVisible(rName, bShow); + } + + InvalidateAllWin(); +} + +bool SdrPaintView::IsLayerVisible(const OUString& rName) const +{ + if(mpPageView) + { + return mpPageView->IsLayerVisible(rName); + } + + return false; +} + +void SdrPaintView::SetLayerLocked(const OUString& rName, bool bLock) +{ + if(mpPageView) + { + mpPageView->SetLayerLocked(rName,bLock); + } +} + +bool SdrPaintView::IsLayerLocked(const OUString& rName) const +{ + if(mpPageView) + { + return mpPageView->IsLayerLocked(rName); + } + + return false; +} + +void SdrPaintView::SetLayerPrintable(const OUString& rName, bool bPrn) +{ + if(mpPageView) + { + mpPageView->SetLayerPrintable(rName,bPrn); + } +} + +bool SdrPaintView::IsLayerPrintable(const OUString& rName) const +{ + if(mpPageView) + { + return mpPageView->IsLayerPrintable(rName); + } + + return false; +} + +void SdrPaintView::PrePaint() +{ + if(mpPageView) + { + mpPageView->PrePaint(); + } +} + + +// #define SVX_REPAINT_TIMER_TEST + +void SdrPaintView::CompleteRedraw(OutputDevice* pOut, const vcl::Region& rReg, sdr::contact::ViewObjectContactRedirector* pRedirector) +{ +#ifdef SVX_REPAINT_TIMER_TEST +#define REMEMBERED_TIMES_COUNT (10) + static bool bDoTimerTest(false); + static bool bTimesInited(false); + static sal_uInt32 nRepeatCount(10); + static double fLastTimes[REMEMBERED_TIMES_COUNT]; + const sal_uInt64 nStartTime(tools::Time::GetSystemTicks()); + sal_uInt32 count(1); + sal_uInt32 a; + + if(bDoTimerTest) + { + count = nRepeatCount; + } + + for(a = 0; a < count; a++) + { +#endif // SVX_REPAINT_TIMER_TEST + + // #i74769# check if pOut is a win and has a ClipRegion. If Yes, the Region + // rReg may be made more granular (fine) with using it. Normally, rReg + // does come from Window::Paint() anyways and thus is based on a single + // rectangle which was derived from exactly that repaint region + vcl::Region aOptimizedRepaintRegion(rReg); + + if(pOut && OUTDEV_WINDOW == pOut->GetOutDevType()) + { + vcl::Window* pWindow = pOut->GetOwnerWindow(); + + if(pWindow->IsInPaint()) + { + if(!pWindow->GetPaintRegion().IsEmpty()) + { + aOptimizedRepaintRegion.Intersect(pWindow->GetPaintRegion()); + } + } + } + + SdrPaintWindow* pPaintWindow = BeginCompleteRedraw(pOut); + OSL_ENSURE(pPaintWindow, "SdrPaintView::CompleteRedraw: No OutDev (!)"); + + DoCompleteRedraw(*pPaintWindow, aOptimizedRepaintRegion, pRedirector); + EndCompleteRedraw(*pPaintWindow, true); + +#ifdef SVX_REPAINT_TIMER_TEST + } + + if(bDoTimerTest) + { + const sal_uInt64 nStopTime(tools::Time::GetSystemTicks()); + const sal_uInt64 nNeededTime(nStopTime - nStartTime); + const double fTimePerPaint((double)nNeededTime / (double)nRepeatCount); + + if(!bTimesInited) + { + for(a = 0; a < REMEMBERED_TIMES_COUNT; a++) + { + fLastTimes[a] = fTimePerPaint; + } + + bTimesInited = true; + } + else + { + for(a = 1; a < REMEMBERED_TIMES_COUNT; a++) + { + fLastTimes[a - 1] = fLastTimes[a]; + } + + fLastTimes[REMEMBERED_TIMES_COUNT - 1] = fTimePerPaint; + } + + double fAddedTimes(0.0); + + for(a = 0; a < REMEMBERED_TIMES_COUNT; a++) + { + fAddedTimes += fLastTimes[a]; + } + + const double fAverageTimePerPaint(fAddedTimes / (double)REMEMBERED_TIMES_COUNT); + + fprintf(stderr, "-----------(start result)----------\n"); + fprintf(stderr, "StartTime : %" SAL_PRIuUINT64 ", StopTime: %" SAL_PRIuUINT64 ", NeededTime: %" SAL_PRIuUINT64 ", TimePerPaint: %f\n", nStartTime, nStopTime, nNeededTime, fTimePerPaint); + fprintf(stderr, "Remembered times: "); + + for(a = 0; a < REMEMBERED_TIMES_COUNT; a++) + { + fprintf(stderr, "%d: %f ", a, fLastTimes[a]); + } + + fprintf(stderr, "\n"); + fprintf(stderr, "AverageTimePerPaint: %f\n", fAverageTimePerPaint); + fprintf(stderr, "-----------(stop result)----------\n"); + } +#endif // SVX_REPAINT_TIMER_TEST +} + + +// #i72889# + +SdrPaintWindow* SdrPaintView::BeginCompleteRedraw(OutputDevice* pOut) +{ + OSL_ENSURE(pOut, "SdrPaintView::BeginCompleteRedraw: No OutDev (!)"); + SdrPaintWindow* pPaintWindow = FindPaintWindow(*pOut); + + if(pPaintWindow) + { + // draw preprocessing, only for known devices + // prepare PreRendering + pPaintWindow->PreparePreRenderDevice(); + } + else + { + // None of the known OutputDevices is the target of this paint, use + // a temporary SdrPaintWindow for this Redraw. + pPaintWindow = new SdrPaintWindow(*this, *pOut); + pPaintWindow->setTemporaryTarget(true); + } + + return pPaintWindow; +} + +void SdrPaintView::DoCompleteRedraw(SdrPaintWindow& rPaintWindow, const vcl::Region& rReg, sdr::contact::ViewObjectContactRedirector* pRedirector) +{ + // redraw all PageViews with the target. This may expand the RedrawRegion + // at the PaintWindow, plus taking care of FormLayer expansion + if(mpPageView) + { + mpPageView->CompleteRedraw(rPaintWindow, rReg, pRedirector); + } +} + +void SdrPaintView::EndCompleteRedraw(SdrPaintWindow& rPaintWindow, bool bPaintFormLayer) +{ + std::unique_ptr<SdrPaintWindow> pPaintWindow; + if (comphelper::LibreOfficeKit::isActive() && rPaintWindow.getTemporaryTarget()) + { + // Tiled rendering, we must paint the TextEdit to the output device. + pPaintWindow.reset(&rPaintWindow); + pPaintWindow->setTemporaryTarget(false); + } + + if(rPaintWindow.getTemporaryTarget()) + { + // get rid of temp target again + delete &rPaintWindow; + } + else + { + // draw postprocessing, only for known devices + // it is necessary to always paint FormLayer + // In the LOK case control rendering is performed through LokControlHandler + if(!comphelper::LibreOfficeKit::isActive() && bPaintFormLayer) + { + ImpFormLayerDrawing(rPaintWindow); + } + + // look for active TextEdit. As long as this cannot be painted to a VDev, + // it cannot get part of buffering. In that case, output evtl. prerender + // early and paint text edit to window. + SdrPageView* pPageView = GetSdrPageView(); + if(IsTextEdit() && pPageView) + { + if (!comphelper::LibreOfficeKit::isActive() || mbPaintTextEdit) + static_cast< SdrView* >(this)->TextEditDrawing(rPaintWindow); + } + + if (comphelper::LibreOfficeKit::isActive() && pPageView) + { + // Look for active text edits in other views showing the same page, + // and show them as well. Show only if Page/MasterPage mode is matching. + bool bRequireMasterPage = pPageView->GetPage() ? pPageView->GetPage()->IsMasterPage() : false; + SdrViewIter::ForAllViews(pPageView->GetPage(), + [this, &bRequireMasterPage, &rPaintWindow] (SdrView* pView) + { + SdrPageView* pCurrentPageView = pView->GetSdrPageView(); + bool bIsCurrentMasterPage = (pCurrentPageView && pCurrentPageView->GetPage()) ? + pCurrentPageView->GetPage()->IsMasterPage() : false; + + if (pView == this || bRequireMasterPage != bIsCurrentMasterPage) + return false; + + if (pView->IsTextEdit() && pView->GetSdrPageView()) + { + pView->TextEditDrawing(rPaintWindow); + } + return false; + }); + } + + // draw Overlay, also to PreRender device if exists + rPaintWindow.DrawOverlay(rPaintWindow.GetRedrawRegion()); + + // output PreRendering + rPaintWindow.OutputPreRenderDevice(rPaintWindow.GetRedrawRegion()); + } +} + + +SdrPaintWindow* SdrPaintView::BeginDrawLayers(OutputDevice* pOut, const vcl::Region& rReg, bool bDisableIntersect) +{ + // #i74769# use BeginCompleteRedraw() as common base + SdrPaintWindow* pPaintWindow = BeginCompleteRedraw(pOut); + OSL_ENSURE(pPaintWindow, "SdrPaintView::BeginDrawLayers: No SdrPaintWindow (!)"); + + if(mpPageView) + { + SdrPageWindow* pKnownTarget = mpPageView->FindPageWindow(*pPaintWindow); + + if(pKnownTarget) + { + vcl::Region aOptimizedRepaintRegion = OptimizeDrawLayersRegion( pOut, rReg, bDisableIntersect ); + + // prepare redraw + pKnownTarget->PrepareRedraw(aOptimizedRepaintRegion); + + // remember prepared SdrPageWindow + mpPageView->setPreparedPageWindow(pKnownTarget); + } + } + + return pPaintWindow; +} + +void SdrPaintView::EndDrawLayers(SdrPaintWindow& rPaintWindow, bool bPaintFormLayer) +{ + // #i74769# use EndCompleteRedraw() as common base + EndCompleteRedraw(rPaintWindow, bPaintFormLayer); + + if(mpPageView) + { + // forget prepared SdrPageWindow + mpPageView->setPreparedPageWindow(nullptr); + } +} + +void SdrPaintView::UpdateDrawLayersRegion(const OutputDevice* pOut, const vcl::Region& rReg) +{ + SdrPaintWindow* pPaintWindow = FindPaintWindow(*pOut); + OSL_ENSURE(pPaintWindow, "SdrPaintView::UpdateDrawLayersRegion: No SdrPaintWindow (!)"); + + if(mpPageView) + { + SdrPageWindow* pKnownTarget = mpPageView->FindPageWindow(*pPaintWindow); + + if(pKnownTarget) + { + vcl::Region aOptimizedRepaintRegion = OptimizeDrawLayersRegion( pOut, rReg, false/*bDisableIntersect*/ ); + pKnownTarget->GetPaintWindow().SetRedrawRegion(aOptimizedRepaintRegion); + mpPageView->setPreparedPageWindow(pKnownTarget); // already set actually + } + } +} + +vcl::Region SdrPaintView::OptimizeDrawLayersRegion(const OutputDevice* pOut, const vcl::Region& rReg, bool bDisableIntersect) +{ + // #i74769# check if pOut is a win and has a ClipRegion. If Yes, the Region + // rReg may be made more granular (fine) with using it. Normally, rReg + // does come from Window::Paint() anyways and thus is based on a single + // rectangle which was derived from exactly that repaint region + vcl::Region aOptimizedRepaintRegion(rReg); + + // #i76114# Intersecting the region with the Window's paint region is disabled + // for print preview in Calc, because the intersection can be empty (if the paint + // region is outside of the table area of the page), and then no clip region + // would be set. + if(pOut && OUTDEV_WINDOW == pOut->GetOutDevType() && !bDisableIntersect) + { + vcl::Window* pWindow = pOut->GetOwnerWindow(); + + if(pWindow->IsInPaint()) + { + if(!pWindow->GetPaintRegion().IsEmpty()) + { + aOptimizedRepaintRegion.Intersect(pWindow->GetPaintRegion()); + } + } + } + return aOptimizedRepaintRegion; +} + + +void SdrPaintView::ImpFormLayerDrawing( SdrPaintWindow& rPaintWindow ) +{ + if(!mpPageView) + return; + + SdrPageWindow* pKnownTarget = mpPageView->FindPageWindow(rPaintWindow); + + if(pKnownTarget) + { + const SdrModel& rModel = GetModel(); + const SdrLayerAdmin& rLayerAdmin = rModel.GetLayerAdmin(); + const SdrLayerID nControlLayerId = rLayerAdmin.GetLayerID(rLayerAdmin.GetControlLayerName()); + + // BUFFERED use GetTargetOutputDevice() now, it may be targeted to VDevs, too + // need to set PreparedPageWindow to make DrawLayer use the correct ObjectContact + mpPageView->setPreparedPageWindow(pKnownTarget); + mpPageView->DrawLayer(nControlLayerId, &rPaintWindow.GetTargetOutputDevice()); + mpPageView->setPreparedPageWindow(nullptr); + } +} + + +bool SdrPaintView::KeyInput(const KeyEvent& /*rKEvt*/, vcl::Window* /*pWin*/) +{ + return false; +} + +void SdrPaintView::GlueInvalidate() const +{ + // Do not invalidate GluePoints in Online + // They are handled on front-end + if (comphelper::LibreOfficeKit::isActive()) + return; + + const sal_uInt32 nWindowCount(PaintWindowCount()); + + for(sal_uInt32 nWinNum(0); nWinNum < nWindowCount; nWinNum++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(nWinNum); + + if(pPaintWindow->OutputToWindow()) + { + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if(mpPageView) + { + const SdrObjList* pOL=mpPageView->GetObjList(); + for (const rtl::Reference<SdrObject>& pObj : *pOL) { + const SdrGluePointList* pGPL=pObj->GetGluePointList(); + if (pGPL!=nullptr && pGPL->GetCount()!=0) { + pGPL->Invalidate(*rOutDev.GetOwnerWindow(), pObj.get()); + } + } + } + } + } +} + +void SdrPaintView::InvalidateAllWin() +{ + const sal_uInt32 nWindowCount(PaintWindowCount()); + + for(sal_uInt32 a(0); a < nWindowCount; a++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(a); + + if(pPaintWindow->OutputToWindow()) + { + InvalidateOneWin(pPaintWindow->GetOutputDevice()); + } + } +} + +void SdrPaintView::InvalidateAllWin(const tools::Rectangle& rRect) +{ + const sal_uInt32 nWindowCount(PaintWindowCount()); + + for(sal_uInt32 a(0); a < nWindowCount; a++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(a); + + if(pPaintWindow->OutputToWindow()) + { + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + tools::Rectangle aRect(rRect); + + Point aOrg(rOutDev.GetMapMode().GetOrigin()); + aOrg.setX(-aOrg.X() ); aOrg.setY(-aOrg.Y() ); + tools::Rectangle aOutRect(aOrg, rOutDev.GetOutputSize()); + + // In case of tiled rendering we want to get all invalidations, so visual area is not interesting. + if (aRect.Overlaps(aOutRect) || comphelper::LibreOfficeKit::isActive()) + { + InvalidateOneWin(rOutDev, aRect); + } + } + } +} + +void SdrPaintView::InvalidateOneWin(OutputDevice& rDevice) +{ + // do not erase background, that causes flicker (!) + rDevice.GetOwnerWindow()->Invalidate(InvalidateFlags::NoErase); +} + +void SdrPaintView::InvalidateOneWin(OutputDevice& rDevice, const tools::Rectangle& rRect) +{ + // do not erase background, that causes flicker (!) + rDevice.GetOwnerWindow()->Invalidate(rRect, InvalidateFlags::NoErase); +} + +void SdrPaintView::LeaveOneGroup() +{ + if(mpPageView) + { + mpPageView->LeaveOneGroup(); + } +} + +void SdrPaintView::LeaveAllGroup() +{ + if(mpPageView) + { + mpPageView->LeaveAllGroup(); + } +} + +bool SdrPaintView::IsGroupEntered() const +{ + if(mpPageView) + { + return (mpPageView->GetEnteredLevel() != 0); + } + + return false; +} + +void SdrPaintView::SetNotPersistDefaultAttr(const SfxItemSet& rAttr) +{ + // bReplaceAll has no effect here at all. + bool bMeasure= dynamic_cast<const SdrView*>(this) != nullptr && static_cast<SdrView*>(this)->IsMeasureTool(); + + if (const SdrLayerIdItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LAYERID)) + { + SdrLayerID nLayerId = pPoolItem->GetValue(); + const SdrLayer* pLayer = GetModel().GetLayerAdmin().GetLayerPerID(nLayerId); + if (pLayer!=nullptr) { + if (bMeasure) maMeasureLayer=pLayer->GetName(); + else maActualLayer=pLayer->GetName(); + } + } + if (const SdrLayerNameItem *pPoolItem = rAttr.GetItemIfSet(SDRATTR_LAYERNAME)) + { + if (bMeasure) maMeasureLayer = pPoolItem->GetValue(); + else maActualLayer = pPoolItem->GetValue(); + } +} + +void SdrPaintView::MergeNotPersistDefaultAttr(SfxItemSet& rAttr) const +{ + // bOnlyHardAttr has no effect here at all. + bool bMeasure= dynamic_cast<const SdrView*>(this) != nullptr && static_cast<const SdrView*>(this)->IsMeasureTool(); + const OUString& aNam = bMeasure ? maMeasureLayer : maActualLayer; + rAttr.Put(SdrLayerNameItem(aNam)); + SdrLayerID nLayer = GetModel().GetLayerAdmin().GetLayerID(aNam); + if (nLayer!=SDRLAYER_NOTFOUND) { + rAttr.Put(SdrLayerIdItem(nLayer)); + } +} + +void SdrPaintView::SetDefaultAttr(const SfxItemSet& rAttr, bool bReplaceAll) +{ +#ifdef DBG_UTIL + { + bool bHasEEFeatureItems=false; + SfxItemIter aIter(rAttr); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem; + pItem = aIter.NextItem()) + { + if (!IsInvalidItem(pItem)) { + sal_uInt16 nW=pItem->Which(); + if (nW>=EE_FEATURE_START && nW<=EE_FEATURE_END) bHasEEFeatureItems=true; + } + } + + if(bHasEEFeatureItems) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + "SdrPaintView::SetDefaultAttr(): Setting EE_FEATURE items at the SdrView does not make sense! It only leads to overhead and unreadable documents.")); + xInfoBox->run(); + } + } +#endif + if (bReplaceAll) maDefaultAttr.Set(rAttr); + else maDefaultAttr.Put(rAttr,false); // if FALSE, regard InvalidItems as "holes," not as Default + SetNotPersistDefaultAttr(rAttr); +} + +void SdrPaintView::SetDefaultStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (mpDefaultStyleSheet) + EndListening(*mpDefaultStyleSheet); + mpDefaultStyleSheet=pStyleSheet; + if (mpDefaultStyleSheet) + StartListening(*mpDefaultStyleSheet); + + if (pStyleSheet!=nullptr && !bDontRemoveHardAttr) { + SfxWhichIter aIter(pStyleSheet->GetItemSet()); + sal_uInt16 nWhich=aIter.FirstWhich(); + while (nWhich!=0) { + if (aIter.GetItemState()==SfxItemState::SET) { + maDefaultAttr.ClearItem(nWhich); + } + nWhich=aIter.NextWhich(); + } + } +} + +void SdrPaintView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if(bOnlyHardAttr || !mpDefaultStyleSheet) + { + rTargetSet.Put(maDefaultAttr, false); + } + else + { + // else merge with DefStyleSheet + rTargetSet.Put(mpDefaultStyleSheet->GetItemSet(), false); + rTargetSet.Put(maDefaultAttr, false); + } + MergeNotPersistDefaultAttr(rTargetSet); +} + +void SdrPaintView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + SetDefaultAttr(rSet,bReplaceAll); +} + +SfxStyleSheet* SdrPaintView::GetStyleSheet() const +{ + return mpDefaultStyleSheet; +} + +void SdrPaintView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + SetDefaultStyleSheet(pStyleSheet,bDontRemoveHardAttr); +} + +void SdrPaintView::MakeVisible(const tools::Rectangle& rRect, vcl::Window& rWin) +{ + // TODO: handle when the text cursor goes out of the chart area + // However this hack avoids that the cursor gets misplaced wrt the text. + if (comphelper::LibreOfficeKit::isActive() && rWin.IsChart()) + { + return; + } + + MapMode aMap(rWin.GetMapMode()); + Size aActualSize(rWin.GetOutDev()->GetOutputSize()); + + if( aActualSize.IsEmpty() ) + return; + + Size aNewSize(rRect.GetSize()); + bool bNewScale=false; + bool bNeedMoreX=aNewSize.Width()>aActualSize.Width(); + bool bNeedMoreY=aNewSize.Height()>aActualSize.Height(); + if (bNeedMoreX || bNeedMoreY) + { + bNewScale=true; + // set new MapMode (Size+Org) and invalidate everything + Fraction aXFact(aNewSize.Width(),aActualSize.Width()); + Fraction aYFact(aNewSize.Height(),aActualSize.Height()); + if (aYFact>aXFact) aXFact=aYFact; + aXFact*=aMap.GetScaleX(); + aXFact.ReduceInaccurate(10); // to avoid runovers and BigInt mapping + aMap.SetScaleX(aXFact); + aMap.SetScaleY(aYFact); + rWin.SetMapMode(aMap); + aActualSize=rWin.GetOutDev()->GetOutputSize(); + } + Point aOrg(aMap.GetOrigin()); + tools::Long dx=0,dy=0; + tools::Long l=-aOrg.X(); + tools::Long r=-aOrg.X()+aActualSize.Width()-1; + tools::Long o=-aOrg.Y(); + tools::Long u=-aOrg.Y()+aActualSize.Height()-1; + if (l>rRect.Left()) dx=rRect.Left()-l; + else if (r<rRect.Right()) dx=rRect.Right()-r; + if (o>rRect.Top()) dy=rRect.Top()-o; + else if (u<rRect.Bottom()) dy=rRect.Bottom()-u; + aMap.SetOrigin(Point(aOrg.X()-dx,aOrg.Y()-dy)); + if (!bNewScale) { + if (dx!=0 || dy!=0) { + rWin.Scroll(-dx,-dy); + rWin.SetMapMode(aMap); + rWin.PaintImmediately(); + } + } else { + rWin.SetMapMode(aMap); + InvalidateOneWin(*rWin.GetOutDev()); + } +} + +void SdrPaintView::DoConnect(SdrOle2Obj* /*pOleObj*/) +{ +} + +void SdrPaintView::SetAnimationEnabled( bool bEnable ) +{ + SetAnimationMode( bEnable ? SdrAnimationMode::Animate : SdrAnimationMode::Disable ); +} + +void SdrPaintView::SetAnimationPause( bool bSet ) +{ + if(mbAnimationPause == bSet) + return; + + mbAnimationPause = bSet; + + if(!mpPageView) + return; + + for(sal_uInt32 b(0); b < mpPageView->PageWindowCount(); b++) + { + SdrPageWindow& rPageWindow = *(mpPageView->GetPageWindow(b)); + sdr::contact::ObjectContact& rObjectContact = rPageWindow.GetObjectContact(); + sdr::animation::primitiveAnimator& rAnimator = rObjectContact.getPrimitiveAnimator(); + + if(rAnimator.IsPaused() != bSet) + { + rAnimator.SetPaused(bSet); + } + } +} + +void SdrPaintView::SetAnimationMode( const SdrAnimationMode eMode ) +{ + meAnimationMode = eMode; +} + +void SdrPaintView::VisAreaChanged(const OutputDevice* pOut) +{ + if(!mpPageView) + return; + + if (pOut) + { + SdrPageWindow* pWindow = mpPageView->FindPageWindow(*const_cast<OutputDevice*>(pOut)); + + if(pWindow) + { + VisAreaChanged(); + } + } + else + { + VisAreaChanged(); + } +} + +void SdrPaintView::VisAreaChanged() +{ + // notify SfxListener + Broadcast(SvxViewChangedHint()); +} + + +void SdrPaintView::onChangeColorConfig() +{ + maGridColor = maColorConfig.GetColorValue( svtools::DRAWGRID ).nColor; +} + + +// Set background color for svx at SdrPageViews +void SdrPaintView::SetApplicationBackgroundColor(Color aBackgroundColor) +{ + if(mpPageView) + { + mpPageView->SetApplicationBackgroundColor(aBackgroundColor); + } +} + +// Set document color for svx at SdrPageViews +void SdrPaintView::SetApplicationDocumentColor(Color aDocumentColor) +{ + if(mpPageView) + { + mpPageView->SetApplicationDocumentColor(aDocumentColor); + } +} + +bool SdrPaintView::IsBufferedOutputAllowed() const +{ + return (mbBufferedOutputAllowed && SvtOptionsDrawinglayer::IsPaintBuffer()); +} + +void SdrPaintView::SetBufferedOutputAllowed(bool bNew) +{ + if(bNew != mbBufferedOutputAllowed) + { + mbBufferedOutputAllowed = bNew; + } +} + +bool SdrPaintView::IsBufferedOverlayAllowed() const +{ + return (mbBufferedOverlayAllowed && SvtOptionsDrawinglayer::IsOverlayBuffer()); +} + +void SdrPaintView::SetBufferedOverlayAllowed(bool bNew) +{ + if(bNew != mbBufferedOverlayAllowed) + { + mbBufferedOverlayAllowed = bNew; + } +} + + +void SdrPaintView::SetPageDecorationAllowed(bool bNew) +{ + if(bNew != mbPageDecorationAllowed) + { + mbPageDecorationAllowed = bNew; + } +} + +void SdrPaintView::SetMasterPageVisualizationAllowed(bool bNew) +{ + if(bNew != mbMasterPageVisualizationAllowed) + { + mbMasterPageVisualizationAllowed = bNew; + } +} + +// #i38135# Sets the timer for Object animations and restarts. +void SdrPaintView::SetAnimationTimer(sal_uInt32 nTime) +{ + if(mpPageView) + { + // first, reset all timers at all windows to 0L + for(sal_uInt32 a(0); a < mpPageView->PageWindowCount(); a++) + { + SdrPageWindow& rPageWindow = *mpPageView->GetPageWindow(a); + sdr::contact::ObjectContact& rObjectContact = rPageWindow.GetObjectContact(); + sdr::animation::primitiveAnimator& rAnimator = rObjectContact.getPrimitiveAnimator(); + rAnimator.SetTime(nTime); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdpoev.cxx b/svx/source/svdraw/svdpoev.cxx new file mode 100644 index 0000000000..1cfca1ab94 --- /dev/null +++ b/svx/source/svdraw/svdpoev.cxx @@ -0,0 +1,647 @@ +/* -*- 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/svdpoev.hxx> +#include <math.h> +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdundo.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/svdtrans.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <tools/debug.hxx> +#include <tools/helpers.hxx> + +#include <svx/polypolygoneditor.hxx> + +using namespace sdr; + + +void SdrPolyEditView::ImpResetPolyPossibilityFlags() +{ + eMarkedPointsSmooth=SdrPathSmoothKind::DontCare; + eMarkedSegmentsKind=SdrPathSegmentKind::DontCare; + bSetMarkedPointsSmoothPossible=false; + bSetMarkedSegmentsKindPossible=false; +} + +SdrPolyEditView::SdrPolyEditView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: SdrEditView(rSdrModel, pOut) +{ + ImpResetPolyPossibilityFlags(); +} + +SdrPolyEditView::~SdrPolyEditView() +{ +} + +void SdrPolyEditView::ImpCheckPolyPossibilities() +{ + ImpResetPolyPossibilityFlags(); + const size_t nMarkCount(GetMarkedObjectCount()); + + if(!nMarkCount || ImpIsFrameHandles()) + return; + + bool b1stSmooth(true); + bool b1stSegm(true); + bool bCurve(false); + bool bSmoothFuz(false); + bool bSegmFuz(false); + basegfx::B2VectorContinuity eSmooth = basegfx::B2VectorContinuity::NONE; + + for(size_t nMarkNum = 0; nMarkNum < nMarkCount; ++nMarkNum) + { + SdrMark* pM = GetSdrMarkByIndex(nMarkNum); + CheckPolyPossibilitiesHelper( pM, b1stSmooth, b1stSegm, bCurve, bSmoothFuz, bSegmFuz, eSmooth ); + } +} + +void SdrPolyEditView::CheckPolyPossibilitiesHelper( SdrMark* pM, bool& b1stSmooth, bool& b1stSegm, bool& bCurve, bool& bSmoothFuz, bool& bSegmFuz, basegfx::B2VectorContinuity& eSmooth ) +{ + SdrObject* pObj = pM->GetMarkedSdrObj(); + SdrPathObj* pPath = dynamic_cast<SdrPathObj*>( pObj ); + + if (!pPath) + return; + + SdrUShortCont& rPts = pM->GetMarkedPoints(); + if (rPts.empty()) + return; + + const bool bClosed(pPath->IsClosed()); + bSetMarkedPointsSmoothPossible = true; + + if (bClosed) + { + bSetMarkedSegmentsKindPossible = true; + } + + for (const auto& rPt : rPts) + { + sal_uInt32 nNum(rPt); + sal_uInt32 nPolyNum, nPntNum; + + if(PolyPolygonEditor::GetRelativePolyPoint(pPath->GetPathPoly(), nNum, nPolyNum, nPntNum)) + { + const basegfx::B2DPolygon aLocalPolygon(pPath->GetPathPoly().getB2DPolygon(nPolyNum)); + bool bCanSegment(bClosed || nPntNum < aLocalPolygon.count() - 1); + + if(!bSetMarkedSegmentsKindPossible && bCanSegment) + { + bSetMarkedSegmentsKindPossible = true; + } + + if(!bSmoothFuz) + { + if (b1stSmooth) + { + b1stSmooth = false; + eSmooth = basegfx::utils::getContinuityInPoint(aLocalPolygon, nPntNum); + } + else + { + bSmoothFuz = (eSmooth != basegfx::utils::getContinuityInPoint(aLocalPolygon, nPntNum)); + } + } + + if(!bSegmFuz && bCanSegment) + { + bool bCrv(aLocalPolygon.isNextControlPointUsed(nPntNum)); + + if(b1stSegm) + { + b1stSegm = false; + bCurve = bCrv; + } + else + { + bSegmFuz = (bCrv != bCurve); + } + } + } + } + + if(!b1stSmooth && !bSmoothFuz) + { + if(basegfx::B2VectorContinuity::NONE == eSmooth) + { + eMarkedPointsSmooth = SdrPathSmoothKind::Angular; + } + + if(basegfx::B2VectorContinuity::C1 == eSmooth) + { + eMarkedPointsSmooth = SdrPathSmoothKind::Asymmetric; + } + + if(basegfx::B2VectorContinuity::C2 == eSmooth) + { + eMarkedPointsSmooth = SdrPathSmoothKind::Symmetric; + } + } + + if(!b1stSegm && !bSegmFuz) + { + eMarkedSegmentsKind = bCurve ? SdrPathSegmentKind::Curve : SdrPathSegmentKind::Line; + } +} + +void SdrPolyEditView::SetMarkedPointsSmooth(SdrPathSmoothKind eKind) +{ + basegfx::B2VectorContinuity eFlags; + + if(SdrPathSmoothKind::Angular == eKind) + { + eFlags = basegfx::B2VectorContinuity::NONE; + } + else if(SdrPathSmoothKind::Asymmetric == eKind) + { + eFlags = basegfx::B2VectorContinuity::C1; + } + else if(SdrPathSmoothKind::Symmetric == eKind) + { + eFlags = basegfx::B2VectorContinuity::C2; + } + else + { + return; + } + + if(!HasMarkedPoints()) + return; + + SortMarkedObjects(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditSetPointsSmooth), GetDescriptionOfMarkedPoints()); + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t nMarkNum(nMarkCount); nMarkNum > 0;) + { + --nMarkNum; + SdrMark* pM = GetSdrMarkByIndex(nMarkNum); + SdrPathObj* pPath = dynamic_cast< SdrPathObj* >( pM->GetMarkedSdrObj() ); + if (!pPath) + continue; + + SdrUShortCont& rPts = pM->GetMarkedPoints(); + PolyPolygonEditor aEditor(pPath->GetPathPoly()); + if (aEditor.SetPointsSmooth(eFlags, rPts)) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pPath)); + pPath->SetPathPoly(aEditor.GetPolyPolygon()); + } + } + + if( bUndo ) + EndUndo(); +} + +void SdrPolyEditView::SetMarkedSegmentsKind(SdrPathSegmentKind eKind) +{ + if(!HasMarkedPoints()) + return; + + SortMarkedObjects(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditSetSegmentsKind), GetDescriptionOfMarkedPoints()); + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t nMarkNum=nMarkCount; nMarkNum > 0;) + { + --nMarkNum; + SdrMark* pM = GetSdrMarkByIndex(nMarkNum); + SdrPathObj* pPath = dynamic_cast< SdrPathObj* >( pM->GetMarkedSdrObj() ); + if (!pPath) + continue; + SdrUShortCont& rPts = pM->GetMarkedPoints(); + PolyPolygonEditor aEditor( pPath->GetPathPoly()); + if (aEditor.SetSegmentsKind(eKind, rPts)) + { + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pPath)); + pPath->SetPathPoly(aEditor.GetPolyPolygon()); + } + } + + if( bUndo ) + EndUndo(); +} + +bool SdrPolyEditView::IsSetMarkedPointsSmoothPossible() const +{ + ForcePossibilities(); + return bSetMarkedPointsSmoothPossible; +} + +SdrPathSmoothKind SdrPolyEditView::GetMarkedPointsSmooth() const +{ + ForcePossibilities(); + return eMarkedPointsSmooth; +} + +bool SdrPolyEditView::IsSetMarkedSegmentsKindPossible() const +{ + ForcePossibilities(); + return bSetMarkedSegmentsKindPossible; +} + +SdrPathSegmentKind SdrPolyEditView::GetMarkedSegmentsKind() const +{ + ForcePossibilities(); + return eMarkedSegmentsKind; +} + +bool SdrPolyEditView::IsDeleteMarkedPointsPossible() const +{ + return HasMarkedPoints(); +} + +void SdrPolyEditView::DeleteMarkedPoints() +{ + if (!HasMarkedPoints()) + return; + + BrkAction(); + SortMarkedObjects(); + const size_t nMarkCount=GetMarkedObjectCount(); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + { + // Description + BegUndo(SvxResId(STR_EditDelete),GetDescriptionOfMarkedPoints(),SdrRepeatFunc::Delete); + } + + for (size_t nMarkNum=nMarkCount; nMarkNum>0;) + { + --nMarkNum; + SdrMark* pM=GetSdrMarkByIndex(nMarkNum); + SdrPathObj* pPath = dynamic_cast< SdrPathObj* >( pM->GetMarkedSdrObj() ); + if (!pPath) + continue; + + SdrUShortCont& rPts = pM->GetMarkedPoints(); + PolyPolygonEditor aEditor( pPath->GetPathPoly()); + if (aEditor.DeletePoints(rPts)) + { + if( aEditor.GetPolyPolygon().count() ) + { + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pPath)); + pPath->SetPathPoly( aEditor.GetPolyPolygon() ); + } + else + { + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pPath)); + pM->GetPageView()->GetObjList()->RemoveObject(pPath->GetOrdNum()); + } + } + } + + if( bUndo ) + EndUndo(); + UnmarkAllPoints(); + MarkListHasChanged(); +} + +void SdrPolyEditView::RipUpAtMarkedPoints() +{ + if(!HasMarkedPoints()) + return; + + SortMarkedObjects(); + const size_t nMarkCount(GetMarkedObjectCount()); + + const bool bUndo = IsUndoEnabled(); + if( bUndo ) + BegUndo(SvxResId(STR_EditRipUp), GetDescriptionOfMarkedPoints()); + + for(size_t nMarkNum = nMarkCount; nMarkNum > 0;) + { + --nMarkNum; + SdrMark* pM = GetSdrMarkByIndex(nMarkNum); + SdrPathObj* pObj = dynamic_cast<SdrPathObj*>( pM->GetMarkedSdrObj() ); + if (!pObj) + continue; + + SdrUShortCont& rPts = pM->GetMarkedPoints(); + + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + bool bCorrectionFlag(false); + sal_uInt32 nMax(pObj->GetHdlCount()); + + for(SdrUShortCont::const_reverse_iterator it = rPts.rbegin(); it != rPts.rend(); ++it) + { + sal_uInt32 nNewPt0Idx(0); + rtl::Reference<SdrPathObj> pNewObj = pObj->RipPoint(*it, nNewPt0Idx); + + if(pNewObj) + { + pM->GetPageView()->GetObjList()->InsertObject(pNewObj.get(), pObj->GetOrdNum() + 1); + if (bUndo) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pNewObj)); + MarkObj(pNewObj.get(), pM->GetPageView(), false, true); + } + + if(nNewPt0Idx) + { + // correction necessary? + DBG_ASSERT(!bCorrectionFlag,"Multiple index corrections at SdrPolyEditView::RipUp()."); + if(!bCorrectionFlag) + { + bCorrectionFlag = true; + + SdrUShortCont aReplaceSet; + for(const auto& rPt : rPts) + { + sal_uInt32 nPntNum(rPt); + nPntNum += nNewPt0Idx; + + if(nPntNum >= nMax) + { + nPntNum -= nMax; + } + + aReplaceSet.insert( static_cast<sal_uInt16>(nPntNum) ); + } + rPts.swap(aReplaceSet); + + it = rPts.rbegin(); + } + } + } + } + + UnmarkAllPoints(); + if( bUndo ) + EndUndo(); + MarkListHasChanged(); +} + +bool SdrPolyEditView::IsRipUpAtMarkedPointsPossible() const +{ + bool bRetval(false); + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t a = 0; a < nMarkCount; ++a) + { + const SdrMark* pMark = GetSdrMarkByIndex(a); + const SdrPathObj* pMarkedPathObject = dynamic_cast< const SdrPathObj* >(pMark->GetMarkedSdrObj()); + + if (!pMarkedPathObject) + continue; + + const SdrUShortCont& rSelectedPoints = pMark->GetMarkedPoints(); + if (rSelectedPoints.empty()) + continue; + + const basegfx::B2DPolyPolygon& rPathPolyPolygon = pMarkedPathObject->GetPathPoly(); + + if(1 == rPathPolyPolygon.count()) + { + // #i76617# Do not yet use basegfx::B2DPolygon since curve definitions + // are different and methods need to be changed thoroughly with interaction rework + const tools::Polygon aPathPolygon(rPathPolyPolygon.getB2DPolygon(0)); + const sal_uInt16 nPointCount(aPathPolygon.GetSize()); + + if(nPointCount >= 3) + { + bRetval = pMarkedPathObject->IsClosedObj() // #i76617# + || std::any_of(rSelectedPoints.begin(), rSelectedPoints.end(), + [nPointCount](const sal_uInt16 nMarkedPointNum) { + return nMarkedPointNum > 0 && nMarkedPointNum < nPointCount - 1; + }); + } + } + } + + return bRetval; +} + +bool SdrPolyEditView::IsOpenCloseMarkedObjectsPossible() const +{ + bool bRetval(false); + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t a = 0; a < nMarkCount; ++a) + { + const SdrMark* pMark = GetSdrMarkByIndex(a); + const SdrPathObj* pMarkedPathObject = dynamic_cast< const SdrPathObj* >(pMark->GetMarkedSdrObj()); + + if(pMarkedPathObject) + { + // #i76617# Do not yet use basegfx::B2DPolygon since curve definitions + // are different and methods need to be changed thoroughly with interaction rework + const tools::PolyPolygon aPathPolyPolygon(pMarkedPathObject->GetPathPoly()); + const sal_uInt16 nPolygonCount(aPathPolyPolygon.Count()); + + for(sal_uInt16 b(0); !bRetval && b < nPolygonCount; b++) + { + const tools::Polygon& rPathPolygon = aPathPolyPolygon[b]; + const sal_uInt16 nPointCount(rPathPolygon.GetSize()); + + bRetval = (nPointCount >= 3); + } + } + } + + return bRetval; +} + +SdrObjClosedKind SdrPolyEditView::GetMarkedObjectsClosedState() const +{ + bool bOpen(false); + bool bClosed(false); + const size_t nMarkCount(GetMarkedObjectCount()); + + for(size_t a = 0; !(bOpen && bClosed) && a < nMarkCount; ++a) + { + const SdrMark* pMark = GetSdrMarkByIndex(a); + const SdrPathObj* pMarkedPathObject = dynamic_cast< const SdrPathObj* >(pMark->GetMarkedSdrObj()); + + if(pMarkedPathObject) + { + if(pMarkedPathObject->IsClosedObj()) + { + bClosed = true; + } + else + { + bOpen = true; + } + } + } + + if(bOpen && bClosed) + { + return SdrObjClosedKind::DontCare; + } + else if(bOpen) + { + return SdrObjClosedKind::Open; + } + else + { + return SdrObjClosedKind::Closed; + } +} + +void SdrPolyEditView::ImpTransformMarkedPoints(PPolyTrFunc pTrFunc, const void* p1, const void* p2, const void* p3, const void* p4) +{ + const bool bUndo = IsUndoEnabled(); + + const size_t nMarkCount=GetMarkedObjectCount(); + for (size_t nm=0; nm<nMarkCount; ++nm) + { + SdrMark* pM=GetSdrMarkByIndex(nm); + SdrObject* pObj=pM->GetMarkedSdrObj(); + SdrPathObj* pPath=dynamic_cast<SdrPathObj*>( pObj ); + if (!pPath) + continue; + + const SdrUShortCont& rPts = pM->GetMarkedPoints(); + if (rPts.empty()) + continue; + + if( bUndo ) + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj)); + + basegfx::B2DPolyPolygon aXPP(pPath->GetPathPoly()); + + for (const auto& rPt : rPts) + { + sal_uInt32 nPt = rPt; + sal_uInt32 nPolyNum, nPointNum; + + if(PolyPolygonEditor::GetRelativePolyPoint(aXPP, nPt, nPolyNum, nPointNum)) + { + //#i83671# used nLocalPointNum (which was the polygon point count) + // instead of the point index (nPointNum). This of course led + // to a wrong point access to the B2DPolygon. + basegfx::B2DPolygon aNewXP(aXPP.getB2DPolygon(nPolyNum)); + Point aPos, aC1, aC2; + bool bC1(false); + bool bC2(false); + + const basegfx::B2DPoint aB2DPos(aNewXP.getB2DPoint(nPointNum)); + aPos = Point(FRound(aB2DPos.getX()), FRound(aB2DPos.getY())); + + if(aNewXP.isPrevControlPointUsed(nPointNum)) + { + const basegfx::B2DPoint aB2DC1(aNewXP.getPrevControlPoint(nPointNum)); + aC1 = Point(FRound(aB2DC1.getX()), FRound(aB2DC1.getY())); + bC1 = true; + } + + if(aNewXP.isNextControlPointUsed(nPointNum)) + { + const basegfx::B2DPoint aB2DC2(aNewXP.getNextControlPoint(nPointNum)); + aC2 = Point(FRound(aB2DC2.getX()), FRound(aB2DC2.getY())); + bC2 = true; + } + + (*pTrFunc)(aPos,&aC1,&aC2,p1,p2,p3,p4); + aNewXP.setB2DPoint(nPointNum, basegfx::B2DPoint(aPos.X(), aPos.Y())); + + if (bC1) + { + aNewXP.setPrevControlPoint(nPointNum, basegfx::B2DPoint(aC1.X(), aC1.Y())); + } + + if (bC2) + { + aNewXP.setNextControlPoint(nPointNum, basegfx::B2DPoint(aC2.X(), aC2.Y())); + } + + aXPP.setB2DPolygon(nPolyNum, aNewXP); + } + } + + pPath->SetPathPoly(aXPP); + } +} + + +static void ImpMove(Point& rPt, Point* pC1, Point* pC2, const void* p1, const void* /*p2*/, const void* /*p3*/, const void* /*p4*/) +{ + rPt.Move(*static_cast<const Size*>(p1)); + if (pC1!=nullptr) pC1->Move(*static_cast<const Size*>(p1)); + if (pC2!=nullptr) pC2->Move(*static_cast<const Size*>(p1)); +} + +void SdrPolyEditView::MoveMarkedPoints(const Size& rSiz) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditMove)); + BegUndo(aStr,GetDescriptionOfMarkedPoints(),SdrRepeatFunc::Move); + ImpTransformMarkedPoints(ImpMove,&rSiz); + EndUndo(); + AdjustMarkHdl(); +} + +static void ImpResize(Point& rPt, Point* pC1, Point* pC2, const void* p1, const void* p2, const void* p3, const void* /*p4*/) +{ + ResizePoint(rPt,*static_cast<const Point*>(p1),*static_cast<const Fraction*>(p2),*static_cast<const Fraction*>(p3)); + if (pC1!=nullptr) ResizePoint(*pC1,*static_cast<const Point*>(p1),*static_cast<const Fraction*>(p2),*static_cast<const Fraction*>(p3)); + if (pC2!=nullptr) ResizePoint(*pC2,*static_cast<const Point*>(p1),*static_cast<const Fraction*>(p2),*static_cast<const Fraction*>(p3)); +} + +void SdrPolyEditView::ResizeMarkedPoints(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditResize)); + BegUndo(aStr,GetDescriptionOfMarkedPoints(),SdrRepeatFunc::Resize); + ImpTransformMarkedPoints(ImpResize,&rRef,&xFact,&yFact); + EndUndo(); + AdjustMarkHdl(); +} + +static void ImpRotate(Point& rPt, Point* pC1, Point* pC2, const void* p1, const void* /*p2*/, const void* p3, const void* p4) +{ + RotatePoint(rPt,*static_cast<const Point*>(p1),*static_cast<const double*>(p3),*static_cast<const double*>(p4)); + if (pC1!=nullptr) RotatePoint(*pC1,*static_cast<const Point*>(p1),*static_cast<const double*>(p3),*static_cast<const double*>(p4)); + if (pC2!=nullptr) RotatePoint(*pC2,*static_cast<const Point*>(p1),*static_cast<const double*>(p3),*static_cast<const double*>(p4)); +} + +void SdrPolyEditView::RotateMarkedPoints(const Point& rRef, Degree100 nAngle) +{ + ForceUndirtyMrkPnt(); + OUString aStr(SvxResId(STR_EditResize)); + BegUndo(aStr,GetDescriptionOfMarkedPoints(),SdrRepeatFunc::Rotate); + double nSin = sin(toRadians(nAngle)); + double nCos = cos(toRadians(nAngle)); + ImpTransformMarkedPoints(ImpRotate,&rRef,&nAngle,&nSin,&nCos); + EndUndo(); + AdjustMarkHdl(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdsnpv.cxx b/svx/source/svdraw/svdsnpv.cxx new file mode 100644 index 0000000000..bcef8b3c3e --- /dev/null +++ b/svx/source/svdraw/svdsnpv.cxx @@ -0,0 +1,633 @@ +/* -*- 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/svdsnpv.hxx> +#include <math.h> + +#include <svx/svdobj.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/svditer.hxx> +#include <svx/sdr/overlay/overlayobjectlist.hxx> +#include <sdr/overlay/overlaycrosshair.hxx> +#include <sdr/overlay/overlayhelpline.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <tools/debug.hxx> +#include <vcl/ptrstyle.hxx> + + +class ImplPageOriginOverlay +{ + // The OverlayObjects + sdr::overlay::OverlayObjectList maObjects; + + // The current position in logical coordinates + basegfx::B2DPoint maPosition; + +public: + ImplPageOriginOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos); + + // The OverlayObjects are cleared using the destructor of OverlayObjectList. + // That destructor calls clear() at the list which removes all objects from the + // OverlayManager and deletes them. + + void SetPosition(const basegfx::B2DPoint& rNewPosition); +}; + +ImplPageOriginOverlay::ImplPageOriginOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos) +: maPosition(rStartPos) +{ + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + std::unique_ptr<sdr::overlay::OverlayCrosshairStriped> aNew(new sdr::overlay::OverlayCrosshairStriped( + maPosition)); + xTargetOverlay->add(*aNew); + maObjects.append(std::move(aNew)); + } + } +} + +void ImplPageOriginOverlay::SetPosition(const basegfx::B2DPoint& rNewPosition) +{ + if(rNewPosition == maPosition) + return; + + // apply to OverlayObjects + for(sal_uInt32 a(0); a < maObjects.count(); a++) + { + sdr::overlay::OverlayCrosshairStriped* pCandidate = + static_cast< sdr::overlay::OverlayCrosshairStriped* >(&maObjects.getOverlayObject(a)); + + if(pCandidate) + { + pCandidate->setBasePosition(rNewPosition); + } + } + + // remember new position + maPosition = rNewPosition; +} + + +class ImplHelpLineOverlay +{ + // The OverlayObjects + sdr::overlay::OverlayObjectList maObjects; + + // The current position in logical coordinates + basegfx::B2DPoint maPosition; + + // HelpLine specific stuff + SdrPageView* mpPageView; + sal_uInt16 mnHelpLineNumber; + SdrHelpLineKind meHelpLineKind; + +public: + ImplHelpLineOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, + SdrPageView* pPageView, sal_uInt16 nHelpLineNumber, SdrHelpLineKind eKind); + + // The OverlayObjects are cleared using the destructor of OverlayObjectList. + // That destructor calls clear() at the list which removes all objects from the + // OverlayManager and deletes them. + + void SetPosition(const basegfx::B2DPoint& rNewPosition); + + // access to HelpLine specific stuff + SdrPageView* GetPageView() const { return mpPageView; } + sal_uInt16 GetHelpLineNumber() const { return mnHelpLineNumber; } + SdrHelpLineKind GetHelpLineKind() const { return meHelpLineKind; } +}; + +ImplHelpLineOverlay::ImplHelpLineOverlay( + const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, + SdrPageView* pPageView, sal_uInt16 nHelpLineNumber, SdrHelpLineKind eKind) +: maPosition(rStartPos), + mpPageView(pPageView), + mnHelpLineNumber(nHelpLineNumber), + meHelpLineKind(eKind) +{ + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + std::unique_ptr<sdr::overlay::OverlayHelplineStriped> aNew(new sdr::overlay::OverlayHelplineStriped( + maPosition, meHelpLineKind)); + xTargetOverlay->add(*aNew); + maObjects.append(std::move(aNew)); + } + } +} + +void ImplHelpLineOverlay::SetPosition(const basegfx::B2DPoint& rNewPosition) +{ + if(rNewPosition == maPosition) + return; + + // apply to OverlayObjects + for(sal_uInt32 a(0); a < maObjects.count(); a++) + { + sdr::overlay::OverlayHelplineStriped* pCandidate = + static_cast< sdr::overlay::OverlayHelplineStriped* >(&maObjects.getOverlayObject(a)); + + if(pCandidate) + { + pCandidate->setBasePosition(rNewPosition); + } + } + + // remember new position + maPosition = rNewPosition; +} + +SdrSnapView::SdrSnapView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: SdrPaintView(rSdrModel, pOut) + ,mpPageOriginOverlay(nullptr) + ,mpHelpLineOverlay(nullptr) + ,nMagnSizPix(4) + ,nSnapAngle(1500) + ,nEliminatePolyPointLimitAngle(0) + ,eCrookMode(SdrCrookMode::Rotate) + ,bSnapEnab(true) + ,bGridSnap(true) + ,bBordSnap(true) + ,bHlplSnap(true) + ,bOFrmSnap(true) + ,bOPntSnap(false) + ,bOConSnap(true) + ,bMoveSnapOnlyTopLeft(false) + ,bOrtho(false) + ,bBigOrtho(true) + ,bAngleSnapEnab(false) + ,bMoveOnlyDragging(false) + ,bSlantButShear(false) + ,bCrookNoContortion(false) + ,bEliminatePolyPoints(false) +{ +} + +SdrSnapView::~SdrSnapView() +{ + BrkSetPageOrg(); + BrkDragHelpLine(); +} + + +bool SdrSnapView::IsAction() const +{ + return IsSetPageOrg() || IsDragHelpLine() || SdrPaintView::IsAction(); +} + +void SdrSnapView::MovAction(const Point& rPnt) +{ + SdrPaintView::MovAction(rPnt); + if (IsSetPageOrg()) { + MovSetPageOrg(rPnt); + } + if (IsDragHelpLine()) { + MovDragHelpLine(rPnt); + } +} + +void SdrSnapView::EndAction() +{ + if (IsSetPageOrg()) { + EndSetPageOrg(); + } + if (IsDragHelpLine()) { + EndDragHelpLine(); + } + SdrPaintView::EndAction(); +} + +void SdrSnapView::BckAction() +{ + BrkSetPageOrg(); + BrkDragHelpLine(); + SdrPaintView::BckAction(); +} + +void SdrSnapView::BrkAction() +{ + BrkSetPageOrg(); + BrkDragHelpLine(); + SdrPaintView::BrkAction(); +} + +void SdrSnapView::TakeActionRect(tools::Rectangle& rRect) const +{ + if (IsSetPageOrg() || IsDragHelpLine()) { + rRect=tools::Rectangle(maDragStat.GetNow(),maDragStat.GetNow()); + } else { + SdrPaintView::TakeActionRect(rRect); + } +} + + +Point SdrSnapView::GetSnapPos(const Point& rPnt, const SdrPageView* pPV) const +{ + Point aPt(rPnt); + SnapPos(aPt,pPV); + return aPt; +} + +#define NOT_SNAPPED 0x7FFFFFFF +SdrSnap SdrSnapView::SnapPos(Point& rPnt, const SdrPageView* pPV) const +{ + if (!bSnapEnab) return SdrSnap::NOTSNAPPED; + tools::Long x=rPnt.X(); + tools::Long y=rPnt.Y(); + if (pPV==nullptr) { + pPV=GetSdrPageView(); + if (pPV==nullptr) return SdrSnap::NOTSNAPPED; + } + + tools::Long dx=NOT_SNAPPED; + tools::Long dy=NOT_SNAPPED; + tools::Long dx1,dy1; + tools::Long mx=aMagnSiz.Width(); + tools::Long my=aMagnSiz.Height(); + if (mbHlplVisible && bHlplSnap && !IsDragHelpLine()) + { + const SdrHelpLineList& rHLL=pPV->GetHelpLines(); + sal_uInt16 nCount=rHLL.GetCount(); + for (sal_uInt16 i=nCount; i>0;) { + i--; + const SdrHelpLine& rHL=rHLL[i]; + const Point& rPos=rHL.GetPos(); + switch (rHL.GetKind()) { + case SdrHelpLineKind::Vertical: { + tools::Long a=x-rPos.X(); + if (std::abs(a)<=mx) { dx1=-a; if (std::abs(dx1)<std::abs(dx)) dx=dx1; } + } break; + case SdrHelpLineKind::Horizontal: { + tools::Long b=y-rPos.Y(); + if (std::abs(b)<=my) { dy1=-b; if (std::abs(dy1)<std::abs(dy)) dy=dy1; } + } break; + case SdrHelpLineKind::Point: { + tools::Long a=x-rPos.X(); + tools::Long b=y-rPos.Y(); + if (std::abs(a)<=mx && std::abs(b)<=my) { + dx1=-a; dy1=-b; + if (std::abs(dx1)<std::abs(dx) && std::abs(dy1)<std::abs(dy)) { dx=dx1; dy=dy1; } + } + } break; + } // switch + } + } + if (mbBordVisible && bBordSnap) { + SdrPage* pPage=pPV->GetPage(); + tools::Long xs=pPage->GetWidth(); + tools::Long ys=pPage->GetHeight(); + tools::Long lft=pPage->GetLeftBorder(); + tools::Long rgt=pPage->GetRightBorder(); + tools::Long upp=pPage->GetUpperBorder(); + tools::Long lwr=pPage->GetLowerBorder(); + tools::Long a; + a=x- lft ; if (std::abs(a)<=mx) { dx1=-a; if (std::abs(dx1)<std::abs(dx)) dx=dx1; } // left margin + a=x-(xs-rgt); if (std::abs(a)<=mx) { dx1=-a; if (std::abs(dx1)<std::abs(dx)) dx=dx1; } // right margin + a=x ; if (std::abs(a)<=mx) { dx1=-a; if (std::abs(dx1)<std::abs(dx)) dx=dx1; } // left edge of paper + a=x- xs ; if (std::abs(a)<=mx) { dx1=-a; if (std::abs(dx1)<std::abs(dx)) dx=dx1; } // right edge of paper + a=y- upp ; if (std::abs(a)<=my) { dy1=-a; if (std::abs(dy1)<std::abs(dy)) dy=dy1; } // left margin + a=y-(ys-lwr); if (std::abs(a)<=my) { dy1=-a; if (std::abs(dy1)<std::abs(dy)) dy=dy1; } // right margin + a=y ; if (std::abs(a)<=my) { dy1=-a; if (std::abs(dy1)<std::abs(dy)) dy=dy1; } // left edge of paper + a=y- ys ; if (std::abs(a)<=my) { dy1=-a; if (std::abs(dy1)<std::abs(dy)) dy=dy1; } // right edge of paper + } + if (bOFrmSnap || bOPntSnap) { + sal_uInt32 nMaxPointSnapCount=200; + sal_uInt32 nMaxFrameSnapCount=200; + + // go back to SdrIterMode::DeepNoGroups runthrough for snap to object comparisons + SdrObjListIter aIter(pPV->GetPage(),SdrIterMode::DeepNoGroups,true); + + while (aIter.IsMore() && (nMaxPointSnapCount>0 || nMaxFrameSnapCount>0)) { + SdrObject* pO=aIter.Next(); + tools::Rectangle aRect(pO->GetCurrentBoundRect()); + aRect.AdjustLeft( -mx ); + aRect.AdjustRight(mx ); + aRect.AdjustTop( -my ); + aRect.AdjustBottom(my ); + if (aRect.Contains(rPnt)) { + if (bOPntSnap && nMaxPointSnapCount>0) + { + sal_uInt32 nCount(pO->GetSnapPointCount()); + for (sal_uInt32 i(0); i < nCount && nMaxPointSnapCount > 0; i++) + { + Point aP(pO->GetSnapPoint(i)); + dx1=x-aP.X(); + dy1=y-aP.Y(); + if (std::abs(dx1)<=mx && std::abs(dy1)<=my && std::abs(dx1)<std::abs(dx) && std::abs(dy1)<std::abs(dy)) { + dx=-dx1; + dy=-dy1; + } + nMaxPointSnapCount--; + } + } + if (bOFrmSnap && nMaxFrameSnapCount>0) { + tools::Rectangle aLog(pO->GetSnapRect()); + tools::Rectangle aR1(aLog); + aR1.AdjustLeft( -mx ); + aR1.AdjustRight(mx ); + aR1.AdjustTop( -my ); + aR1.AdjustBottom(my ); + if (aR1.Contains(rPnt)) { + if (std::abs(x-aLog.Left ())<=mx) { dx1=-(x-aLog.Left ()); if (std::abs(dx1)<std::abs(dx)) dx=dx1; } + if (std::abs(x-aLog.Right ())<=mx) { dx1=-(x-aLog.Right ()); if (std::abs(dx1)<std::abs(dx)) dx=dx1; } + if (std::abs(y-aLog.Top ())<=my) { dy1=-(y-aLog.Top ()); if (std::abs(dy1)<std::abs(dy)) dy=dy1; } + if (std::abs(y-aLog.Bottom())<=my) { dy1=-(y-aLog.Bottom()); if (std::abs(dy1)<std::abs(dy)) dy=dy1; } + } + nMaxFrameSnapCount--; + } + } + } + } + if(bGridSnap) + { + double fSnapWidth(aSnapWdtX); + if(dx == NOT_SNAPPED && fSnapWidth != 0.0) + { + double fx = static_cast<double>(x); + + // round instead of trunc + if(fx - static_cast<double>(pPV->GetPageOrigin().X()) >= 0.0) + fx += fSnapWidth / 2.0; + else + fx -= fSnapWidth / 2.0; + + x = static_cast<tools::Long>((fx - static_cast<double>(pPV->GetPageOrigin().X())) / fSnapWidth); + x = static_cast<tools::Long>(static_cast<double>(x) * fSnapWidth + static_cast<double>(pPV->GetPageOrigin().X())); + dx = 0; + } + fSnapWidth = double(aSnapWdtY); + if(dy == NOT_SNAPPED && fSnapWidth) + { + double fy = static_cast<double>(y); + + // round instead of trunc + if(fy - static_cast<double>(pPV->GetPageOrigin().Y()) >= 0.0) + fy += fSnapWidth / 2.0; + else + fy -= fSnapWidth / 2.0; + + y = static_cast<tools::Long>((fy - static_cast<double>(pPV->GetPageOrigin().Y())) / fSnapWidth); + y = static_cast<tools::Long>(static_cast<double>(y) * fSnapWidth + static_cast<double>(pPV->GetPageOrigin().Y())); + dy = 0; + } + } + SdrSnap bRet=SdrSnap::NOTSNAPPED; + if (dx==NOT_SNAPPED) dx=0; else bRet|=SdrSnap::XSNAPPED; + if (dy==NOT_SNAPPED) dy=0; else bRet|=SdrSnap::YSNAPPED; + rPnt.setX(x+dx ); + rPnt.setY(y+dy ); + return bRet; +} + +void SdrSnapView::CheckSnap(const Point& rPt, tools::Long& nBestXSnap, tools::Long& nBestYSnap, bool& bXSnapped, bool& bYSnapped) const +{ + Point aPt(rPt); + SdrSnap nRet=SnapPos(aPt,nullptr); + aPt-=rPt; + if (nRet & SdrSnap::XSNAPPED) { + if (bXSnapped) { + if (std::abs(aPt.X())<std::abs(nBestXSnap)) { + nBestXSnap=aPt.X(); + } + } else { + nBestXSnap=aPt.X(); + bXSnapped=true; + } + } + if (nRet & SdrSnap::YSNAPPED) { + if (bYSnapped) { + if (std::abs(aPt.Y())<std::abs(nBestYSnap)) { + nBestYSnap=aPt.Y(); + } + } else { + nBestYSnap=aPt.Y(); + bYSnapped=true; + } + } +} + + +void SdrSnapView::BegSetPageOrg(const Point& rPnt) +{ + BrkAction(); + + DBG_ASSERT(nullptr == mpPageOriginOverlay, "SdrSnapView::BegSetPageOrg: There exists an ImplPageOriginOverlay (!)"); + basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y()); + mpPageOriginOverlay = new ImplPageOriginOverlay(*this, aStartPos); + maDragStat.Reset(GetSnapPos(rPnt,nullptr)); +} + +void SdrSnapView::MovSetPageOrg(const Point& rPnt) +{ + if(IsSetPageOrg()) + { + maDragStat.NextMove(GetSnapPos(rPnt,nullptr)); + DBG_ASSERT(mpPageOriginOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + basegfx::B2DPoint aNewPos(maDragStat.GetNow().X(), maDragStat.GetNow().Y()); + mpPageOriginOverlay->SetPosition(aNewPos); + } +} + +void SdrSnapView::EndSetPageOrg() +{ + if(!IsSetPageOrg()) + return; + + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + Point aPnt(maDragStat.GetNow()); + pPV->SetPageOrigin(aPnt); + } + + // cleanup + BrkSetPageOrg(); +} + +void SdrSnapView::BrkSetPageOrg() +{ + if(IsSetPageOrg()) + { + DBG_ASSERT(mpPageOriginOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)"); + delete mpPageOriginOverlay; + mpPageOriginOverlay = nullptr; + } +} + + +bool SdrSnapView::PickHelpLine(const Point& rPnt, short nTol, const OutputDevice& rOut, sal_uInt16& rnHelpLineNum, SdrPageView*& rpPV) const +{ + rpPV=nullptr; + nTol=ImpGetHitTolLogic(nTol,&rOut); + SdrPageView* pPV = GetSdrPageView(); + + if(pPV) + { + Point aPnt(rPnt); + sal_uInt16 nIndex=pPV->GetHelpLines().HitTest(aPnt,sal_uInt16(nTol),rOut); + if (nIndex!=SDRHELPLINE_NOTFOUND) { + rpPV=pPV; + rnHelpLineNum=nIndex; + return true; + } + } + return false; +} + +// start HelpLine drag for new HelpLine +bool SdrSnapView::BegDragHelpLine(sal_uInt16 nHelpLineNum, SdrPageView* pPV) +{ + bool bRet(false); + + BrkAction(); + + if(pPV && nHelpLineNum < pPV->GetHelpLines().GetCount()) + { + const SdrHelpLineList& rHelpLines = pPV->GetHelpLines(); + const SdrHelpLine& rHelpLine = rHelpLines[nHelpLineNum]; + Point aHelpLinePos = rHelpLine.GetPos(); + basegfx::B2DPoint aStartPos(aHelpLinePos.X(), aHelpLinePos.Y()); + + DBG_ASSERT(nullptr == mpHelpLineOverlay, "SdrSnapView::BegDragHelpLine: There exists an ImplHelpLineOverlay (!)"); + mpHelpLineOverlay = new ImplHelpLineOverlay(*this, aStartPos, pPV, nHelpLineNum, rHelpLine.GetKind()); + + maDragStat.Reset(GetSnapPos(aHelpLinePos, pPV)); + maDragStat.SetMinMove(ImpGetMinMovLogic(-3, nullptr)); + + bRet = true; + } + + return bRet; +} + +// start HelpLine drag with existing HelpLine +void SdrSnapView::BegDragHelpLine(const Point& rPnt, SdrHelpLineKind eNewKind) +{ + BrkAction(); + + if(GetSdrPageView()) + { + DBG_ASSERT(nullptr == mpHelpLineOverlay, "SdrSnapView::BegDragHelpLine: There exists an ImplHelpLineOverlay (!)"); + basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y()); + mpHelpLineOverlay = new ImplHelpLineOverlay(*this, aStartPos, nullptr, 0, eNewKind); + maDragStat.Reset(GetSnapPos(rPnt, nullptr)); + } +} + +PointerStyle SdrSnapView::GetDraggedHelpLinePointer() const +{ + if(IsDragHelpLine()) + { + switch(mpHelpLineOverlay->GetHelpLineKind()) + { + case SdrHelpLineKind::Vertical : return PointerStyle::ESize; + case SdrHelpLineKind::Horizontal: return PointerStyle::SSize; + default : return PointerStyle::Move; + } + } + + return PointerStyle::Move; +} + +void SdrSnapView::MovDragHelpLine(const Point& rPnt) +{ + if(IsDragHelpLine() && maDragStat.CheckMinMoved(rPnt)) + { + Point aPnt(GetSnapPos(rPnt, nullptr)); + + if(aPnt != maDragStat.GetNow()) + { + maDragStat.NextMove(aPnt); + DBG_ASSERT(mpHelpLineOverlay, "SdrSnapView::MovDragHelpLine: no ImplHelpLineOverlay (!)"); + basegfx::B2DPoint aNewPos(maDragStat.GetNow().X(), maDragStat.GetNow().Y()); + mpHelpLineOverlay->SetPosition(aNewPos); + } + } +} + +bool SdrSnapView::EndDragHelpLine() +{ + bool bRet(false); + + if(IsDragHelpLine()) + { + if(maDragStat.IsMinMoved()) + { + SdrPageView* pPageView = mpHelpLineOverlay->GetPageView(); + + if(pPageView) + { + // moved existing one + Point aPnt(maDragStat.GetNow()); + const SdrHelpLineList& rHelpLines = pPageView->GetHelpLines(); + SdrHelpLine aChangedHelpLine = rHelpLines[mpHelpLineOverlay->GetHelpLineNumber()]; + aChangedHelpLine.SetPos(aPnt); + pPageView->SetHelpLine(mpHelpLineOverlay->GetHelpLineNumber(), aChangedHelpLine); + + bRet = true; + } + else + { + // create new one + pPageView = GetSdrPageView(); + + if(pPageView) + { + Point aPnt(maDragStat.GetNow()); + SdrHelpLine aNewHelpLine(mpHelpLineOverlay->GetHelpLineKind(), aPnt); + pPageView->InsertHelpLine(aNewHelpLine); + + bRet = true; + } + } + } + + // cleanup + BrkDragHelpLine(); + } + + return bRet; +} + +void SdrSnapView::BrkDragHelpLine() +{ + if(IsDragHelpLine()) + { + DBG_ASSERT(mpHelpLineOverlay, "SdrSnapView::EndDragHelpLine: no ImplHelpLineOverlay (!)"); + delete mpHelpLineOverlay; + mpHelpLineOverlay = nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdtext.cxx b/svx/source/svdraw/svdtext.cxx new file mode 100644 index 0000000000..0fe4fa47d4 --- /dev/null +++ b/svx/source/svdraw/svdtext.cxx @@ -0,0 +1,132 @@ +/* -*- 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/svdotext.hxx> +#include <svx/svdetc.hxx> +#include <editeng/outlobj.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdmodel.hxx> +#include <svl/itemset.hxx> +#include <osl/diagnose.h> +#include <libxml/xmlwriter.h> +#include <memory> + +SdrText::SdrText( SdrTextObj& rObject ) +: mrObject( rObject ) +, mbPortionInfoChecked( false ) +{ + OSL_ENSURE(&mrObject, "SdrText created without SdrTextObj (!)"); +} + +SdrText::~SdrText() +{ +} + +void SdrText::CheckPortionInfo( const SdrOutliner& rOutliner ) +{ + if(mbPortionInfoChecked) + return; + + // #i102062# no action when the Outliner is the HitTestOutliner, + // this will remove WrongList info at the OPO + if(&rOutliner == &mrObject.getSdrModelFromSdrObject().GetHitTestOutliner()) + return; + + // TODO: optimization: we could create a BigTextObject + mbPortionInfoChecked=true; + + if(mpOutlinerParaObject && rOutliner.ShouldCreateBigTextObject()) + { + // #i102062# MemoryLeak closed + mpOutlinerParaObject = rOutliner.CreateParaObject(); + } +} + +void SdrText::ReformatText() +{ + mbPortionInfoChecked=false; + mpOutlinerParaObject->ClearPortionInfo(); +} + +const SfxItemSet& SdrText::GetItemSet() const +{ + return const_cast< SdrText* >(this)->GetObjectItemSet(); +} + +void SdrText::SetOutlinerParaObject( std::optional<OutlinerParaObject> pTextObject ) +{ + mpOutlinerParaObject = std::move(pTextObject); + mbPortionInfoChecked = false; +} + +OutlinerParaObject* SdrText::GetOutlinerParaObject() +{ + return mpOutlinerParaObject ? &*mpOutlinerParaObject : nullptr; +} + +const OutlinerParaObject* SdrText::GetOutlinerParaObject() const +{ + return mpOutlinerParaObject ? &*mpOutlinerParaObject : nullptr; +} + +/** returns the current OutlinerParaObject and removes it from this instance */ +std::optional<OutlinerParaObject> SdrText::RemoveOutlinerParaObject() +{ + std::optional<OutlinerParaObject> pOPO = std::move(mpOutlinerParaObject); + mbPortionInfoChecked = false; + return pOPO; +} + +void SdrText::ForceOutlinerParaObject( OutlinerMode nOutlMode ) +{ + if(mpOutlinerParaObject) + return; + + std::unique_ptr<Outliner> pOutliner( + SdrMakeOutliner( + nOutlMode, + mrObject.getSdrModelFromSdrObject())); + + if(pOutliner) + { + Outliner& aDrawOutliner(mrObject.getSdrModelFromSdrObject().GetDrawOutliner()); + pOutliner->SetCalcFieldValueHdl( aDrawOutliner.GetCalcFieldValueHdl() ); + pOutliner->SetStyleSheet( 0, GetStyleSheet()); + SetOutlinerParaObject( pOutliner->CreateParaObject() ); + } +} + +const SfxItemSet& SdrText::GetObjectItemSet() +{ + return mrObject.GetObjectItemSet(); +} + +SfxStyleSheet* SdrText::GetStyleSheet() const +{ + return mrObject.GetStyleSheet(); +} + +void SdrText::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrText")); + mpOutlinerParaObject->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdtrans.cxx b/svx/source/svdraw/svdtrans.cxx new file mode 100644 index 0000000000..23c7495ad7 --- /dev/null +++ b/svx/source/svdraw/svdtrans.cxx @@ -0,0 +1,882 @@ +/* -*- 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/svdtrans.hxx> +#include <math.h> +#include <svx/xpoly.hxx> +#include <rtl/ustrbuf.hxx> + +#include <vcl/virdev.hxx> +#include <tools/bigint.hxx> +#include <tools/UnitConversion.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/localedatawrapper.hxx> +#include <sal/log.hxx> + +void MoveXPoly(XPolygon& rPoly, const Size& S) +{ + rPoly.Move(S.Width(),S.Height()); +} + +void ResizeRect(tools::Rectangle& rRect, const Point& rRef, const Fraction& rxFact, const Fraction& ryFact) +{ + Fraction aXFact(rxFact); + Fraction aYFact(ryFact); + + if (!aXFact.IsValid()) { + SAL_WARN( "svx.svdraw", "invalid fraction xFract, using Fraction(1,1)" ); + aXFact = Fraction(1,1); + tools::Long nWdt = rRect.Right() - rRect.Left(); + if (nWdt == 0) rRect.AdjustRight( 1 ); + } + rRect.SetLeft( rRef.X() + FRound( (rRect.Left() - rRef.X()) * double(aXFact) ) ); + rRect.SetRight( rRef.X() + FRound( (rRect.Right() - rRef.X()) * double(aXFact) ) ); + + if (!aYFact.IsValid()) { + SAL_WARN( "svx.svdraw", "invalid fraction yFract, using Fraction(1,1)" ); + aYFact = Fraction(1,1); + tools::Long nHgt = rRect.Bottom() - rRect.Top(); + if (nHgt == 0) rRect.AdjustBottom( 1 ); + } + rRect.SetTop( rRef.Y() + FRound( (rRect.Top() - rRef.Y()) * double(aYFact) ) ); + rRect.SetBottom( rRef.Y() + FRound( (rRect.Bottom() - rRef.Y()) * double(aYFact) ) ); + + rRect.Normalize(); +} + + +void ResizePoly(tools::Polygon& rPoly, const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + sal_uInt16 nCount=rPoly.GetSize(); + for (sal_uInt16 i=0; i<nCount; i++) { + ResizePoint(rPoly[i],rRef,xFact,yFact); + } +} + +void ResizeXPoly(XPolygon& rPoly, const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + sal_uInt16 nCount=rPoly.GetPointCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + ResizePoint(rPoly[i],rRef,xFact,yFact); + } +} + +void RotatePoly(tools::Polygon& rPoly, const Point& rRef, double sn, double cs) +{ + sal_uInt16 nCount=rPoly.GetSize(); + for (sal_uInt16 i=0; i<nCount; i++) { + RotatePoint(rPoly[i],rRef,sn,cs); + } +} + +void RotateXPoly(XPolygon& rPoly, const Point& rRef, double sn, double cs) +{ + sal_uInt16 nCount=rPoly.GetPointCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + RotatePoint(rPoly[i],rRef,sn,cs); + } +} + +void RotateXPoly(XPolyPolygon& rPoly, const Point& rRef, double sn, double cs) +{ + sal_uInt16 nCount=rPoly.Count(); + for (sal_uInt16 i=0; i<nCount; i++) { + RotateXPoly(rPoly[i],rRef,sn,cs); + } +} + +void MirrorPoint(Point& rPnt, const Point& rRef1, const Point& rRef2) +{ + tools::Long mx=rRef2.X()-rRef1.X(); + tools::Long my=rRef2.Y()-rRef1.Y(); + if (mx==0) { // vertical axis + tools::Long dx=rRef1.X()-rPnt.X(); + rPnt.AdjustX(2*dx ); + } else if (my==0) { // horizontal axis + tools::Long dy=rRef1.Y()-rPnt.Y(); + rPnt.AdjustY(2*dy ); + } else if (mx==my) { // diagonal axis '\' + tools::Long dx1=rPnt.X()-rRef1.X(); + tools::Long dy1=rPnt.Y()-rRef1.Y(); + rPnt.setX(rRef1.X()+dy1 ); + rPnt.setY(rRef1.Y()+dx1 ); + } else if (mx==-my) { // diagonal axis '/' + tools::Long dx1=rPnt.X()-rRef1.X(); + tools::Long dy1=rPnt.Y()-rRef1.Y(); + rPnt.setX(rRef1.X()-dy1 ); + rPnt.setY(rRef1.Y()-dx1 ); + } else { // arbitrary axis + // TODO: Optimize this! Raise perpendicular on the mirroring axis..? + Degree100 nRefAngle=GetAngle(rRef2-rRef1); + rPnt-=rRef1; + Degree100 nPntAngle=GetAngle(rPnt); + Degree100 nAngle=2_deg100*(nRefAngle-nPntAngle); + double a = toRadians(nAngle); + double nSin=sin(a); + double nCos=cos(a); + RotatePoint(rPnt,Point(),nSin,nCos); + rPnt+=rRef1; + } +} + +void MirrorXPoly(XPolygon& rPoly, const Point& rRef1, const Point& rRef2) +{ + sal_uInt16 nCount=rPoly.GetPointCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + MirrorPoint(rPoly[i],rRef1,rRef2); + } +} + +void ShearPoly(tools::Polygon& rPoly, const Point& rRef, double tn) +{ + sal_uInt16 nCount=rPoly.GetSize(); + for (sal_uInt16 i=0; i<nCount; i++) { + ShearPoint(rPoly[i],rRef,tn); + } +} + +void ShearXPoly(XPolygon& rPoly, const Point& rRef, double tn, bool bVShear) +{ + sal_uInt16 nCount=rPoly.GetPointCount(); + for (sal_uInt16 i=0; i<nCount; i++) { + ShearPoint(rPoly[i],rRef,tn,bVShear); + } +} + +double CrookRotateXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter, + const Point& rRad, double& rSin, double& rCos, bool bVert) +{ + bool bC1=pC1!=nullptr; + bool bC2=pC2!=nullptr; + tools::Long x0=rPnt.X(); + tools::Long y0=rPnt.Y(); + tools::Long cx=rCenter.X(); + tools::Long cy=rCenter.Y(); + double nAngle=GetCrookAngle(rPnt,rCenter,rRad,bVert); + double sn=sin(nAngle); + double cs=cos(nAngle); + RotatePoint(rPnt,rCenter,sn,cs); + if (bC1) { + if (bVert) { + // move into the direction of the center, as a basic position for the rotation + pC1->AdjustY( -y0 ); + // resize, account for the distance from the center + pC1->setY(FRound(static_cast<double>(pC1->Y()) /rRad.X()*(cx-pC1->X())) ); + pC1->AdjustY(cy ); + } else { + // move into the direction of the center, as a basic position for the rotation + pC1->AdjustX( -x0 ); + // resize, account for the distance from the center + tools::Long nPntRad=cy-pC1->Y(); + double nFact=static_cast<double>(nPntRad)/static_cast<double>(rRad.Y()); + pC1->setX(FRound(static_cast<double>(pC1->X())*nFact) ); + pC1->AdjustX(cx ); + } + RotatePoint(*pC1,rCenter,sn,cs); + } + if (bC2) { + if (bVert) { + // move into the direction of the center, as a basic position for the rotation + pC2->AdjustY( -y0 ); + // resize, account for the distance from the center + pC2->setY(FRound(static_cast<double>(pC2->Y()) /rRad.X()*(rCenter.X()-pC2->X())) ); + pC2->AdjustY(cy ); + } else { + // move into the direction of the center, as a basic position for the rotation + pC2->AdjustX( -x0 ); + // resize, account for the distance from the center + tools::Long nPntRad=rCenter.Y()-pC2->Y(); + double nFact=static_cast<double>(nPntRad)/static_cast<double>(rRad.Y()); + pC2->setX(FRound(static_cast<double>(pC2->X())*nFact) ); + pC2->AdjustX(cx ); + } + RotatePoint(*pC2,rCenter,sn,cs); + } + rSin=sn; + rCos=cs; + return nAngle; +} + +double CrookSlantXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter, + const Point& rRad, double& rSin, double& rCos, bool bVert) +{ + bool bC1=pC1!=nullptr; + bool bC2=pC2!=nullptr; + tools::Long x0=rPnt.X(); + tools::Long y0=rPnt.Y(); + tools::Long dx1=0,dy1=0; + tools::Long dxC1=0,dyC1=0; + tools::Long dxC2=0,dyC2=0; + if (bVert) { + tools::Long nStart=rCenter.X()-rRad.X(); + dx1=rPnt.X()-nStart; + rPnt.setX(nStart ); + if (bC1) { + dxC1=pC1->X()-nStart; + pC1->setX(nStart ); + } + if (bC2) { + dxC2=pC2->X()-nStart; + pC2->setX(nStart ); + } + } else { + tools::Long nStart=rCenter.Y()-rRad.Y(); + dy1=rPnt.Y()-nStart; + rPnt.setY(nStart ); + if (bC1) { + dyC1=pC1->Y()-nStart; + pC1->setY(nStart ); + } + if (bC2) { + dyC2=pC2->Y()-nStart; + pC2->setY(nStart ); + } + } + double nAngle=GetCrookAngle(rPnt,rCenter,rRad,bVert); + double sn=sin(nAngle); + double cs=cos(nAngle); + RotatePoint(rPnt,rCenter,sn,cs); + if (bC1) { if (bVert) pC1->AdjustY( -(y0-rCenter.Y()) ); else pC1->AdjustX( -(x0-rCenter.X()) ); RotatePoint(*pC1,rCenter,sn,cs); } + if (bC2) { if (bVert) pC2->AdjustY( -(y0-rCenter.Y()) ); else pC2->AdjustX( -(x0-rCenter.X()) ); RotatePoint(*pC2,rCenter,sn,cs); } + if (bVert) { + rPnt.AdjustX(dx1 ); + if (bC1) pC1->AdjustX(dxC1 ); + if (bC2) pC2->AdjustX(dxC2 ); + } else { + rPnt.AdjustY(dy1 ); + if (bC1) pC1->AdjustY(dyC1 ); + if (bC2) pC2->AdjustY(dyC2 ); + } + rSin=sn; + rCos=cs; + return nAngle; +} + +double CrookStretchXPoint(Point& rPnt, Point* pC1, Point* pC2, const Point& rCenter, + const Point& rRad, double& rSin, double& rCos, bool bVert, + const tools::Rectangle& rRefRect) +{ + tools::Long y0=rPnt.Y(); + CrookSlantXPoint(rPnt,pC1,pC2,rCenter,rRad,rSin,rCos,bVert); + if (bVert) { + } else { + tools::Long nTop=rRefRect.Top(); + tools::Long nBtm=rRefRect.Bottom(); + tools::Long nHgt=nBtm-nTop; + tools::Long dy=rPnt.Y()-y0; + double a=static_cast<double>(y0-nTop)/nHgt; + a*=dy; + rPnt.setY(y0+FRound(a) ); + } + return 0.0; +} + + +void CrookRotatePoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert) +{ + double nSin,nCos; + sal_uInt16 nPointCnt=rPoly.GetPointCount(); + sal_uInt16 i=0; + while (i<nPointCnt) { + Point* pPnt=&rPoly[i]; + Point* pC1=nullptr; + Point* pC2=nullptr; + if (i+1<nPointCnt && rPoly.IsControl(i)) { // control point to the left + pC1=pPnt; + i++; + pPnt=&rPoly[i]; + } + i++; + if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right + pC2=&rPoly[i]; + i++; + } + CrookRotateXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert); + } +} + +void CrookSlantPoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert) +{ + double nSin,nCos; + sal_uInt16 nPointCnt=rPoly.GetPointCount(); + sal_uInt16 i=0; + while (i<nPointCnt) { + Point* pPnt=&rPoly[i]; + Point* pC1=nullptr; + Point* pC2=nullptr; + if (i+1<nPointCnt && rPoly.IsControl(i)) { // control point to the left + pC1=pPnt; + i++; + pPnt=&rPoly[i]; + } + i++; + if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right + pC2=&rPoly[i]; + i++; + } + CrookSlantXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert); + } +} + +void CrookStretchPoly(XPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert, const tools::Rectangle& rRefRect) +{ + double nSin,nCos; + sal_uInt16 nPointCnt=rPoly.GetPointCount(); + sal_uInt16 i=0; + while (i<nPointCnt) { + Point* pPnt=&rPoly[i]; + Point* pC1=nullptr; + Point* pC2=nullptr; + if (i+1<nPointCnt && rPoly.IsControl(i)) { // control point to the left + pC1=pPnt; + i++; + pPnt=&rPoly[i]; + } + i++; + if (i<nPointCnt && rPoly.IsControl(i)) { // control point to the right + pC2=&rPoly[i]; + i++; + } + CrookStretchXPoint(*pPnt,pC1,pC2,rCenter,rRad,nSin,nCos,bVert,rRefRect); + } +} + + +void CrookRotatePoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert) +{ + sal_uInt16 nPolyCount=rPoly.Count(); + for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) { + CrookRotatePoly(rPoly[nPolyNum],rCenter,rRad,bVert); + } +} + +void CrookSlantPoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert) +{ + sal_uInt16 nPolyCount=rPoly.Count(); + for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) { + CrookSlantPoly(rPoly[nPolyNum],rCenter,rRad,bVert); + } +} + +void CrookStretchPoly(XPolyPolygon& rPoly, const Point& rCenter, const Point& rRad, bool bVert, const tools::Rectangle& rRefRect) +{ + sal_uInt16 nPolyCount=rPoly.Count(); + for (sal_uInt16 nPolyNum=0; nPolyNum<nPolyCount; nPolyNum++) { + CrookStretchPoly(rPoly[nPolyNum],rCenter,rRad,bVert,rRefRect); + } +} + + +Degree100 GetAngle(const Point& rPnt) +{ + Degree100 a; + if (rPnt.Y()==0) { + if (rPnt.X()<0) a=-18000_deg100; + } else if (rPnt.X()==0) { + if (rPnt.Y()>0) a=-9000_deg100; + else a=9000_deg100; + } else { + a = Degree100(FRound(basegfx::rad2deg<100>(atan2(static_cast<double>(-rPnt.Y()), static_cast<double>(rPnt.X()))))); + } + return a; +} + +Degree100 NormAngle18000(Degree100 a) +{ + while (a<-18000_deg100) a+=36000_deg100; + while (a>=18000_deg100) a-=36000_deg100; + return a; +} + +Degree100 NormAngle36000(Degree100 a) +{ + a %= 36000_deg100; + if (a < 0_deg100) + a += 36000_deg100; + return a; +} + +sal_uInt16 GetAngleSector(Degree100 nAngle) { return (NormAngle36000(nAngle) / 9000_deg100).get(); } + +tools::Long GetLen(const Point& rPnt) +{ + tools::Long x=std::abs(rPnt.X()); + tools::Long y=std::abs(rPnt.Y()); + if (x+y<0x8000) { // because 7FFF * 7FFF * 2 = 7FFE0002 + x*=x; + y*=y; + x+=y; + x=FRound(sqrt(static_cast<double>(x))); + return x; + } else { + double nx=x; + double ny=y; + nx*=nx; + ny*=ny; + nx+=ny; + nx=sqrt(nx); + if (nx>0x7FFFFFFF) { + return 0x7FFFFFFF; // we can't go any further, for fear of an overrun! + } else { + return FRound(nx); + } + } +} + + +void GeoStat::RecalcSinCos() +{ + if (m_nRotationAngle==0_deg100) { + mfSinRotationAngle=0.0; + mfCosRotationAngle=1.0; + } else { + double a = toRadians(m_nRotationAngle); + mfSinRotationAngle=sin(a); + mfCosRotationAngle=cos(a); + } +} + +void GeoStat::RecalcTan() +{ + if (m_nShearAngle==0_deg100) { + mfTanShearAngle=0.0; + } else { + double a = toRadians(m_nShearAngle); + mfTanShearAngle=tan(a); + } +} + + +tools::Polygon Rect2Poly(const tools::Rectangle& rRect, const GeoStat& rGeo) +{ + tools::Polygon aPol(5); + aPol[0]=rRect.TopLeft(); + aPol[1]=rRect.TopRight(); + aPol[2]=rRect.BottomRight(); + aPol[3]=rRect.BottomLeft(); + aPol[4]=rRect.TopLeft(); + if (rGeo.m_nShearAngle) ShearPoly(aPol,rRect.TopLeft(),rGeo.mfTanShearAngle); + if (rGeo.m_nRotationAngle) RotatePoly(aPol,rRect.TopLeft(),rGeo.mfSinRotationAngle,rGeo.mfCosRotationAngle); + return aPol; +} + +namespace svx +{ +tools::Rectangle polygonToRectangle(const tools::Polygon& rPolygon, GeoStat& rGeo) +{ + rGeo.m_nRotationAngle = GetAngle(rPolygon[1] - rPolygon[0]); + rGeo.m_nRotationAngle = NormAngle36000(rGeo.m_nRotationAngle); + + // rotation successful + rGeo.RecalcSinCos(); + + Point aPoint1(rPolygon[1] - rPolygon[0]); + if (rGeo.m_nRotationAngle) + RotatePoint(aPoint1, Point(0,0), -rGeo.mfSinRotationAngle, rGeo.mfCosRotationAngle); // -Sin to reverse rotation + tools::Long nWidth = aPoint1.X(); + + Point aPoint0(rPolygon[0]); + Point aPoint3(rPolygon[3] - rPolygon[0]); + if (rGeo.m_nRotationAngle) + RotatePoint(aPoint3, Point(0,0), -rGeo.mfSinRotationAngle, rGeo.mfCosRotationAngle); // -Sin to reverse rotation + tools::Long nHeight = aPoint3.Y(); + + Degree100 nShearAngle = GetAngle(aPoint3); + nShearAngle -= 27000_deg100; // ShearWink is measured against a vertical line + nShearAngle = -nShearAngle; // negating, because '+' is shearing clock-wise + + bool bMirror = aPoint3.Y() < 0; + if (bMirror) + { // "exchange of points" when mirroring + nHeight = -nHeight; + nShearAngle += 18000_deg100; + aPoint0 = rPolygon[3]; + } + + nShearAngle = NormAngle18000(nShearAngle); + if (nShearAngle < -9000_deg100 || nShearAngle > 9000_deg100) + { + nShearAngle = NormAngle18000(nShearAngle + 18000_deg100); + } + + if (nShearAngle < -SDRMAXSHEAR) + nShearAngle = -SDRMAXSHEAR; // limit ShearWinkel (shear angle) to +/- 89.00 deg + + if (nShearAngle > SDRMAXSHEAR) + nShearAngle = SDRMAXSHEAR; + + rGeo.m_nShearAngle = nShearAngle; + rGeo.RecalcTan(); + + Point aRU(aPoint0); + aRU.AdjustX(nWidth); + aRU.AdjustY(nHeight); + + return tools::Rectangle(aPoint0, aRU); +} + +} // end svx + +void OrthoDistance8(const Point& rPt0, Point& rPt, bool bBigOrtho) +{ + tools::Long dx=rPt.X()-rPt0.X(); + tools::Long dy=rPt.Y()-rPt0.Y(); + tools::Long dxa=std::abs(dx); + tools::Long dya=std::abs(dy); + if (dx==0 || dy==0 || dxa==dya) return; + if (dxa>=dya*2) { rPt.setY(rPt0.Y() ); return; } + if (dya>=dxa*2) { rPt.setX(rPt0.X() ); return; } + if ((dxa<dya) != bBigOrtho) { + rPt.setY(rPt0.Y()+(dxa* (dy>=0 ? 1 : -1) ) ); + } else { + rPt.setX(rPt0.X()+(dya* (dx>=0 ? 1 : -1) ) ); + } +} + +void OrthoDistance4(const Point& rPt0, Point& rPt, bool bBigOrtho) +{ + tools::Long dx=rPt.X()-rPt0.X(); + tools::Long dy=rPt.Y()-rPt0.Y(); + tools::Long dxa=std::abs(dx); + tools::Long dya=std::abs(dy); + if ((dxa<dya) != bBigOrtho) { + rPt.setY(rPt0.Y()+(dxa* (dy>=0 ? 1 : -1) ) ); + } else { + rPt.setX(rPt0.X()+(dya* (dx>=0 ? 1 : -1) ) ); + } +} + + +tools::Long BigMulDiv(tools::Long nVal, tools::Long nMul, tools::Long nDiv) +{ + if (!nDiv) + return 0x7fffffff; + return BigInt::Scale(nVal, nMul, nDiv); +} + +static FrPair toPair(o3tl::Length eFrom, o3tl::Length eTo) +{ + const auto& [nNum, nDen] = o3tl::getConversionMulDiv(eFrom, eTo); + return FrPair(nNum, nDen); +} + +// How many eU units fit into a mm, respectively an inch? +// Or: How many mm, respectively inches, are there in an eU (and then give me the inverse) + +static FrPair GetInchOrMM(MapUnit eU) +{ + switch (eU) { + case MapUnit::Map1000thInch: return toPair(o3tl::Length::in, o3tl::Length::in1000); + case MapUnit::Map100thInch : return toPair(o3tl::Length::in, o3tl::Length::in100); + case MapUnit::Map10thInch : return toPair(o3tl::Length::in, o3tl::Length::in10); + case MapUnit::MapInch : return toPair(o3tl::Length::in, o3tl::Length::in); + case MapUnit::MapPoint : return toPair(o3tl::Length::in, o3tl::Length::pt); + case MapUnit::MapTwip : return toPair(o3tl::Length::in, o3tl::Length::twip); + case MapUnit::Map100thMM : return toPair(o3tl::Length::mm, o3tl::Length::mm100); + case MapUnit::Map10thMM : return toPair(o3tl::Length::mm, o3tl::Length::mm10); + case MapUnit::MapMM : return toPair(o3tl::Length::mm, o3tl::Length::mm); + case MapUnit::MapCM : return toPair(o3tl::Length::mm, o3tl::Length::cm); + case MapUnit::MapPixel : { + ScopedVclPtrInstance< VirtualDevice > pVD; + pVD->SetMapMode(MapMode(MapUnit::Map100thMM)); + Point aP(pVD->PixelToLogic(Point(64,64))); // 64 pixels for more accuracy + return FrPair(6400,aP.X(),6400,aP.Y()); + } + case MapUnit::MapAppFont: case MapUnit::MapSysFont: { + ScopedVclPtrInstance< VirtualDevice > pVD; + pVD->SetMapMode(MapMode(eU)); + Point aP(pVD->LogicToPixel(Point(32,32))); // 32 units for more accuracy + pVD->SetMapMode(MapMode(MapUnit::Map100thMM)); + aP=pVD->PixelToLogic(aP); + return FrPair(3200,aP.X(),3200,aP.Y()); + } + default: break; + } + return Fraction(1,1); +} + +// Calculate the factor that we need to convert units from eS to eD. +// e. g. GetMapFactor(UNIT_MM,UNIT_100TH_MM) => 100. + +FrPair GetMapFactor(MapUnit eS, MapUnit eD) +{ + if (eS==eD) return FrPair(1,1,1,1); + const auto eFrom = MapToO3tlLength(eS, o3tl::Length::invalid); + const auto eTo = MapToO3tlLength(eD, o3tl::Length::invalid); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + return toPair(eFrom, eTo); + FrPair aS(GetInchOrMM(eS)); + FrPair aD(GetInchOrMM(eD)); + bool bSInch=IsInch(eS); + bool bDInch=IsInch(eD); + FrPair aRet(aD.X()/aS.X(),aD.Y()/aS.Y()); + if (bSInch && !bDInch) { aRet.X()*=Fraction(127,5); aRet.Y()*=Fraction(127,5); } + if (!bSInch && bDInch) { aRet.X()*=Fraction(5,127); aRet.Y()*=Fraction(5,127); } + return aRet; +}; + +FrPair GetMapFactor(FieldUnit eS, FieldUnit eD) +{ + if (eS==eD) return FrPair(1,1,1,1); + auto eFrom = FieldToO3tlLength(eS), eTo = FieldToO3tlLength(eD); + if (eFrom == o3tl::Length::invalid) + { + if (eTo == o3tl::Length::invalid) + return FrPair(1,1,1,1); + eFrom = IsInch(eD) ? o3tl::Length::in : o3tl::Length::mm; + } + else if (eTo == o3tl::Length::invalid) + eTo = IsInch(eS) ? o3tl::Length::in : o3tl::Length::mm; + return toPair(eFrom, eTo); +}; + +void SdrFormatter::Undirty() +{ + const o3tl::Length eFrom = MapToO3tlLength(m_eSrcMU, o3tl::Length::invalid); + const o3tl::Length eTo = MapToO3tlLength(m_eDstMU, o3tl::Length::invalid); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + { + const auto& [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo); + sal_Int64 nMul = mul; + sal_Int64 nDiv = div; + short nComma = 0; + + // shorten trailing zeros for dividend + while (0 == (nMul % 10)) + { + nComma--; + nMul /= 10; + } + + // shorten trailing zeros for divisor + while (0 == (nDiv % 10)) + { + nComma++; + nDiv /= 10; + } + m_nMul = nMul; + m_nDiv = nDiv; + m_nComma = nComma; + } + else + { + m_nMul = m_nDiv = 1; + m_nComma = 0; + } + m_bDirty=false; +} + + +OUString SdrFormatter::GetStr(tools::Long nVal) const +{ + static constexpr OUString aNullCode(u"0"_ustr); + + if(!nVal) + { + return aNullCode; + } + + // we may lose some decimal places here, because of MulDiv instead of Real + bool bNeg(nVal < 0); + SvtSysLocale aSysLoc; + const LocaleDataWrapper& rLoc = aSysLoc.GetLocaleData(); + + if (m_bDirty) + const_cast<SdrFormatter*>(this)->Undirty(); + + sal_Int16 nC(m_nComma); + + if(bNeg) + nVal = -nVal; + + while(nC <= -3) + { + nVal *= 1000; + nC += 3; + } + + while(nC <= -1) + { + nVal *= 10; + nC++; + } + + if(m_nMul != m_nDiv) + nVal = BigMulDiv(nVal, m_nMul, m_nDiv); + + OUStringBuffer aStr = OUString::number(nVal); + + if(nC > 0 && aStr.getLength() <= nC ) + { + // decimal separator necessary + sal_Int32 nCount(nC - aStr.getLength()); + + if(nCount >= 0 && LocaleDataWrapper::isNumLeadingZero()) + nCount++; + + for(sal_Int32 i=0; i<nCount; i++) + aStr.insert(0, aNullCode); + + // remove superfluous decimal points + sal_Int32 nNumDigits(LocaleDataWrapper::getNumDigits()); + sal_Int32 nWeg(nC - nNumDigits); + + if(nWeg > 0) + { + // TODO: we should round here + aStr.remove(aStr.getLength() - nWeg, nWeg); + nC = nNumDigits; + } + } + + // remember everything before the decimal separator for later + sal_Int32 nForComma(aStr.getLength() - nC); + + if(nC > 0) + { + // insert comma char (decimal separator) + // remove trailing zeros + while(nC > 0 && aStr[aStr.getLength() - 1] == aNullCode.getStr()[0]) + { + aStr.remove(aStr.getLength() - 1, 1); + nC--; + } + + if(nC > 0) + { + // do we still have decimal places? + sal_Unicode cDec(rLoc.getNumDecimalSep()[0]); + aStr.insert(nForComma, cDec); + } + } + + // add in thousands separator (if necessary) + if( nForComma > 3 ) + { + const OUString& aThoSep( rLoc.getNumThousandSep() ); + if ( aThoSep.getLength() > 0 ) + { + sal_Unicode cTho( aThoSep[0] ); + sal_Int32 i(nForComma - 3); + + while(i > 0) + { + aStr.insert(i, cTho); + i -= 3; + } + } + } + + if(aStr.isEmpty()) + aStr.append(aNullCode); + + if(bNeg && (aStr.getLength() > 1 || aStr[0] != aNullCode.getStr()[0])) + { + aStr.insert(0, "-"); + } + + return aStr.makeStringAndClear(); +} + +OUString SdrFormatter::GetUnitStr(MapUnit eUnit) +{ + switch(eUnit) + { + // metrically + case MapUnit::Map100thMM : + return "/100mm"; + case MapUnit::Map10thMM : + return "/10mm"; + case MapUnit::MapMM : + return "mm"; + case MapUnit::MapCM : + return "cm"; + + // Inch + case MapUnit::Map1000thInch: + return "/1000\""; + case MapUnit::Map100thInch : + return "/100\""; + case MapUnit::Map10thInch : + return "/10\""; + case MapUnit::MapInch : + return "\""; + case MapUnit::MapPoint : + return "pt"; + case MapUnit::MapTwip : + return "twip"; + + // others + case MapUnit::MapPixel : + return "pixel"; + case MapUnit::MapSysFont : + return "sysfont"; + case MapUnit::MapAppFont : + return "appfont"; + case MapUnit::MapRelative : + return "%"; + default: + return OUString(); + } +} + +OUString SdrFormatter::GetUnitStr(FieldUnit eUnit) +{ + switch(eUnit) + { + default : + case FieldUnit::NONE : + case FieldUnit::CUSTOM : + return OUString(); + + // metrically + case FieldUnit::MM_100TH: + return "/100mm"; + case FieldUnit::MM : + return "mm"; + case FieldUnit::CM : + return "cm"; + case FieldUnit::M : + return "m"; + case FieldUnit::KM : + return "km"; + + // Inch + case FieldUnit::TWIP : + return "twip"; + case FieldUnit::POINT : + return "pt"; + case FieldUnit::PICA : + return "pica"; + case FieldUnit::INCH : + return "\""; + case FieldUnit::FOOT : + return "ft"; + case FieldUnit::MILE : + return "mile(s)"; + + // others + case FieldUnit::PERCENT: + return "%"; + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdundo.cxx b/svx/source/svdraw/svdundo.cxx new file mode 100644 index 0000000000..0dcfede6e0 --- /dev/null +++ b/svx/source/svdraw/svdundo.cxx @@ -0,0 +1,1789 @@ +/* -*- 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 <com/sun/star/drawing/FillStyle.hpp> + +#include <svx/svdundo.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdview.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xfillit0.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/scene3d.hxx> +#include <editeng/outlobj.hxx> +#include <svx/svdogrp.hxx> +#include <sdr/properties/itemsettools.hxx> +#include <svx/sdr/properties/properties.hxx> +#include <svx/svdocapt.hxx> +#include <svl/whiter.hxx> +#include <svx/e3dsceneupdater.hxx> +#include <svx/svdviter.hxx> +#include <svx/svdotable.hxx> // #i124389# +#include <utility> +#include <vcl/svapp.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/svdoashp.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svx/diagram/datamodel.hxx> +#include <svx/diagram/IDiagramHelper.hxx> + + +// iterates over all views and unmarks this SdrObject if it is marked +static void ImplUnmarkObject( SdrObject* pObj ) +{ + SdrViewIter::ForAllViews( pObj, + [&pObj] (SdrView* pView) + { + pView->MarkObj( pObj, pView->GetSdrPageView(), true ); + }); +} + +SdrUndoAction::SdrUndoAction(SdrModel& rNewMod) + : rMod(rNewMod), m_nViewShellId(-1) +{ + if (SfxViewShell* pViewShell = SfxViewShell::Current()) + m_nViewShellId = pViewShell->GetViewShellId(); +} + +SdrUndoAction::~SdrUndoAction() {} + +bool SdrUndoAction::CanRepeat(SfxRepeatTarget& rView) const +{ + SdrView* pV=dynamic_cast<SdrView*>( &rView ); + if (pV!=nullptr) return CanSdrRepeat(*pV); + return false; +} + +void SdrUndoAction::Repeat(SfxRepeatTarget& rView) +{ + SdrView* pV=dynamic_cast<SdrView*>( &rView ); + if (pV!=nullptr) SdrRepeat(*pV); + DBG_ASSERT(pV!=nullptr,"Repeat: SfxRepeatTarget that was handed over is not a SdrView"); +} + +OUString SdrUndoAction::GetRepeatComment(SfxRepeatTarget& rView) const +{ + SdrView* pV=dynamic_cast<SdrView*>( &rView ); + if (pV!=nullptr) return GetSdrRepeatComment(); + return OUString(); +} + +bool SdrUndoAction::CanSdrRepeat(SdrView& /*rView*/) const +{ + return false; +} + +void SdrUndoAction::SdrRepeat(SdrView& /*rView*/) +{ +} + +OUString SdrUndoAction::GetSdrRepeatComment() const +{ + return OUString(); +} + +ViewShellId SdrUndoAction::GetViewShellId() const +{ + return m_nViewShellId; +} + +SdrUndoGroup::SdrUndoGroup(SdrModel& rNewMod) +: SdrUndoAction(rNewMod), + eFunction(SdrRepeatFunc::NONE) +{} + +SdrUndoGroup::~SdrUndoGroup() +{ +} + +void SdrUndoGroup::AddAction(std::unique_ptr<SdrUndoAction> pAct) +{ + maActions.push_back(std::move(pAct)); +} + +void SdrUndoGroup::Undo() +{ + for (auto it = maActions.rbegin(); it != maActions.rend(); ++it) + (*it)->Undo(); +} + +void SdrUndoGroup::Redo() +{ + for (std::unique_ptr<SdrUndoAction> & pAction : maActions) + pAction->Redo(); +} + +OUString SdrUndoGroup::GetComment() const +{ + return aComment.replaceAll("%1", aObjDescription); +} + +bool SdrUndoGroup::CanSdrRepeat(SdrView& rView) const +{ + switch (eFunction) + { + case SdrRepeatFunc::NONE : return false; + case SdrRepeatFunc::Delete : return rView.AreObjectsMarked(); + case SdrRepeatFunc::CombinePolyPoly: return rView.IsCombinePossible(); + case SdrRepeatFunc::CombineOnePoly : return rView.IsCombinePossible(true); + case SdrRepeatFunc::DismantlePolys : return rView.IsDismantlePossible(); + case SdrRepeatFunc::DismantleLines : return rView.IsDismantlePossible(true); + case SdrRepeatFunc::ConvertToPoly : return rView.IsConvertToPolyObjPossible(); + case SdrRepeatFunc::ConvertToPath : return rView.IsConvertToPathObjPossible(); + case SdrRepeatFunc::Group : return rView.IsGroupPossible(); + case SdrRepeatFunc::Ungroup : return rView.IsUnGroupPossible(); + case SdrRepeatFunc::PutToTop : return rView.IsToTopPossible(); + case SdrRepeatFunc::PutToBottom : return rView.IsToBtmPossible(); + case SdrRepeatFunc::MoveToTop : return rView.IsToTopPossible(); + case SdrRepeatFunc::MoveToBottom : return rView.IsToBtmPossible(); + case SdrRepeatFunc::ReverseOrder : return rView.IsReverseOrderPossible(); + case SdrRepeatFunc::ImportMtf : return rView.IsImportMtfPossible(); + default: break; + } // switch + return false; +} + +void SdrUndoGroup::SdrRepeat(SdrView& rView) +{ + switch (eFunction) + { + case SdrRepeatFunc::NONE : break; + case SdrRepeatFunc::Delete : rView.DeleteMarked(); break; + case SdrRepeatFunc::CombinePolyPoly : rView.CombineMarkedObjects(false); break; + case SdrRepeatFunc::CombineOnePoly : rView.CombineMarkedObjects(); break; + case SdrRepeatFunc::DismantlePolys : rView.DismantleMarkedObjects(); break; + case SdrRepeatFunc::DismantleLines : rView.DismantleMarkedObjects(true); break; + case SdrRepeatFunc::ConvertToPoly : rView.ConvertMarkedToPolyObj(); break; + case SdrRepeatFunc::ConvertToPath : rView.ConvertMarkedToPathObj(false); break; + case SdrRepeatFunc::Group : rView.GroupMarked(); break; + case SdrRepeatFunc::Ungroup : rView.UnGroupMarked(); break; + case SdrRepeatFunc::PutToTop : rView.PutMarkedToTop(); break; + case SdrRepeatFunc::PutToBottom : rView.PutMarkedToBtm(); break; + case SdrRepeatFunc::MoveToTop : rView.MovMarkedToTop(); break; + case SdrRepeatFunc::MoveToBottom : rView.MovMarkedToBtm(); break; + case SdrRepeatFunc::ReverseOrder : rView.ReverseOrderOfMarked(); break; + case SdrRepeatFunc::ImportMtf : rView.DoImportMarkedMtf(); break; + default: break; + } // switch +} + +OUString SdrUndoGroup::GetSdrRepeatComment() const +{ + return aComment.replaceAll("%1", SvxResId(STR_ObjNameSingulPlural)); +} + +SdrUndoObj::SdrUndoObj(SdrObject& rNewObj) +: SdrUndoAction(rNewObj.getSdrModelFromSdrObject()) + ,mxObj(&rNewObj) +{ +} + +SdrUndoObj::~SdrUndoObj() {} + +OUString SdrUndoObj::GetDescriptionStringForObject( const SdrObject& _rForObject, TranslateId pStrCacheID, bool bRepeat ) +{ + const OUString rStr {SvxResId(pStrCacheID)}; + + const sal_Int32 nPos = rStr.indexOf("%1"); + if (nPos < 0) + return rStr; + + if (bRepeat) + return rStr.replaceAt(nPos, 2, SvxResId(STR_ObjNameSingulPlural)); + + return rStr.replaceAt(nPos, 2, _rForObject.TakeObjNameSingul()); +} + +OUString SdrUndoObj::ImpGetDescriptionStr(TranslateId pStrCacheID, bool bRepeat) const +{ + if ( mxObj ) + return GetDescriptionStringForObject( *mxObj, pStrCacheID, bRepeat ); + return OUString(); +} + +// common call method for possible change of the page when UNDO/REDO is triggered +void SdrUndoObj::ImpShowPageOfThisObject() +{ + if(mxObj && mxObj->IsInserted() && mxObj->getSdrPageFromSdrObject()) + { + SdrHint aHint(SdrHintKind::SwitchToPage, *mxObj, mxObj->getSdrPageFromSdrObject()); + mxObj->getSdrModelFromSdrObject().Broadcast(aHint); + } +} + +void SdrUndoAttrObj::ensureStyleSheetInStyleSheetPool(SfxStyleSheetBasePool& rStyleSheetPool, SfxStyleSheet& rSheet) +{ + SfxStyleSheetBase* pThere = rStyleSheetPool.Find(rSheet.GetName(), rSheet.GetFamily()); + + if(!pThere) + { + // re-insert remembered style which was removed in the meantime. To do this + // without assertion, do it without parent and set parent after insertion + const OUString aParent(rSheet.GetParent()); + + rSheet.SetParent(OUString()); + rStyleSheetPool.Insert(&rSheet); + rSheet.SetParent(aParent); + } +} + +SdrUndoAttrObj::SdrUndoAttrObj(SdrObject& rNewObj, bool bStyleSheet1, bool bSaveText) + : SdrUndoObj(rNewObj) + , bHaveToTakeRedoSet(true) +{ + bStyleSheet = bStyleSheet1; + + SdrObjList* pOL = rNewObj.GetSubList(); + bool bIsGroup(pOL!=nullptr && pOL->GetObjCount()); + bool bIs3DScene(bIsGroup && DynCastE3dScene(mxObj.get())); + + if(bIsGroup) + { + // it's a group object! + pUndoGroup.reset(new SdrUndoGroup(mxObj->getSdrModelFromSdrObject())); + + for (const rtl::Reference<SdrObject>& pObj : *pOL) + { + pUndoGroup->AddAction( + std::make_unique<SdrUndoAttrObj>(*pObj, bStyleSheet1)); + } + } + + if(bIsGroup && !bIs3DScene) + return; + + moUndoSet.emplace( mxObj->GetMergedItemSet() ); + + if(bStyleSheet) + mxUndoStyleSheet = mxObj->GetStyleSheet(); + + if(bSaveText) + { + auto p = mxObj->GetOutlinerParaObject(); + if(p) + pTextUndo = *p; + } +} + +SdrUndoAttrObj::~SdrUndoAttrObj() +{ + moUndoSet.reset(); + moRedoSet.reset(); + pUndoGroup.reset(); + pTextUndo.reset(); + pTextRedo.reset(); +} + +void SdrUndoAttrObj::Undo() +{ + E3DModifySceneSnapRectUpdater aUpdater(mxObj.get()); + bool bIs3DScene(DynCastE3dScene(mxObj.get())); + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + if(!pUndoGroup || bIs3DScene) + { + if(bHaveToTakeRedoSet) + { + bHaveToTakeRedoSet = false; + + moRedoSet.emplace( mxObj->GetMergedItemSet() ); + + if(bStyleSheet) + mxRedoStyleSheet = mxObj->GetStyleSheet(); + + if(pTextUndo) + { + // #i8508# + auto p = mxObj->GetOutlinerParaObject(); + if(p) + pTextRedo = *p; + } + } + + if(bStyleSheet) + { + mxRedoStyleSheet = mxObj->GetStyleSheet(); + SfxStyleSheet* pSheet = mxUndoStyleSheet.get(); + + if(pSheet && mxObj->getSdrModelFromSdrObject().GetStyleSheetPool()) + { + ensureStyleSheetInStyleSheetPool(*mxObj->getSdrModelFromSdrObject().GetStyleSheetPool(), *pSheet); + mxObj->SetStyleSheet(pSheet, true); + } + else + { + OSL_ENSURE(false, "OOps, something went wrong in SdrUndoAttrObj (!)"); + } + } + + sdr::properties::ItemChangeBroadcaster aItemChange(*mxObj); + + // Since ClearItem sets back everything to normal + // it also sets fit-to-size text to non-fit-to-size text and + // switches on autogrowheight (the default). That may lead to + // losing the geometry size info for the object when it is + // laid out again from AdjustTextFrameWidthAndHeight(). This makes + // rescuing the size of the object necessary. + const tools::Rectangle aSnapRect = mxObj->GetSnapRect(); + // SdrObjCustomShape::NbcSetSnapRect needs logic instead of snap rect + const tools::Rectangle aLogicRect = mxObj->GetLogicRect(); + + if(moUndoSet) + { + if(dynamic_cast<const SdrCaptionObj*>( mxObj.get() ) != nullptr) + { + // do a more smooth item deletion here, else the text + // rect will be reformatted, especially when information regarding + // vertical text is changed. When clearing only set items it's + // slower, but safer regarding such information (it's not changed + // usually) + SfxWhichIter aIter(*moUndoSet); + sal_uInt16 nWhich(aIter.FirstWhich()); + + while(nWhich) + { + if(SfxItemState::SET != aIter.GetItemState(false)) + { + mxObj->ClearMergedItem(nWhich); + } + + nWhich = aIter.NextWhich(); + } + } + else + { + mxObj->ClearMergedItem(); + } + + mxObj->SetMergedItemSet(*moUndoSet); + } + + // Restore previous size here when it was changed. + if(aSnapRect != mxObj->GetSnapRect()) + { + if(dynamic_cast<const SdrObjCustomShape*>(mxObj.get())) + mxObj->NbcSetSnapRect(aLogicRect); + else + mxObj->NbcSetSnapRect(aSnapRect); + } + + mxObj->GetProperties().BroadcastItemChange(aItemChange); + + if(pTextUndo) + { + mxObj->SetOutlinerParaObject(*pTextUndo); + } + } + + if(pUndoGroup) + { + pUndoGroup->Undo(); + } +} + +void SdrUndoAttrObj::Redo() +{ + E3DModifySceneSnapRectUpdater aUpdater(mxObj.get()); + bool bIs3DScene(DynCastE3dScene(mxObj.get())); + + if(!pUndoGroup || bIs3DScene) + { + if(bStyleSheet) + { + mxUndoStyleSheet = mxObj->GetStyleSheet(); + SfxStyleSheet* pSheet = mxRedoStyleSheet.get(); + + if(pSheet && mxObj->getSdrModelFromSdrObject().GetStyleSheetPool()) + { + ensureStyleSheetInStyleSheetPool(*mxObj->getSdrModelFromSdrObject().GetStyleSheetPool(), *pSheet); + mxObj->SetStyleSheet(pSheet, true); + } + else + { + OSL_ENSURE(false, "OOps, something went wrong in SdrUndoAttrObj (!)"); + } + } + + sdr::properties::ItemChangeBroadcaster aItemChange(*mxObj); + + const tools::Rectangle aSnapRect = mxObj->GetSnapRect(); + const tools::Rectangle aLogicRect = mxObj->GetLogicRect(); + + if(moRedoSet) + { + if(dynamic_cast<const SdrCaptionObj*>( mxObj.get() ) != nullptr) + { + // do a more smooth item deletion here, else the text + // rect will be reformatted, especially when information regarding + // vertical text is changed. When clearing only set items it's + // slower, but safer regarding such information (it's not changed + // usually) + SfxWhichIter aIter(*moRedoSet); + sal_uInt16 nWhich(aIter.FirstWhich()); + + while(nWhich) + { + if(SfxItemState::SET != aIter.GetItemState(false)) + { + mxObj->ClearMergedItem(nWhich); + } + + nWhich = aIter.NextWhich(); + } + } + else + { + mxObj->ClearMergedItem(); + } + + mxObj->SetMergedItemSet(*moRedoSet); + } + + // Restore previous size here when it was changed. + if(aSnapRect != mxObj->GetSnapRect()) + { + if(dynamic_cast<const SdrObjCustomShape*>(mxObj.get())) + mxObj->NbcSetSnapRect(aLogicRect); + else + mxObj->NbcSetSnapRect(aSnapRect); + } + + mxObj->GetProperties().BroadcastItemChange(aItemChange); + + // #i8508# + if(pTextRedo) + { + mxObj->SetOutlinerParaObject(*pTextRedo); + } + } + + if(pUndoGroup) + { + pUndoGroup->Redo(); + } + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +OUString SdrUndoAttrObj::GetComment() const +{ + if(bStyleSheet) + { + return ImpGetDescriptionStr(STR_EditSetStylesheet); + } + else + { + return ImpGetDescriptionStr(STR_EditSetAttributes); + } +} + +OUString SdrUndoAttrObj::GetSdrRepeatComment() const +{ + if(bStyleSheet) + { + return ImpGetDescriptionStr(STR_EditSetStylesheet, true); + } + else + { + return ImpGetDescriptionStr(STR_EditSetAttributes, true); + } +} + + +SdrUndoMoveObj::~SdrUndoMoveObj() {} + +void SdrUndoMoveObj::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + mxObj->Move(Size(-aDistance.Width(),-aDistance.Height())); +} + +void SdrUndoMoveObj::Redo() +{ + mxObj->Move(Size(aDistance.Width(),aDistance.Height())); + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +OUString SdrUndoMoveObj::GetComment() const +{ + return ImpGetDescriptionStr(STR_EditMove); +} + +void SdrUndoMoveObj::SdrRepeat(SdrView& rView) +{ + rView.MoveMarkedObj(aDistance); +} + +bool SdrUndoMoveObj::CanSdrRepeat(SdrView& rView) const +{ + return rView.AreObjectsMarked(); +} + +OUString SdrUndoMoveObj::GetSdrRepeatComment() const +{ + return ImpGetDescriptionStr(STR_EditMove,true); +} + + +SdrUndoGeoObj::SdrUndoGeoObj(SdrObject& rNewObj) + : SdrUndoObj(rNewObj) + , mbSkipChangeLayout(false) +{ + SdrObjList* pOL=rNewObj.GetSubList(); + if (pOL!=nullptr && pOL->GetObjCount() && !DynCastE3dScene(&rNewObj)) + { + // this is a group object! + // If this were 3D scene, we'd only add an Undo for the scene itself + // (which we do elsewhere). + pUndoGroup.reset(new SdrUndoGroup(mxObj->getSdrModelFromSdrObject())); + for (const rtl::Reference<SdrObject>& pObj : *pOL) + pUndoGroup->AddAction(std::make_unique<SdrUndoGeoObj>(*pObj)); + } + else + { + pUndoGeo = mxObj->GetGeoData(); + } +} + +SdrUndoGeoObj::~SdrUndoGeoObj() +{ + pUndoGeo.reset(); + pRedoGeo.reset(); + pUndoGroup.reset(); +} + +void SdrUndoGeoObj::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + if(pUndoGroup) + { + pUndoGroup->Undo(); + + // only repaint, no objectchange + mxObj->ActionChanged(); + } + else + { + pRedoGeo = mxObj->GetGeoData(); + + auto pTableObj = dynamic_cast<sdr::table::SdrTableObj*>(mxObj.get()); + if (pTableObj && mbSkipChangeLayout) + pTableObj->SetSkipChangeLayout(true); + mxObj->SetGeoData(*pUndoGeo); + if (pTableObj && mbSkipChangeLayout) + pTableObj->SetSkipChangeLayout(false); + } +} + +void SdrUndoGeoObj::Redo() +{ + if(pUndoGroup) + { + pUndoGroup->Redo(); + + // only repaint, no objectchange + mxObj->ActionChanged(); + } + else + { + pUndoGeo = mxObj->GetGeoData(); + mxObj->SetGeoData(*pRedoGeo); + } + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +OUString SdrUndoGeoObj::GetComment() const +{ + return ImpGetDescriptionStr(STR_DragMethObjOwn); +} + +SdrUndoDiagramModelData::SdrUndoDiagramModelData(SdrObject& rNewObj, svx::diagram::DiagramDataStatePtr& rStartState) +: SdrUndoObj(rNewObj) +, m_aStartState(rStartState) +, m_aEndState() +{ + if(rNewObj.isDiagram()) + m_aEndState = rNewObj.getDiagramHelper()->extractDiagramDataState(); +} + +SdrUndoDiagramModelData::~SdrUndoDiagramModelData() +{ +} + +void SdrUndoDiagramModelData::implUndoRedo(bool bUndo) +{ + if(!mxObj) + return; + + if(!mxObj->isDiagram()) + return; + + mxObj->getDiagramHelper()->applyDiagramDataState( + bUndo ? m_aStartState : m_aEndState); + mxObj->getDiagramHelper()->reLayout(*static_cast<SdrObjGroup*>(mxObj.get())); +} + +void SdrUndoDiagramModelData::Undo() +{ + implUndoRedo(true); +} + +void SdrUndoDiagramModelData::Redo() +{ + implUndoRedo(false); +} + +OUString SdrUndoDiagramModelData::GetComment() const +{ + return ImpGetDescriptionStr(STR_DiagramModelDataChange); +} + +SdrUndoObjList::SdrUndoObjList(SdrObject& rNewObj, bool bOrdNumDirect) + : SdrUndoObj(rNewObj) +{ + pObjList=mxObj->getParentSdrObjListFromSdrObject(); + if (bOrdNumDirect) + { + nOrdNum=mxObj->GetOrdNumDirect(); + } + else + { + nOrdNum=mxObj->GetOrdNum(); + } +} + +SdrUndoObjList::~SdrUndoObjList() +{ +} + +void SdrUndoRemoveObj::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + DBG_ASSERT(!mxObj->IsInserted(),"UndoRemoveObj: mxObj has already been inserted."); + if (mxObj->IsInserted()) + return; + + // #i11426# + // For UNDOs in Calc/Writer it is necessary to adapt the anchor + // position of the target object. + Point aOwnerAnchorPos(0, 0); + + if (dynamic_cast< const SdrObjGroup* >(pObjList->getSdrObjectFromSdrObjList()) != nullptr) + { + aOwnerAnchorPos = pObjList->getSdrObjectFromSdrObjList()->GetAnchorPos(); + } + + E3DModifySceneSnapRectUpdater aUpdater(pObjList->getSdrObjectFromSdrObjList()); + pObjList->InsertObject(mxObj.get(), nOrdNum); + + // #i11426# + if(aOwnerAnchorPos.X() || aOwnerAnchorPos.Y()) + { + mxObj->NbcSetAnchorPos(aOwnerAnchorPos); + } +} + +void SdrUndoRemoveObj::Redo() +{ + DBG_ASSERT(mxObj->IsInserted(),"RedoRemoveObj: mxObj is not inserted."); + if (mxObj->IsInserted()) + { + ImplUnmarkObject( mxObj.get() ); + E3DModifySceneSnapRectUpdater aUpdater(mxObj.get()); + pObjList->RemoveObject(mxObj->GetOrdNum()); + } + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +SdrUndoRemoveObj::~SdrUndoRemoveObj() +{ +} + + +void SdrUndoInsertObj::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + DBG_ASSERT(mxObj->IsInserted(),"UndoInsertObj: mxObj is not inserted."); + if (mxObj->IsInserted()) + { + ImplUnmarkObject( mxObj.get() ); + + rtl::Reference<SdrObject> pChkObj= pObjList->RemoveObject(mxObj->GetOrdNum()); + DBG_ASSERT(pChkObj.get()==mxObj.get(),"UndoInsertObj: RemoveObjNum!=mxObj"); + } +} + +void SdrUndoInsertObj::Redo() +{ + DBG_ASSERT(!mxObj->IsInserted(),"RedoInsertObj: mxObj is already inserted"); + if (!mxObj->IsInserted()) + { + // Restore anchor position of an object, + // which becomes a member of a group, because its cleared in method + // <InsertObject(..)>. Needed for correct Redo in Writer. (#i45952#) + Point aAnchorPos( 0, 0 ); + + if (dynamic_cast<const SdrObjGroup*>(pObjList->getSdrObjectFromSdrObjList()) != nullptr) + { + aAnchorPos = mxObj->GetAnchorPos(); + } + + pObjList->InsertObject(mxObj.get(), nOrdNum); + + // Arcs lose position when grouped (#i45952#) + if ( aAnchorPos.X() || aAnchorPos.Y() ) + { + mxObj->NbcSetAnchorPos( aAnchorPos ); + } + } + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +SdrUndoDelObj::SdrUndoDelObj(SdrObject& rNewObj, bool bOrdNumDirect) +: SdrUndoRemoveObj(rNewObj,bOrdNumDirect) +{ +} + +void SdrUndoDelObj::Undo() +{ + SdrUndoRemoveObj::Undo(); +} + +void SdrUndoDelObj::Redo() +{ + SdrUndoRemoveObj::Redo(); +} + +OUString SdrUndoDelObj::GetComment() const +{ + return ImpGetDescriptionStr(STR_EditDelete); +} + +void SdrUndoDelObj::SdrRepeat(SdrView& rView) +{ + rView.DeleteMarked(); +} + +bool SdrUndoDelObj::CanSdrRepeat(SdrView& rView) const +{ + return rView.AreObjectsMarked(); +} + +OUString SdrUndoDelObj::GetSdrRepeatComment() const +{ + return ImpGetDescriptionStr(STR_EditDelete,true); +} + + +void SdrUndoNewObj::Undo() +{ + SdrUndoInsertObj::Undo(); +} + +void SdrUndoNewObj::Redo() +{ + SdrUndoInsertObj::Redo(); +} + +OUString SdrUndoNewObj::GetComment( const SdrObject& _rForObject ) +{ + return GetDescriptionStringForObject( _rForObject, STR_UndoInsertObj ); +} + +OUString SdrUndoNewObj::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoInsertObj); +} + +SdrUndoReplaceObj::SdrUndoReplaceObj(SdrObject& rOldObj1, SdrObject& rNewObj1) + : SdrUndoObj(rOldObj1) + , mxNewObj(&rNewObj1) +{ + pObjList=mxObj->getParentSdrObjListFromSdrObject(); +} + +SdrUndoReplaceObj::~SdrUndoReplaceObj() +{ +} + +void SdrUndoReplaceObj::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + DBG_ASSERT(!mxObj->IsInserted(),"SdrUndoReplaceObj::Undo(): Old object is already inserted!"); + DBG_ASSERT(mxNewObj->IsInserted(),"SdrUndoReplaceObj::Undo(): New object is not inserted!"); + + ImplUnmarkObject( mxNewObj.get() ); + pObjList->ReplaceObject(mxObj.get(), mxNewObj->GetOrdNum()); +} + +void SdrUndoReplaceObj::Redo() +{ + ImplUnmarkObject( mxObj.get() ); + pObjList->ReplaceObject(mxNewObj.get(), mxObj->GetOrdNum()); + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + + +OUString SdrUndoCopyObj::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoCopyObj); +} + + +// #i11702# + +SdrUndoObjectLayerChange::SdrUndoObjectLayerChange(SdrObject& rObj, SdrLayerID aOldLayer, SdrLayerID aNewLayer) + : SdrUndoObj(rObj) + , maOldLayer(aOldLayer) + , maNewLayer(aNewLayer) +{ +} + +void SdrUndoObjectLayerChange::Undo() +{ + ImpShowPageOfThisObject(); + mxObj->SetLayer(maOldLayer); +} + +void SdrUndoObjectLayerChange::Redo() +{ + mxObj->SetLayer(maNewLayer); + ImpShowPageOfThisObject(); +} + + +SdrUndoObjOrdNum::SdrUndoObjOrdNum(SdrObject& rNewObj, sal_uInt32 nOldOrdNum1, sal_uInt32 nNewOrdNum1) + : SdrUndoObj(rNewObj) + , nOldOrdNum(nOldOrdNum1) + , nNewOrdNum(nNewOrdNum1) +{ +} + +void SdrUndoObjOrdNum::Undo() +{ + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + SdrObjList* pOL=mxObj->getParentSdrObjListFromSdrObject(); + if (pOL==nullptr) + { + OSL_FAIL("UndoObjOrdNum: mxObj does not have an ObjList."); + return; + } + pOL->SetObjectOrdNum(nNewOrdNum,nOldOrdNum); +} + +void SdrUndoObjOrdNum::Redo() +{ + SdrObjList* pOL=mxObj->getParentSdrObjListFromSdrObject(); + if (pOL==nullptr) + { + OSL_FAIL("RedoObjOrdNum: mxObj does not have an ObjList."); + return; + } + pOL->SetObjectOrdNum(nOldOrdNum,nNewOrdNum); + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +OUString SdrUndoObjOrdNum::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoObjOrdNum); +} + +SdrUndoSort::SdrUndoSort(const SdrPage & rPage, + ::std::vector<sal_Int32> const& rSortOrder) + : SdrUndoAction(rPage.getSdrModelFromSdrPage()) + , m_OldSortOrder(rSortOrder.size()) + , m_NewSortOrder(rSortOrder) + , m_nPage(rPage.GetPageNum()) +{ + // invert order + for (size_t i = 0; i < rSortOrder.size(); ++i) + { + m_OldSortOrder[rSortOrder[i]] = i; + } +} + +void SdrUndoSort::Do(::std::vector<sal_Int32> & rSortOrder) +{ + SdrPage & rPage(*rMod.GetPage(m_nPage)); + if (rPage.GetObjCount() != rSortOrder.size()) + { + // can probably happen with sw's cursed SdrVirtObj mess - no good solution for that + SAL_WARN("svx", "SdrUndoSort size mismatch"); + return; + } + + // hopefully this can't throw + rPage.sort(rSortOrder); +} + +void SdrUndoSort::Undo() +{ + Do(m_OldSortOrder); +} + +void SdrUndoSort::Redo() +{ + Do(m_NewSortOrder); +} + +OUString SdrUndoSort::GetComment() const +{ + return SvxResId(STR_SortShapes); +} + +SdrUndoObjSetText::SdrUndoObjSetText(SdrObject& rNewObj, sal_Int32 nText) + : SdrUndoObj(rNewObj) + , bNewTextAvailable(false) + , bEmptyPresObj(false) + , mnText(nText) +{ + SdrText* pText = static_cast< SdrTextObj*>( &rNewObj )->getText(mnText); + if( pText && pText->GetOutlinerParaObject() ) + pOldText = *pText->GetOutlinerParaObject(); + + bEmptyPresObj = rNewObj.IsEmptyPresObj(); +} + +SdrUndoObjSetText::~SdrUndoObjSetText() +{ + pOldText.reset(); + pNewText.reset(); +} + +bool SdrUndoObjSetText::IsDifferent() const +{ + if (!pOldText || !pNewText) + return pOldText || pNewText; + return *pOldText != *pNewText; +} + +void SdrUndoObjSetText::AfterSetText() +{ + if (!bNewTextAvailable) + { + SdrText* pText = static_cast< SdrTextObj*>( mxObj.get() )->getText(mnText); + if( pText && pText->GetOutlinerParaObject() ) + pNewText = *pText->GetOutlinerParaObject(); + bNewTextAvailable=true; + } +} + +void SdrUndoObjSetText::Undo() +{ + // only works with SdrTextObj + SdrTextObj* pTarget = DynCastSdrTextObj(mxObj.get()); + + if(!pTarget) + { + OSL_ENSURE(false, "SdrUndoObjSetText::Undo with SdrObject not based on SdrTextObj (!)"); + return; + } + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); + + // save old text for Redo + if(!bNewTextAvailable) + { + AfterSetText(); + } + + SdrText* pText = pTarget->getText(mnText); + if (pText) + { + // copy text for Undo, because the original now belongs to SetOutlinerParaObject() + pTarget->NbcSetOutlinerParaObjectForText(pOldText, pText); + } + + pTarget->SetEmptyPresObj(bEmptyPresObj); + pTarget->ActionChanged(); + + // #i124389# if it's a table, also need to relayout TextFrame + if(dynamic_cast< sdr::table::SdrTableObj* >(pTarget) != nullptr) + { + pTarget->NbcAdjustTextFrameWidthAndHeight(); + } + + // #i122410# SetOutlinerParaObject at SdrText does not trigger a + // BroadcastObjectChange, but it is needed to make evtl. SlideSorters + // update their preview. + pTarget->BroadcastObjectChange(); +} + +void SdrUndoObjSetText::Redo() +{ + // only works with SdrTextObj + SdrTextObj* pTarget = DynCastSdrTextObj(mxObj.get()); + + if(!pTarget) + { + OSL_ENSURE(false, "SdrUndoObjSetText::Redo with SdrObject not based on SdrTextObj (!)"); + return; + } + + SdrText* pText = pTarget->getText(mnText); + if (pText) + { + // copy text for Undo, because the original now belongs to SetOutlinerParaObject() + pTarget->NbcSetOutlinerParaObjectForText( pNewText, pText ); + } + + pTarget->ActionChanged(); + + // #i124389# if it's a table, also need to relayout TextFrame + if(dynamic_cast< sdr::table::SdrTableObj* >(pTarget) != nullptr) + { + pTarget->NbcAdjustTextFrameWidthAndHeight(); + } + + // #i122410# NbcSetOutlinerParaObjectForText at SdrTextObj does not trigger a + // BroadcastObjectChange, but it is needed to make evtl. SlideSorters + // update their preview. + pTarget->BroadcastObjectChange(); + + // Trigger PageChangeCall + ImpShowPageOfThisObject(); +} + +OUString SdrUndoObjSetText::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoObjSetText); +} + +OUString SdrUndoObjSetText::GetSdrRepeatComment() const +{ + return ImpGetDescriptionStr(STR_UndoObjSetText); +} + +void SdrUndoObjSetText::SdrRepeat(SdrView& rView) +{ + if (!(bNewTextAvailable && rView.AreObjectsMarked())) + return; + + const SdrMarkList& rML=rView.GetMarkedObjectList(); + + const bool bUndo = rView.IsUndoEnabled(); + if( bUndo ) + { + OUString aStr = ImpGetDescriptionStr(STR_UndoObjSetText); + rView.BegUndo(aStr); + } + + const size_t nCount=rML.GetMarkCount(); + for (size_t nm=0; nm<nCount; ++nm) + { + SdrObject* pObj2=rML.GetMark(nm)->GetMarkedSdrObj(); + SdrTextObj* pTextObj=DynCastSdrTextObj( pObj2 ); + if (pTextObj!=nullptr) + { + if( bUndo ) + rView.AddUndo(std::make_unique<SdrUndoObjSetText>(*pTextObj,0)); + + pTextObj->SetOutlinerParaObject(pNewText); + } + } + + if( bUndo ) + rView.EndUndo(); +} + +bool SdrUndoObjSetText::CanSdrRepeat(SdrView& rView) const +{ + bool bOk = false; + if (bNewTextAvailable && rView.AreObjectsMarked()) { + bOk=true; + } + return bOk; +} + +// Undo/Redo for setting object's name (#i73249#) +SdrUndoObjStrAttr::SdrUndoObjStrAttr( SdrObject& rNewObj, + const ObjStrAttrType eObjStrAttr, + OUString sOldStr, + OUString sNewStr) + : SdrUndoObj( rNewObj ) + , meObjStrAttr( eObjStrAttr ) + , msOldStr(std::move( sOldStr )) + , msNewStr(std::move( sNewStr )) +{ +} + +void SdrUndoObjStrAttr::Undo() +{ + ImpShowPageOfThisObject(); + + switch ( meObjStrAttr ) + { + case ObjStrAttrType::Name: + mxObj->SetName( msOldStr ); + break; + case ObjStrAttrType::Title: + mxObj->SetTitle( msOldStr ); + break; + case ObjStrAttrType::Description: + mxObj->SetDescription( msOldStr ); + break; + } +} + +void SdrUndoObjStrAttr::Redo() +{ + switch ( meObjStrAttr ) + { + case ObjStrAttrType::Name: + mxObj->SetName( msNewStr ); + break; + case ObjStrAttrType::Title: + mxObj->SetTitle( msNewStr ); + break; + case ObjStrAttrType::Description: + mxObj->SetDescription( msNewStr ); + break; + } + + ImpShowPageOfThisObject(); +} + +OUString SdrUndoObjStrAttr::GetComment() const +{ + OUString aStr; + switch ( meObjStrAttr ) + { + case ObjStrAttrType::Name: + aStr = ImpGetDescriptionStr( STR_UndoObjName) + + " '" + msNewStr + "'"; + break; + case ObjStrAttrType::Title: + aStr = ImpGetDescriptionStr( STR_UndoObjTitle ); + break; + case ObjStrAttrType::Description: + aStr = ImpGetDescriptionStr( STR_UndoObjDescription ); + break; + } + + return aStr; +} + +SdrUndoObjDecorative::SdrUndoObjDecorative(SdrObject & rObj, bool const WasDecorative) + : SdrUndoObj(rObj) + , m_WasDecorative(WasDecorative) +{ +} + +void SdrUndoObjDecorative::Undo() +{ + ImpShowPageOfThisObject(); + + mxObj->SetDecorative(m_WasDecorative); +} + +void SdrUndoObjDecorative::Redo() +{ + mxObj->SetDecorative(!m_WasDecorative); + + ImpShowPageOfThisObject(); +} + +OUString SdrUndoObjDecorative::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoObjDecorative); +} + + +SdrUndoLayer::SdrUndoLayer(sal_uInt16 nLayerNum, SdrLayerAdmin& rNewLayerAdmin, SdrModel& rNewModel) + : SdrUndoAction(rNewModel) + , pLayer(rNewLayerAdmin.GetLayer(nLayerNum)) + , pLayerAdmin(&rNewLayerAdmin) + , nNum(nLayerNum) + , bItsMine(false) +{ +} + +SdrUndoLayer::~SdrUndoLayer() +{ + if (bItsMine) + { + delete pLayer; + } +} + +void SdrUndoNewLayer::Undo() +{ + DBG_ASSERT(!bItsMine,"SdrUndoNewLayer::Undo(): Layer already belongs to UndoAction."); + bItsMine=true; + // coverity[leaked_storage] - owned by this SdrUndoNewLayer as pLayer + SdrLayer* pCmpLayer = pLayerAdmin->RemoveLayer(nNum).release(); + assert(pCmpLayer == pLayer && "SdrUndoNewLayer::Undo(): Removed layer is != pLayer."); (void)pCmpLayer; +} + +void SdrUndoNewLayer::Redo() +{ + DBG_ASSERT(bItsMine,"SdrUndoNewLayer::Undo(): Layer does not belong to UndoAction."); + bItsMine=false; + pLayerAdmin->InsertLayer(std::unique_ptr<SdrLayer>(pLayer),nNum); +} + +OUString SdrUndoNewLayer::GetComment() const +{ + return SvxResId(STR_UndoNewLayer); +} + + +void SdrUndoDelLayer::Undo() +{ + DBG_ASSERT(bItsMine,"SdrUndoDelLayer::Undo(): Layer does not belong to UndoAction."); + bItsMine=false; + pLayerAdmin->InsertLayer(std::unique_ptr<SdrLayer>(pLayer),nNum); +} + +void SdrUndoDelLayer::Redo() +{ + DBG_ASSERT(!bItsMine,"SdrUndoDelLayer::Undo(): Layer already belongs to UndoAction."); + bItsMine=true; + // coverity[leaked_storage] - owned by this SdrUndoNewLayer as pLayer + SdrLayer* pCmpLayer= pLayerAdmin->RemoveLayer(nNum).release(); + assert(pCmpLayer == pLayer && "SdrUndoDelLayer::Redo(): Removed layer is != pLayer."); (void)pCmpLayer; +} + +OUString SdrUndoDelLayer::GetComment() const +{ + return SvxResId(STR_UndoDelLayer); +} + + +SdrUndoPage::SdrUndoPage(SdrPage& rNewPg) +: SdrUndoAction(rNewPg.getSdrModelFromSdrPage()) + ,mxPage(&rNewPg) +{ +} + +SdrUndoPage::~SdrUndoPage() {} + +void SdrUndoPage::ImpInsertPage(sal_uInt16 nNum) +{ + DBG_ASSERT(!mxPage->IsInserted(),"SdrUndoPage::ImpInsertPage(): mxPage is already inserted."); + if (!mxPage->IsInserted()) + { + if (mxPage->IsMasterPage()) + { + rMod.InsertMasterPage(mxPage.get(), nNum); + } + else + { + rMod.InsertPage(mxPage.get(), nNum); + } + } +} + +void SdrUndoPage::ImpRemovePage(sal_uInt16 nNum) +{ + DBG_ASSERT(mxPage->IsInserted(),"SdrUndoPage::ImpRemovePage(): mxPage is not inserted."); + if (!mxPage->IsInserted()) + return; + + rtl::Reference<SdrPage> pChkPg; + if (mxPage->IsMasterPage()) + { + pChkPg = rMod.RemoveMasterPage(nNum); + } + else + { + pChkPg = rMod.RemovePage(nNum); + } + DBG_ASSERT(pChkPg==mxPage,"SdrUndoPage::ImpRemovePage(): RemovePage!=mxPage"); +} + +void SdrUndoPage::ImpMovePage(sal_uInt16 nOldNum, sal_uInt16 nNewNum) +{ + DBG_ASSERT(mxPage->IsInserted(),"SdrUndoPage::ImpMovePage(): mxPage is not inserted."); + if (mxPage->IsInserted()) + { + if (mxPage->IsMasterPage()) + { + rMod.MoveMasterPage(nOldNum,nNewNum); + } + else + { + rMod.MovePage(nOldNum,nNewNum); + } + } +} + +OUString SdrUndoPage::ImpGetDescriptionStr(TranslateId pStrCacheID) +{ + return SvxResId(pStrCacheID); +} + + +SdrUndoPageList::SdrUndoPageList(SdrPage& rNewPg) + : SdrUndoPage(rNewPg) +{ + nPageNum=rNewPg.GetPageNum(); +} + +SdrUndoPageList::~SdrUndoPageList() +{ +} + + +SdrUndoDelPage::SdrUndoDelPage(SdrPage& rNewPg) + : SdrUndoPageList(rNewPg) + , mbHasFillBitmap(false) +{ + // keep fill bitmap separately to remove it from pool if not used elsewhere + if (mxPage->IsMasterPage()) + { + SfxStyleSheet* const pStyleSheet = mxPage->getSdrPageProperties().GetStyleSheet(); + if (pStyleSheet) + queryFillBitmap(pStyleSheet->GetItemSet()); + } + else + { + queryFillBitmap(mxPage->getSdrPageProperties().GetItemSet()); + } + if (bool(mpFillBitmapItem)) + clearFillBitmap(); + + // now remember the master page relationships + if(!mxPage->IsMasterPage()) + return; + + sal_uInt16 nPageCnt(rMod.GetPageCount()); + + for(sal_uInt16 nPageNum2(0); nPageNum2 < nPageCnt; nPageNum2++) + { + SdrPage* pDrawPage = rMod.GetPage(nPageNum2); + + if(pDrawPage->TRG_HasMasterPage()) + { + SdrPage& rMasterPage = pDrawPage->TRG_GetMasterPage(); + + if(mxPage.get() == &rMasterPage) + { + if(!pUndoGroup) + { + pUndoGroup.reset( new SdrUndoGroup(rMod) ); + } + + pUndoGroup->AddAction(rMod.GetSdrUndoFactory().CreateUndoPageRemoveMasterPage(*pDrawPage)); + } + } + } +} + +SdrUndoDelPage::~SdrUndoDelPage() +{ +} + +void SdrUndoDelPage::Undo() +{ + if (bool(mpFillBitmapItem)) + restoreFillBitmap(); + ImpInsertPage(nPageNum); + if (pUndoGroup!=nullptr) + { + // recover master page relationships + pUndoGroup->Undo(); + } +} + +void SdrUndoDelPage::Redo() +{ + ImpRemovePage(nPageNum); + if (bool(mpFillBitmapItem)) + clearFillBitmap(); +} + +OUString SdrUndoDelPage::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoDelPage); +} + +OUString SdrUndoDelPage::GetSdrRepeatComment() const +{ + return ImpGetDescriptionStr(STR_UndoDelPage); +} + +void SdrUndoDelPage::SdrRepeat(SdrView& /*rView*/) +{ +} + +bool SdrUndoDelPage::CanSdrRepeat(SdrView& /*rView*/) const +{ + return false; +} + +void SdrUndoDelPage::queryFillBitmap(const SfxItemSet& rItemSet) +{ + if (const XFillBitmapItem *pItem = rItemSet.GetItemIfSet(XATTR_FILLBITMAP, false)) + mpFillBitmapItem.reset(pItem->Clone()); + if (const XFillStyleItem *pItem = rItemSet.GetItemIfSet(XATTR_FILLSTYLE, false)) + mbHasFillBitmap = pItem->GetValue() == css::drawing::FillStyle_BITMAP; +} + +void SdrUndoDelPage::clearFillBitmap() +{ + if (mxPage->IsMasterPage()) + { + SfxStyleSheet* const pStyleSheet = mxPage->getSdrPageProperties().GetStyleSheet(); + assert(bool(pStyleSheet)); // who took away my stylesheet? + if (pStyleSheet->GetListenerCount() == 1) + { + SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + rItemSet.ClearItem(XATTR_FILLBITMAP); + if (mbHasFillBitmap) + rItemSet.ClearItem(XATTR_FILLSTYLE); + } + } + else + { + SdrPageProperties &rPageProps = mxPage->getSdrPageProperties(); + rPageProps.ClearItem(XATTR_FILLBITMAP); + if (mbHasFillBitmap) + rPageProps.ClearItem(XATTR_FILLSTYLE); + } +} + +void SdrUndoDelPage::restoreFillBitmap() +{ + if (mxPage->IsMasterPage()) + { + SfxStyleSheet* const pStyleSheet = mxPage->getSdrPageProperties().GetStyleSheet(); + assert(bool(pStyleSheet)); // who took away my stylesheet? + if (pStyleSheet->GetListenerCount() == 1) + { + SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + rItemSet.Put(*mpFillBitmapItem); + if (mbHasFillBitmap) + rItemSet.Put(XFillStyleItem(css::drawing::FillStyle_BITMAP)); + } + } + else + { + SdrPageProperties &rPageProps = mxPage->getSdrPageProperties(); + rPageProps.PutItem(*mpFillBitmapItem); + if (mbHasFillBitmap) + rPageProps.PutItem(XFillStyleItem(css::drawing::FillStyle_BITMAP)); + } +} + + +void SdrUndoNewPage::Undo() +{ + ImpRemovePage(nPageNum); +} + +void SdrUndoNewPage::Redo() +{ + ImpInsertPage(nPageNum); +} + +OUString SdrUndoNewPage::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoNewPage); +} + + +OUString SdrUndoCopyPage::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoCopPage); +} + +OUString SdrUndoCopyPage::GetSdrRepeatComment() const +{ + return ImpGetDescriptionStr(STR_UndoCopPage); +} + +void SdrUndoCopyPage::SdrRepeat(SdrView& /*rView*/) +{ + +} + +bool SdrUndoCopyPage::CanSdrRepeat(SdrView& /*rView*/) const +{ + return false; +} + + +void SdrUndoSetPageNum::Undo() +{ + ImpMovePage(nNewPageNum,nOldPageNum); +} + +void SdrUndoSetPageNum::Redo() +{ + ImpMovePage(nOldPageNum,nNewPageNum); +} + +OUString SdrUndoSetPageNum::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoMovPage); +} + +SdrUndoPageMasterPage::SdrUndoPageMasterPage(SdrPage& rChangedPage) + : SdrUndoPage(rChangedPage) + , mbOldHadMasterPage(mxPage->TRG_HasMasterPage()) + , maOldMasterPageNumber(0) +{ + // get current state from page + if(mbOldHadMasterPage) + { + maOldSet = mxPage->TRG_GetMasterPageVisibleLayers(); + maOldMasterPageNumber = mxPage->TRG_GetMasterPage().GetPageNum(); + } +} + +SdrUndoPageMasterPage::~SdrUndoPageMasterPage() +{ +} + +SdrUndoPageRemoveMasterPage::SdrUndoPageRemoveMasterPage(SdrPage& rChangedPage) +: SdrUndoPageMasterPage(rChangedPage) +{ +} + +void SdrUndoPageRemoveMasterPage::Undo() +{ + if(mbOldHadMasterPage) + { + mxPage->TRG_SetMasterPage(*mxPage->getSdrModelFromSdrPage().GetMasterPage(maOldMasterPageNumber)); + mxPage->TRG_SetMasterPageVisibleLayers(maOldSet); + } +} + +void SdrUndoPageRemoveMasterPage::Redo() +{ + mxPage->TRG_ClearMasterPage(); +} + +OUString SdrUndoPageRemoveMasterPage::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoDelPageMasterDscr); +} + +SdrUndoPageChangeMasterPage::SdrUndoPageChangeMasterPage(SdrPage& rChangedPage) + : SdrUndoPageMasterPage(rChangedPage) + , mbNewHadMasterPage(false) + , maNewMasterPageNumber(0) +{ +} + +void SdrUndoPageChangeMasterPage::Undo() +{ + // remember values from new page + if(mxPage->TRG_HasMasterPage()) + { + mbNewHadMasterPage = true; + maNewSet = mxPage->TRG_GetMasterPageVisibleLayers(); + maNewMasterPageNumber = mxPage->TRG_GetMasterPage().GetPageNum(); + } + + // restore old values + if(mbOldHadMasterPage) + { + mxPage->TRG_ClearMasterPage(); + mxPage->TRG_SetMasterPage(*mxPage->getSdrModelFromSdrPage().GetMasterPage(maOldMasterPageNumber)); + mxPage->TRG_SetMasterPageVisibleLayers(maOldSet); + } +} + +void SdrUndoPageChangeMasterPage::Redo() +{ + // restore new values + if(mbNewHadMasterPage) + { + mxPage->TRG_ClearMasterPage(); + mxPage->TRG_SetMasterPage(*mxPage->getSdrModelFromSdrPage().GetMasterPage(maNewMasterPageNumber)); + mxPage->TRG_SetMasterPageVisibleLayers(maNewSet); + } +} + +OUString SdrUndoPageChangeMasterPage::GetComment() const +{ + return ImpGetDescriptionStr(STR_UndoChgPageMasterDscr); +} + + +SdrUndoFactory::~SdrUndoFactory(){} + +// shapes + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoMoveObject( SdrObject& rObject, const Size& rDist ) +{ + return std::make_unique<SdrUndoMoveObj>( rObject, rDist ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoGeoObject( SdrObject& rObject ) +{ + return std::make_unique<SdrUndoGeoObj>( rObject ); +} + +// Diagram ModelData changes +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoDiagramModelData( SdrObject& rObject, std::shared_ptr< svx::diagram::DiagramDataState >& rStartState ) +{ + return std::make_unique<SdrUndoDiagramModelData>( rObject, rStartState ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoAttrObject( SdrObject& rObject, bool bStyleSheet1, bool bSaveText ) +{ + return std::make_unique<SdrUndoAttrObj>( rObject, bStyleSheet1, bSaveText ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoRemoveObject(SdrObject& rObject) +{ + return std::make_unique<SdrUndoRemoveObj>(rObject); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoInsertObject( SdrObject& rObject, bool bOrdNumDirect ) +{ + return std::make_unique<SdrUndoInsertObj>( rObject, bOrdNumDirect ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoDeleteObject( SdrObject& rObject, bool bOrdNumDirect ) +{ + return std::make_unique<SdrUndoDelObj>( rObject, bOrdNumDirect ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoNewObject( SdrObject& rObject, bool bOrdNumDirect ) +{ + return std::make_unique<SdrUndoNewObj>( rObject, bOrdNumDirect ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoCopyObject( SdrObject& rObject, bool bOrdNumDirect ) +{ + return std::make_unique<SdrUndoCopyObj>( rObject, bOrdNumDirect ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoObjectOrdNum( SdrObject& rObject, sal_uInt32 nOldOrdNum1, sal_uInt32 nNewOrdNum1) +{ + return std::make_unique<SdrUndoObjOrdNum>( rObject, nOldOrdNum1, nNewOrdNum1 ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoSort(SdrPage & rPage, ::std::vector<sal_Int32> const& rSortOrder) +{ + return std::make_unique<SdrUndoSort>(rPage, rSortOrder); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoReplaceObject( SdrObject& rOldObject, SdrObject& rNewObject ) +{ + return std::make_unique<SdrUndoReplaceObj>( rOldObject, rNewObject ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoObjectLayerChange( SdrObject& rObject, SdrLayerID aOldLayer, SdrLayerID aNewLayer ) +{ + return std::make_unique<SdrUndoObjectLayerChange>( rObject, aOldLayer, aNewLayer ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoObjectSetText( SdrObject& rNewObj, sal_Int32 nText ) +{ + return std::make_unique<SdrUndoObjSetText>( rNewObj, nText ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoObjectStrAttr( SdrObject& rObject, + SdrUndoObjStrAttr::ObjStrAttrType eObjStrAttrType, + const OUString& sOldStr, + const OUString& sNewStr ) +{ + return std::make_unique<SdrUndoObjStrAttr>( rObject, eObjStrAttrType, sOldStr, sNewStr ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoObjectDecorative( + SdrObject& rObject, bool const WasDecorative) +{ + return std::make_unique<SdrUndoObjDecorative>(rObject, WasDecorative); +} + + +// layer +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoNewLayer(sal_uInt16 nLayerNum, SdrLayerAdmin& rNewLayerAdmin, SdrModel& rNewModel) +{ + return std::make_unique<SdrUndoNewLayer>( nLayerNum, rNewLayerAdmin, rNewModel ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoDeleteLayer(sal_uInt16 nLayerNum, SdrLayerAdmin& rNewLayerAdmin, SdrModel& rNewModel) +{ + return std::make_unique<SdrUndoDelLayer>( nLayerNum, rNewLayerAdmin, rNewModel ); +} + +// page +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoDeletePage(SdrPage& rPage) +{ + return std::make_unique<SdrUndoDelPage>(rPage); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoNewPage(SdrPage& rPage) +{ + return std::make_unique<SdrUndoNewPage>( rPage ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoCopyPage(SdrPage& rPage) +{ + return std::make_unique<SdrUndoCopyPage>( rPage ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoSetPageNum(SdrPage& rNewPg, sal_uInt16 nOldPageNum1, sal_uInt16 nNewPageNum1) +{ + return std::make_unique<SdrUndoSetPageNum>( rNewPg, nOldPageNum1, nNewPageNum1 ); +} + // master page +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoPageRemoveMasterPage(SdrPage& rChangedPage) +{ + return std::make_unique<SdrUndoPageRemoveMasterPage>( rChangedPage ); +} + +std::unique_ptr<SdrUndoAction> SdrUndoFactory::CreateUndoPageChangeMasterPage(SdrPage& rChangedPage) +{ + return std::make_unique<SdrUndoPageChangeMasterPage>(rChangedPage); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdview.cxx b/svx/source/svdraw/svdview.cxx new file mode 100644 index 0000000000..27233a6435 --- /dev/null +++ b/svx/source/svdraw/svdview.cxx @@ -0,0 +1,1553 @@ +/* -*- 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 <editeng/outlobj.hxx> + +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdmrkv.hxx> +#include <svx/svdedxv.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdomedia.hxx> +#include <svx/svdetc.hxx> + +#include <svx/sdr/table/tablecontroller.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdview.hxx> +#include <editeng/flditem.hxx> +#include <svx/obj3d.hxx> +#include <svx/svddrgmt.hxx> +#include <svx/svdotable.hxx> +#include <tools/debug.hxx> +#include <svx/sdr/overlay/overlaypolypolygon.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrhittesthelper.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> +#include <svx/sdr/contact/objectcontactofpageview.hxx> +#include <sal/log.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/window.hxx> +#include <comphelper/lok.hxx> + + +SdrViewEvent::SdrViewEvent() + : mpHdl(nullptr), + mpObj(nullptr), + mpRootObj(nullptr), + mpPV(nullptr), + mpURLField(nullptr), + meHit(SdrHitKind::NONE), + meEvent(SdrEventKind::NONE), + mnMouseClicks(0), + mnMouseMode(MouseEventModifiers::NONE), + mnMouseCode(0), + mnHlplIdx(0), + mnGlueId(0), + mbMouseDown(false), + mbMouseUp(false), + mbIsAction(false), + mbIsTextEdit(false), + mbAddMark(false), + mbUnmark(false), + mbPrevNextMark(false), + mbMarkPrev(false) +{ +} + +// helper class for all D&D overlays + +void SdrDropMarkerOverlay::ImplCreateOverlays( + const SdrView& rView, + const basegfx::B2DPolyPolygon& rLinePolyPolygon) +{ + for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++) + { + SdrPaintWindow* pCandidate = rView.GetPaintWindow(a); + const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager(); + + if (xTargetOverlay.is()) + { + std::unique_ptr<sdr::overlay::OverlayPolyPolygonStripedAndFilled> pNew(new sdr::overlay::OverlayPolyPolygonStripedAndFilled( + rLinePolyPolygon)); + + xTargetOverlay->add(*pNew); + maObjects.append(std::move(pNew)); + } + } +} + +SdrDropMarkerOverlay::SdrDropMarkerOverlay(const SdrView& rView, const SdrObject& rObject) +{ + ImplCreateOverlays( + rView, + rObject.TakeXorPoly()); +} + +SdrDropMarkerOverlay::SdrDropMarkerOverlay(const SdrView& rView, const tools::Rectangle& rRectangle) +{ + basegfx::B2DPolygon aB2DPolygon; + + aB2DPolygon.append(basegfx::B2DPoint(rRectangle.Left(), rRectangle.Top())); + aB2DPolygon.append(basegfx::B2DPoint(rRectangle.Right(), rRectangle.Top())); + aB2DPolygon.append(basegfx::B2DPoint(rRectangle.Right(), rRectangle.Bottom())); + aB2DPolygon.append(basegfx::B2DPoint(rRectangle.Left(), rRectangle.Bottom())); + aB2DPolygon.setClosed(true); + + ImplCreateOverlays( + rView, + basegfx::B2DPolyPolygon(aB2DPolygon)); +} + +SdrDropMarkerOverlay::SdrDropMarkerOverlay(const SdrView& rView, const Point& rStart, const Point& rEnd) +{ + basegfx::B2DPolygon aB2DPolygon; + + aB2DPolygon.append(basegfx::B2DPoint(rStart.X(), rStart.Y())); + aB2DPolygon.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y())); + aB2DPolygon.setClosed(true); + + ImplCreateOverlays( + rView, + basegfx::B2DPolyPolygon(aB2DPolygon)); +} + +SdrDropMarkerOverlay::~SdrDropMarkerOverlay() +{ + // The OverlayObjects are cleared using the destructor of OverlayObjectList. + // That destructor calls clear() at the list which removes all objects from the + // OverlayManager and deletes them. +} + +SdrView::SdrView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: SdrCreateView(rSdrModel, pOut), + mbNoExtendedMouseDispatcher(false), + mbNoExtendedKeyDispatcher(false), + mbMasterPagePaintCaching(false) +{ + maAccessibilityOptions.AddListener(this); + onAccessibilityOptionsChanged(); +} + +SdrView::~SdrView() +{ + maAccessibilityOptions.RemoveListener(this); +} + +bool SdrView::KeyInput(const KeyEvent& rKEvt, vcl::Window* pWin) +{ + SetActualWin(pWin ? pWin->GetOutDev() : nullptr); + bool bRet = SdrCreateView::KeyInput(rKEvt,pWin); + if (!bRet && !IsExtendedKeyInputDispatcherEnabled()) { + bRet = true; + switch (rKEvt.GetKeyCode().GetFullFunction()) { + case KeyFuncType::DELETE: DeleteMarked(); break; + case KeyFuncType::UNDO: GetModel().Undo(); break; + case KeyFuncType::REDO: GetModel().Redo(); break; + default: { + switch (rKEvt.GetKeyCode().GetFullCode()) { + case KEY_ESCAPE: { + if (IsTextEdit()) SdrEndTextEdit(); + if (IsAction()) BrkAction(); + if (pWin!=nullptr) pWin->ReleaseMouse(); + } break; + case KEY_DELETE: DeleteMarked(); break; + case KEY_UNDO: case KEY_BACKSPACE+KEY_MOD2: GetModel().Undo(); break; + case KEY_BACKSPACE+KEY_MOD2+KEY_SHIFT: GetModel().Redo(); break; + case KEY_REPEAT: case KEY_BACKSPACE+KEY_MOD2+KEY_MOD1: GetModel().Repeat(*this); break; + case KEY_MOD1+KEY_A: MarkAll(); break; + default: bRet=false; + } // switch + } + } // switch + if (bRet && pWin!=nullptr) { + pWin->SetPointer(GetPreferredPointer( + pWin->PixelToLogic(pWin->ScreenToOutputPixel( pWin->GetPointerPosPixel() ) ), + pWin->GetOutDev(), + rKEvt.GetKeyCode().GetModifier())); + } + } + return bRet; +} + +bool SdrView::MouseButtonDown(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + SetActualWin(pWin); + if (rMEvt.IsLeft()) maDragStat.SetMouseDown(true); + bool bRet = SdrCreateView::MouseButtonDown(rMEvt,pWin); + if (!bRet && !IsExtendedMouseEventDispatcherEnabled()) { + SdrViewEvent aVEvt; + PickAnything(rMEvt,SdrMouseEventKind::BUTTONDOWN,aVEvt); + bRet = DoMouseEvent(aVEvt); + } + return bRet; +} + +bool SdrView::MouseButtonUp(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + SetActualWin(pWin); + if (rMEvt.IsLeft()) maDragStat.SetMouseDown(false); + bool bAction = IsAction(); + bool bRet = !bAction && SdrCreateView::MouseButtonUp(rMEvt,pWin); + if (!bRet && !IsExtendedMouseEventDispatcherEnabled()) { + SdrViewEvent aVEvt; + PickAnything(rMEvt,SdrMouseEventKind::BUTTONUP,aVEvt); + bRet = DoMouseEvent(aVEvt); + } + return bRet; +} + +bool SdrView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + SetActualWin(pWin); + maDragStat.SetMouseDown(rMEvt.IsLeft()); + bool bRet = SdrCreateView::MouseMove(rMEvt,pWin); + if (!IsExtendedMouseEventDispatcherEnabled() && !IsTextEditInSelectionMode()) { + SdrViewEvent aVEvt; + PickAnything(rMEvt,SdrMouseEventKind::MOVE,aVEvt); + if (DoMouseEvent(aVEvt)) bRet=true; + } + + return bRet; +} + +bool SdrView::Command(const CommandEvent& rCEvt, vcl::Window* pWin) +{ + SetActualWin(pWin->GetOutDev()); + bool bRet = SdrCreateView::Command(rCEvt,pWin); + return bRet; +} + +void SdrView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + SdrCreateView::GetAttributes(rTargetSet, bOnlyHardAttr); +} + +SdrHitKind SdrView::PickAnything(const MouseEvent& rMEvt, SdrMouseEventKind nEventKind, SdrViewEvent& rVEvt) const +{ + rVEvt.mbMouseDown = nEventKind==SdrMouseEventKind::BUTTONDOWN; + rVEvt.mbMouseUp = nEventKind==SdrMouseEventKind::BUTTONUP; + rVEvt.mnMouseClicks = rMEvt.GetClicks(); + rVEvt.mnMouseMode = rMEvt.GetMode(); + rVEvt.mnMouseCode = rMEvt.GetButtons() | rMEvt.GetModifier(); + const OutputDevice* pOut=mpActualOutDev; + if (pOut==nullptr) + { + pOut = GetFirstOutputDevice(); + } + Point aPnt(rMEvt.GetPosPixel()); + if (pOut!=nullptr) aPnt=pOut->PixelToLogic(aPnt); + + if (mbNegativeX) + { + // Shape's x coordinates are all negated, + // Hence negate mouse event's x coord to match. + aPnt.setX(-aPnt.X()); + } + + rVEvt.maLogicPos = aPnt; + return PickAnything(aPnt,rVEvt); +} + +// Dragging with the Mouse (Move) +// Example when creating a rectangle: MouseDown has to happen without a ModKey, +// else we usually force a selection (see below). +// When pressing Shift, Ctrl and Alt at the same time while doing a MouseMove, +// a centered, not snapped square is created. +// The dual allocation of Ortho and Shift won't usually create a problem, as the +// two functions are in most cases mutually exclusive. Only shearing (the kind +// that happens when contorting, not when rotating) can use both functions at +// the same time. To get around this, the user can use e. g. help lines. +#define MODKEY_NoSnap bCtrl /* temporarily disable snapping */ +#define MODKEY_Ortho bShift /* ortho */ +#define MODKEY_Center bAlt /* create/resize centeredly */ +#define MODKEY_AngleSnap bShift +#define MODKEY_CopyDrag bCtrl /* drag and copy */ + +// click somewhere (MouseDown) +#define MODKEY_PolyPoly bAlt /* new Poly at InsPt and at Create */ +#define MODKEY_MultiMark bShift /* MarkObj without doing UnmarkAll first */ +#define MODKEY_Unmark bAlt /* deselect with a dragged frame */ +#define MODKEY_ForceMark bCtrl /* force dragging a frame, even if there's an object at cursor position */ +#define MODKEY_DeepMark bAlt /* MarkNextObj */ +#define MODKEY_DeepBackw bShift /* MarkNextObj but backwards */ + +SdrHitKind SdrView::PickAnything(const Point& rLogicPos, SdrViewEvent& rVEvt) const +{ + const OutputDevice* pOut=mpActualOutDev; + if (pOut==nullptr) + { + pOut = GetFirstOutputDevice(); + } + + // #i73628# Use a non-changeable copy of he logic position + const Point aLocalLogicPosition(rLogicPos); + + bool bEditMode=IsEditMode(); + bool bPointMode=bEditMode && HasMarkablePoints(); + bool bGluePointMode=IsGluePointEditMode(); + bool bInsPolyPt=bPointMode && IsInsObjPointMode() && IsInsObjPointPossible(); + bool bInsGluePt=bGluePointMode && IsInsGluePointMode() && IsInsGluePointPossible(); + bool bIsTextEdit=IsTextEdit(); + bool bTextEditHit=IsTextEditHit(aLocalLogicPosition); + bool bTextEditSel=IsTextEditInSelectionMode(); + bool bShift = (rVEvt.mnMouseCode & KEY_SHIFT) != 0; + bool bCtrl = (rVEvt.mnMouseCode & KEY_MOD1) != 0; + bool bAlt = (rVEvt.mnMouseCode & KEY_MOD2) != 0; + SdrHitKind eHit=SdrHitKind::NONE; + SdrHdl* pHdl=pOut!=nullptr && !bTextEditSel ? PickHandle(aLocalLogicPosition) : nullptr; + SdrPageView* pPV=nullptr; + SdrObject* pObj=nullptr; + SdrObject* pHitObj=nullptr; + bool bHitPassDirect=true; + sal_uInt16 nHlplIdx=0; + sal_uInt16 nGlueId=0; + if (bTextEditHit || bTextEditSel) + { + eHit=SdrHitKind::TextEdit; + } + else if (pHdl!=nullptr) + { + eHit=SdrHitKind::Handle; // handle is hit: highest priority + } + else if (bEditMode && IsHlplVisible() && IsHlplFront() && pOut!=nullptr && PickHelpLine(aLocalLogicPosition,mnHitTolLog,*pOut,nHlplIdx,pPV)) + { + eHit=SdrHitKind::HelpLine; // help line in the foreground hit: can be moved now + } + else if (bGluePointMode && PickGluePoint(aLocalLogicPosition,pObj,nGlueId,pPV)) + { + eHit=SdrHitKind::Gluepoint; // deselected gluepoint hit + } + else if ((pHitObj = PickObj(aLocalLogicPosition,mnHitTolLog,pPV,SdrSearchOptions::DEEP|SdrSearchOptions::MARKED,&pObj,&bHitPassDirect))) + { + eHit=SdrHitKind::MarkedObject; + sdr::table::SdrTableObj* pTableObj = dynamic_cast< sdr::table::SdrTableObj* >( pObj ); + if( pTableObj ) + { + sal_Int32 nX = 0, nY = 0; + switch( pTableObj->CheckTableHit( aLocalLogicPosition, nX, nY ) ) + { + case sdr::table::TableHitKind::Cell: + eHit = SdrHitKind::Cell; + break; + case sdr::table::TableHitKind::CellTextArea: + eHit = SdrHitKind::TextEditObj; + break; + default: + break; + } + } + } + else if ((pHitObj = PickObj(aLocalLogicPosition,mnHitTolLog,pPV,SdrSearchOptions::DEEP|SdrSearchOptions::ALSOONMASTER|SdrSearchOptions::WHOLEPAGE,&pObj,&bHitPassDirect))) + { + // MasterPages and WholePage for Macro and URL + eHit=SdrHitKind::UnmarkedObject; + sdr::table::SdrTableObj* pTableObj = dynamic_cast< sdr::table::SdrTableObj* >( pObj ); + if( pTableObj ) + { + sal_Int32 nX = 0, nY = 0; + switch( pTableObj->CheckTableHit( aLocalLogicPosition, nX, nY, mnHitTolLog ) ) + { + case sdr::table::TableHitKind::Cell: + eHit = SdrHitKind::Cell; + break; + case sdr::table::TableHitKind::CellTextArea: + // Keep state on UnmarkedObject to allow the below + // 'check for URL field' to be executed, else popups + // for e.g. URL links when hoovering and clicking + // them will not work. Tried several other changes, + // but this one safely keeps existing behaviour as-is. + // Except for the LOK. LOK doesn't have hoovering popup + // feature. + eHit = comphelper::LibreOfficeKit::isActive() ? SdrHitKind::TextEditObj : SdrHitKind::UnmarkedObject; + break; + default: + break; + } + } + } + else if (bEditMode && IsHlplVisible() && !IsHlplFront() && pOut!=nullptr && PickHelpLine(aLocalLogicPosition,mnHitTolLog,*pOut,nHlplIdx,pPV)) + { + eHit=SdrHitKind::HelpLine; // help line in foreground hit: can be moved now + } + if (eHit==SdrHitKind::UnmarkedObject) + { + bool bRoot=pObj->HasMacro(); + bool bDeep=pObj!=pHitObj && pHitObj->HasMacro(); + bool bMid=false; // Have we hit upon a grouped group with a macro? + SdrObject* pMidObj=nullptr; + if (pObj!=pHitObj) + { + SdrObject* pObjTmp=pHitObj->getParentSdrObjectFromSdrObject(); + if (pObjTmp==pObj) pObjTmp=nullptr; + while (pObjTmp!=nullptr) + { + if (pObjTmp->HasMacro()) + { + bMid=true; + pMidObj=pObjTmp; + } + pObjTmp=pObjTmp->getParentSdrObjectFromSdrObject(); + if (pObjTmp==pObj) pObjTmp=nullptr; + } + } + + if (bDeep || bMid || bRoot) + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos=aLocalLogicPosition; + aHitRec.nTol=mnHitTolLog; + aHitRec.pVisiLayer=&pPV->GetVisibleLayers(); + aHitRec.pPageView=pPV; + if (bDeep) bDeep=pHitObj->IsMacroHit(aHitRec); + if (bMid ) bMid =pMidObj->IsMacroHit(aHitRec); + if (bRoot) bRoot=pObj->IsMacroHit(aHitRec); + if (bRoot || bMid || bDeep) + { + // Priorities: 1. Root, 2. Mid, 3. Deep + rVEvt.mpRootObj = pObj; + if (!bRoot) pObj=pMidObj; + if (!bRoot && !bMid) pObj=pHitObj; + eHit=SdrHitKind::Macro; + } + } + } + // check for URL field + if (eHit==SdrHitKind::UnmarkedObject) + { + SdrTextObj* pTextObj=DynCastSdrTextObj( pHitObj ); + if (pTextObj!=nullptr && pTextObj->HasText()) + { + // use the primitive-based HitTest which is more accurate anyways. It + // will correctly handle rotated/mirrored/sheared/scaled text and can + // now return a HitContainer containing the primitive hierarchy of the + // primitive that triggered the hit. The first entry is that primitive, + // the others are the full stack of primitives leading to that one which + // includes grouping primitives (like TextHierarchyPrimitives we deed here) + // but also all decomposed ones which lead to the creation of that primitive + drawinglayer::primitive2d::Primitive2DContainer aHitContainer; + const bool bTEHit(pPV && SdrObjectPrimitiveHit(*pTextObj, aLocalLogicPosition, {0, 0}, *pPV, &pPV->GetVisibleLayers(), true, &aHitContainer)); + + if (bTEHit && !aHitContainer.empty()) + { + // search for TextHierarchyFieldPrimitive2D which contains the needed information + // about a possible URLField + const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D* pTextHierarchyFieldPrimitive2D = nullptr; + + for (const drawinglayer::primitive2d::Primitive2DReference& xReference : aHitContainer) + { + auto pBasePrimitive = xReference.get(); + if (pBasePrimitive && pBasePrimitive->getPrimitive2DID() == PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D) + { + pTextHierarchyFieldPrimitive2D = static_cast<const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D*>(pBasePrimitive); + break; + } + } + + if (nullptr != pTextHierarchyFieldPrimitive2D) + { + if (drawinglayer::primitive2d::FieldType::FIELD_TYPE_URL == pTextHierarchyFieldPrimitive2D->getType()) + { + // problem with the old code is that a *pointer* to an instance of + // SvxURLField is set in the Event which is per se not good since that + // data comes from a temporary EditEngine's data and could vanish any + // moment. Have to replace for now with a static instance that gets + // filled/initialized from the original data held in the TextHierarchyField- + // Primitive2D (see impTextBreakupHandler::impCheckFieldPrimitive). + // Unfortunately things like 'TargetFrame' are still used in Calc, so this + // can currently not get replaced. For the future the Name/Value vector or + // the TextHierarchyFieldPrimitive2D itself should/will be used for handling + // that data + static SvxURLField aSvxURLField; + + aSvxURLField.SetURL(pTextHierarchyFieldPrimitive2D->getValue("URL")); + aSvxURLField.SetRepresentation(pTextHierarchyFieldPrimitive2D->getValue("Representation")); + aSvxURLField.SetTargetFrame(pTextHierarchyFieldPrimitive2D->getValue("TargetFrame")); + const OUString aFormat(pTextHierarchyFieldPrimitive2D->getValue("SvxURLFormat")); + + if (!aFormat.isEmpty()) + { + aSvxURLField.SetFormat(static_cast<SvxURLFormat>(aFormat.toInt32())); + } + + // set HitKind and pointer to local static instance in the Event + // to comply to old stuff + eHit = SdrHitKind::UrlField; + rVEvt.mpURLField = &aSvxURLField; + } + } + } + } + if (eHit==SdrHitKind::UnmarkedObject && !pHitObj->getHyperlink().isEmpty()) + { + static SvxURLField aSvxURLField; + aSvxURLField.SetURL(pHitObj->getHyperlink()); + rVEvt.mpURLField = &aSvxURLField; + eHit = SdrHitKind::UrlField; + } + } + + if (bHitPassDirect && + (eHit==SdrHitKind::MarkedObject || eHit==SdrHitKind::UnmarkedObject) && + (IsTextTool() || (IsEditMode() && IsQuickTextEditMode())) && pHitObj->HasTextEdit()) + { + auto pTextObj = DynCastSdrTextObj(pHitObj); + + // Around the TextEditArea there's a border to select without going into text edit mode. + tools::Rectangle aBoundRect; + const GeoStat& rGeo = pTextObj->GetGeoStat(); + if (pTextObj && !rGeo.m_nRotationAngle && !rGeo.m_nShearAngle) + { + pTextObj->TakeTextEditArea(nullptr, nullptr, &aBoundRect, nullptr); + } + else + aBoundRect = pHitObj->GetCurrentBoundRect(); + + // Force to SnapRect when Fontwork + if( pTextObj && pTextObj->IsFontwork() ) + aBoundRect = pHitObj->GetSnapRect(); + + sal_Int32 nTolerance(mnHitTolLog); + bool bBoundRectHit(false); + + if(pOut) + { + nTolerance = pOut->PixelToLogic(Size(2, 0)).Width(); + } + + if( (aLocalLogicPosition.X() >= aBoundRect.Left() - nTolerance && aLocalLogicPosition.X() <= aBoundRect.Left() + nTolerance) + || (aLocalLogicPosition.X() >= aBoundRect.Right() - nTolerance && aLocalLogicPosition.X() <= aBoundRect.Right() + nTolerance) + || (aLocalLogicPosition.Y() >= aBoundRect.Top() - nTolerance && aLocalLogicPosition.Y() <= aBoundRect.Top() + nTolerance) + || (aLocalLogicPosition.Y() >= aBoundRect.Bottom() - nTolerance && aLocalLogicPosition.Y() <= aBoundRect.Bottom() + nTolerance)) + { + bBoundRectHit = true; + } + + if(!bBoundRectHit && aBoundRect.Contains(aLocalLogicPosition)) + { + bool bTEHit(pPV + && SdrObjectPrimitiveHit(*pHitObj, aLocalLogicPosition, { 2000.0, 0.0 }, + *pPV, &pPV->GetVisibleLayers(), true)); + + // TextEdit attached to an object in a locked layer + if (bTEHit && pPV->GetLockedLayers().IsSet(pHitObj->GetLayer())) + { + bTEHit=false; + } + + if (bTEHit) + { + rVEvt.mpRootObj=pObj; + pObj=pHitObj; + eHit=SdrHitKind::TextEditObj; + } + } + } + if (!bHitPassDirect && eHit==SdrHitKind::UnmarkedObject) { + eHit=SdrHitKind::NONE; + pObj=nullptr; + pPV=nullptr; + } + bool bMouseLeft = (rVEvt.mnMouseCode & MOUSE_LEFT) != 0; + bool bMouseRight = (rVEvt.mnMouseCode & MOUSE_RIGHT) != 0; + bool bMouseDown = rVEvt.mbMouseDown; + bool bMouseUp = rVEvt.mbMouseUp; + SdrEventKind eEvent=SdrEventKind::NONE; + bool bIsAction=IsAction(); + + if (bIsAction) + { + if (bMouseDown) + { + if (bMouseRight) eEvent=SdrEventKind::BackAction; + } + else if (bMouseUp) + { + if (bMouseLeft) + { + eEvent=SdrEventKind::EndAction; + if (IsDragObj()) + { + eEvent=SdrEventKind::EndDrag; + } + else if (IsCreateObj() || IsInsObjPoint()) + { + eEvent=IsCreateObj() ? SdrEventKind::EndCreate : SdrEventKind::EndInsertObjPoint; + } + else if (IsMarking()) + { + eEvent=SdrEventKind::EndMark; + if (!maDragStat.IsMinMoved()) + { + eEvent=SdrEventKind::BrkMark; + rVEvt.mbAddMark = MODKEY_MultiMark; + } + } + } + } + else + { + eEvent=SdrEventKind::MoveAction; + } + } + else if (eHit==SdrHitKind::TextEdit) + { + eEvent=SdrEventKind::TextEdit; + } + else if (bMouseDown && bMouseLeft) + { + if (rVEvt.mnMouseClicks == 2 && rVEvt.mnMouseCode == MOUSE_LEFT && pObj!=nullptr && pHitObj!=nullptr && pHitObj->HasTextEdit() && eHit==SdrHitKind::MarkedObject) + { + rVEvt.mpRootObj = pObj; + pObj=pHitObj; + eEvent=SdrEventKind::BeginTextEdit; + } + else if (MODKEY_ForceMark && eHit!=SdrHitKind::UrlField) + { + eEvent=SdrEventKind::BeginMark; // AddMark,Unmark */ + } + else if (eHit==SdrHitKind::HelpLine) + { + eEvent=SdrEventKind::BeginDragHelpline; // nothing, actually + } + else if (eHit==SdrHitKind::Gluepoint) + { + eEvent=SdrEventKind::MarkGluePoint; // AddMark+Drag + rVEvt.mbAddMark = MODKEY_MultiMark || MODKEY_DeepMark; // if not hit with Deep + } + else if (eHit==SdrHitKind::Handle) + { + eEvent=SdrEventKind::BeginDragObj; // Mark+Drag,AddMark+Drag,DeepMark+Drag,Unmark + bool bGlue=pHdl->GetKind()==SdrHdlKind::Glue; + bool bPoly=!bGlue && IsPointMarkable(*pHdl); + bool bMarked=bGlue || (bPoly && pHdl->IsSelected()); + if (bGlue || bPoly) + { + eEvent=bGlue ? SdrEventKind::MarkGluePoint : SdrEventKind::MarkPoint; + if (MODKEY_DeepMark) + { + rVEvt.mbAddMark = true; + rVEvt.mbPrevNextMark = true; + rVEvt.mbMarkPrev = MODKEY_DeepBackw; + } + else if (MODKEY_MultiMark) + { + rVEvt.mbAddMark = true; + rVEvt.mbUnmark = bMarked; // Toggle + if (bGlue) + { + pObj=pHdl->GetObj(); + nGlueId=static_cast<sal_uInt16>(pHdl->GetObjHdlNum()); + } + } + else if (bMarked) + { + eEvent=SdrEventKind::BeginDragObj; // don't change MarkState, only change Drag + } + } + } + else if (bInsPolyPt && (MODKEY_PolyPoly || (!MODKEY_MultiMark && !MODKEY_DeepMark))) + { + eEvent=SdrEventKind::BeginInsertObjPoint; + } + else if (bInsGluePt && !MODKEY_MultiMark && !MODKEY_DeepMark) + { + eEvent=SdrEventKind::BeginInsertGluePoint; + } + else if (eHit==SdrHitKind::TextEditObj) + { + eEvent=SdrEventKind::BeginTextEdit; // AddMark+Drag,DeepMark+Drag,Unmark + if (MODKEY_MultiMark || MODKEY_DeepMark) + { // if not hit with Deep + eEvent=SdrEventKind::MarkObj; + } + } + else if (eHit==SdrHitKind::Macro) + { + eEvent=SdrEventKind::BeginMacroObj; // AddMark+Drag + if (MODKEY_MultiMark || MODKEY_DeepMark) + { // if not hit with Deep + eEvent=SdrEventKind::MarkObj; + } + } + else if (eHit==SdrHitKind::UrlField) + { + eEvent=SdrEventKind::ExecuteUrl; // AddMark+Drag + if (MODKEY_MultiMark || MODKEY_DeepMark) + { // if not hit with Deep + eEvent=SdrEventKind::MarkObj; + } + } + else if (eHit==SdrHitKind::MarkedObject) + { + eEvent=SdrEventKind::BeginDragObj; // DeepMark+Drag,Unmark + + if (MODKEY_MultiMark || MODKEY_DeepMark) + { // if not hit with Deep + eEvent=SdrEventKind::MarkObj; + } + } + else if (IsCreateMode()) + { + eEvent=SdrEventKind::BeginCreateObj; // nothing, actually + } + else if (eHit==SdrHitKind::UnmarkedObject) + { + eEvent=SdrEventKind::MarkObj; // AddMark+Drag + } + else + { + eEvent=SdrEventKind::BeginMark; + } + + if (eEvent==SdrEventKind::MarkObj) + { + rVEvt.mbAddMark = MODKEY_MultiMark || MODKEY_DeepMark; // if not hit with Deep + rVEvt.mbPrevNextMark = MODKEY_DeepMark; + rVEvt.mbMarkPrev = MODKEY_DeepMark && MODKEY_DeepBackw; + } + if (eEvent==SdrEventKind::BeginMark) + { + rVEvt.mbAddMark = MODKEY_MultiMark; + rVEvt.mbUnmark = MODKEY_Unmark; + } + } + rVEvt.mbIsAction = bIsAction; + rVEvt.mbIsTextEdit = bIsTextEdit; + rVEvt.maLogicPos = aLocalLogicPosition; + rVEvt.mpHdl = pHdl; + rVEvt.mpObj = pObj; + if (rVEvt.mpRootObj == nullptr) + rVEvt.mpRootObj = pObj; + rVEvt.mpPV = pPV; + rVEvt.mnHlplIdx = nHlplIdx; + rVEvt.mnGlueId = nGlueId; + rVEvt.meHit = eHit; + rVEvt.meEvent = eEvent; +#ifdef DGB_UTIL + if (rVEvt.mpRootObj != nullptr) + { + if (rVEvt.mpRootObj->getParentSdrObjListFromSdrObject() != rVEvt.mpPV->GetObjList()) + { + OSL_FAIL("SdrView::PickAnything(): pRootObj->getParentSdrObjListFromSdrObject()!=pPV->GetObjList() !"); + } + } +#endif + return eHit; +} + +bool SdrView::DoMouseEvent(const SdrViewEvent& rVEvt) +{ + bool bRet=false; + SdrHitKind eHit = rVEvt.meHit; + Point aLogicPos(rVEvt.maLogicPos); + + bool bShift = (rVEvt.mnMouseCode & KEY_SHIFT) != 0; + bool bCtrl = (rVEvt.mnMouseCode & KEY_MOD1) != 0; + bool bAlt = (rVEvt.mnMouseCode & KEY_MOD2) != 0; + bool bMouseLeft = (rVEvt.mnMouseCode & MOUSE_LEFT) != 0; + bool bMouseDown = rVEvt.mbMouseDown; + bool bMouseUp = rVEvt.mbMouseUp; + if (bMouseDown) { + if (bMouseLeft) maDragStat.SetMouseDown(true); + } else if (bMouseUp) { + if (bMouseLeft) maDragStat.SetMouseDown(false); + } else { // else, MouseMove + maDragStat.SetMouseDown(bMouseLeft); + } + +#ifdef MODKEY_NoSnap + SetSnapEnabled(!MODKEY_NoSnap); +#endif +#ifdef MODKEY_Ortho + SetOrtho(MODKEY_Ortho!=IsOrthoDesired()); +#endif +#ifdef MODKEY_AngleSnap + SetAngleSnapEnabled(MODKEY_AngleSnap); +#endif +#ifdef MODKEY_CopyDrag + SetDragWithCopy(MODKEY_CopyDrag); +#endif +#ifdef MODKEY_Center + SetCreate1stPointAsCenter(MODKEY_Center); + SetResizeAtCenter(MODKEY_Center); + SetCrookAtCenter(MODKEY_Center); +#endif + if (bMouseLeft && bMouseDown && rVEvt.mbIsTextEdit && (eHit==SdrHitKind::UnmarkedObject || eHit==SdrHitKind::NONE)) { + SdrEndTextEdit(); // User has clicked beneath object, exit edit mode. + // pHdl is invalid, then, that shouldn't matter, though, as we expect + // pHdl==NULL (because of eHit). + } + switch (rVEvt.meEvent) + { + case SdrEventKind::NONE: bRet=false; break; + case SdrEventKind::TextEdit: bRet=false; break; // Events handled by the OutlinerView are not taken into account here. + case SdrEventKind::MoveAction: MovAction(aLogicPos); bRet=true; break; + case SdrEventKind::EndAction: EndAction(); bRet=true; break; + case SdrEventKind::BackAction: BckAction(); bRet=true; break; + case SdrEventKind::EndMark : EndAction(); bRet=true; break; + case SdrEventKind::BrkMark : { + BrkAction(); + if (!MarkObj(aLogicPos, mnHitTolLog, rVEvt.mbAddMark)) + { + // No object hit. Do the following: + // 1. deselect any selected gluepoints + // 2. deselect any selected polygon points + // 3. deselect any selected objects + if (!rVEvt.mbAddMark) UnmarkAll(); + } + bRet=true; + } break; + case SdrEventKind::EndCreate: { // if necessary, MarkObj + SdrCreateCmd eCmd=SdrCreateCmd::NextPoint; + if (MODKEY_PolyPoly) eCmd=SdrCreateCmd::NextObject; + if (rVEvt.mnMouseClicks > 1) eCmd=SdrCreateCmd::ForceEnd; + if (!EndCreateObj(eCmd)) { // Don't evaluate event for Create? -> Select + if (eHit==SdrHitKind::UnmarkedObject || eHit==SdrHitKind::TextEdit) { + MarkObj(rVEvt.mpRootObj, rVEvt.mpPV); + if (eHit==SdrHitKind::TextEdit) + { + bool bRet2(mpActualOutDev && OUTDEV_WINDOW == mpActualOutDev->GetOutDevType() && + SdrBeginTextEdit(rVEvt.mpObj, rVEvt.mpPV, mpActualOutDev->GetOwnerWindow())); + + if(bRet2) + { + MouseEvent aMEvt(mpActualOutDev->LogicToPixel(aLogicPos), 1, + rVEvt.mnMouseMode,rVEvt.mnMouseCode,rVEvt.mnMouseCode); + + OutlinerView* pOLV=GetTextEditOutlinerView(); + if (pOLV!=nullptr) { + pOLV->MouseButtonDown(aMEvt); // event for the Outliner, but without double-click + pOLV->MouseButtonUp(aMEvt); // event for the Outliner, but without double-click + } + } + } + bRet=true; // object is selected and (if necessary) TextEdit is started + } else bRet=false; // canceled Create, nothing else + } else bRet=true; // return true for EndCreate + } break; + case SdrEventKind::EndDrag: { + bRet=EndDragObj(IsDragWithCopy()); + ForceMarkedObjToAnotherPage(); // TODO: Undo+bracing missing! + } break; + case SdrEventKind::MarkObj: { // + (if applicable) BegDrag + if (!rVEvt.mbAddMark) UnmarkAllObj(); + bool bUnmark = rVEvt.mbUnmark; + if (rVEvt.mbPrevNextMark) { + bRet=MarkNextObj(aLogicPos, mnHitTolLog, rVEvt.mbMarkPrev); + } else { + SortMarkedObjects(); + const size_t nCount0=GetMarkedObjectCount(); + bRet=MarkObj(aLogicPos, mnHitTolLog, rVEvt.mbAddMark); + SortMarkedObjects(); + const size_t nCount1=GetMarkedObjectCount(); + bUnmark=nCount1<nCount0; + } + if (!bUnmark) { + BegDragObj(aLogicPos,nullptr,nullptr,mnMinMovLog); + bRet=true; + } + } break; + case SdrEventKind::MarkPoint: { // + (if applicable) BegDrag + if (!rVEvt.mbAddMark) UnmarkAllPoints(); + if (rVEvt.mbPrevNextMark) { + MarkNextPoint(); + bRet=false; + } else { + bRet = MarkPoint(*rVEvt.mpHdl, rVEvt.mbUnmark); + } + if (!rVEvt.mbUnmark && !rVEvt.mbPrevNextMark) { + BegDragObj(aLogicPos, nullptr, rVEvt.mpHdl, mnMinMovLog); + bRet=true; + } + } break; + case SdrEventKind::MarkGluePoint: { // + (if applicable) BegDrag + if (!rVEvt.mbAddMark) UnmarkAllGluePoints(); + if (rVEvt.mbPrevNextMark) { + MarkNextGluePoint(); + bRet=false; + } else { + bRet=MarkGluePoint(rVEvt.mpObj,rVEvt.mnGlueId,rVEvt.mbUnmark); + } + if (!rVEvt.mbUnmark && !rVEvt.mbPrevNextMark) { + SdrHdl* pHdl = GetGluePointHdl(rVEvt.mpObj, rVEvt.mnGlueId); + BegDragObj(aLogicPos,nullptr,pHdl,mnMinMovLog); + bRet=true; + } + } break; + case SdrEventKind::BeginMark: bRet = BegMark(aLogicPos,rVEvt.mbAddMark,rVEvt.mbUnmark); break; + case SdrEventKind::BeginInsertObjPoint: bRet = BegInsObjPoint(aLogicPos, MODKEY_PolyPoly); break; + case SdrEventKind::EndInsertObjPoint: { + SdrCreateCmd eCmd=SdrCreateCmd::NextPoint; + if (MODKEY_PolyPoly) eCmd=SdrCreateCmd::NextObject; + if (rVEvt.mnMouseClicks > 1) eCmd = SdrCreateCmd::ForceEnd; + EndInsObjPoint(eCmd); + bRet=true; + } break; + case SdrEventKind::BeginInsertGluePoint: bRet=BegInsGluePoint(aLogicPos); break; + case SdrEventKind::BeginDragHelpline: bRet = BegDragHelpLine(rVEvt.mnHlplIdx,rVEvt.mpPV); break; + case SdrEventKind::BeginDragObj: bRet=BegDragObj(aLogicPos, nullptr, rVEvt.mpHdl, mnMinMovLog); break; + case SdrEventKind::BeginCreateObj: { + if (mnCurrentInvent==SdrInventor::Default && mnCurrentIdent==SdrObjKind::Caption) { + tools::Long nHgt=SdrEngineDefaults::GetFontHeight(); + bRet=BegCreateCaptionObj(aLogicPos,Size(5*nHgt,2*nHgt)); + } else bRet=BegCreateObj(aLogicPos); + } break; + case SdrEventKind::BeginMacroObj: { + BegMacroObj(aLogicPos, mnHitTolLog, rVEvt.mpObj, rVEvt.mpPV, mpActualOutDev->GetOwnerWindow()); + bRet=false; + } break; + case SdrEventKind::BeginTextEdit: { + if (!IsObjMarked(rVEvt.mpObj)) { + UnmarkAllObj(); + MarkObj(rVEvt.mpRootObj,rVEvt.mpPV); + } + + bRet = mpActualOutDev && OUTDEV_WINDOW == mpActualOutDev->GetOutDevType()&& + SdrBeginTextEdit(rVEvt.mpObj, rVEvt.mpPV, mpActualOutDev->GetOwnerWindow()); + + if(bRet) + { + MouseEvent aMEvt(mpActualOutDev->LogicToPixel(aLogicPos), + 1, rVEvt.mnMouseMode, rVEvt.mnMouseCode, rVEvt.mnMouseCode); + OutlinerView* pOLV=GetTextEditOutlinerView(); + if (pOLV!=nullptr) pOLV->MouseButtonDown(aMEvt); // event for the Outliner, but without double-click + } + } break; + default: break; + } // switch + if (bRet && mpActualOutDev && mpActualOutDev->GetOutDevType()==OUTDEV_WINDOW) { + vcl::Window* pWin=mpActualOutDev->GetOwnerWindow(); + // left mouse button pressed? + bool bLeftDown = (rVEvt.mnMouseCode & MOUSE_LEFT) != 0 && rVEvt.mbMouseDown; + // left mouse button released? + bool bLeftUp = (rVEvt.mnMouseCode & MOUSE_LEFT) != 0 && rVEvt.mbMouseUp; + // left mouse button pressed or held? + bool bLeftDown1=(rVEvt.mnMouseCode & MOUSE_LEFT) != 0 && !rVEvt.mbMouseUp; + pWin->SetPointer(GetPreferredPointer(rVEvt.maLogicPos, pWin->GetOutDev(), + rVEvt.mnMouseCode & (KEY_SHIFT|KEY_MOD1|KEY_MOD2),bLeftDown1)); + bool bAction=IsAction(); + if (bLeftDown && bAction) + pWin->CaptureMouse(); + else if (bLeftUp || (rVEvt.mbIsAction && !bAction)) + pWin->ReleaseMouse(); + } + return bRet; +} + +PointerStyle SdrView::GetPreferredPointer(const Point& rMousePos, const OutputDevice* pOut, sal_uInt16 nModifier, bool bLeftDown) const +{ + // Actions + if (IsCreateObj()) + { + return mpCurrentCreate->GetCreatePointer(); + } + if (mpCurrentSdrDragMethod) + { + return mpCurrentSdrDragMethod->GetSdrDragPointer(); + } + if (IsMarkObj() || IsMarkPoints() || IsMarkGluePoints() || IsSetPageOrg()) return PointerStyle::Arrow; + if (IsDragHelpLine()) return GetDraggedHelpLinePointer(); + if (IsMacroObj()) { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos=pOut->LogicToPixel(rMousePos); + aHitRec.nTol=nMacroTol; + aHitRec.pVisiLayer=&pMacroPV->GetVisibleLayers(); + aHitRec.pPageView=pMacroPV; + return pMacroObj->GetMacroPointer(aHitRec); + } + + // TextEdit, ObjEdit, Macro + if (IsTextEdit() && (IsTextEditInSelectionMode() || IsTextEditHit(rMousePos))) + { + if(!pOut || IsTextEditInSelectionMode()) + { + if (mpTextEditOutliner->IsVertical()) + return PointerStyle::TextVertical; + else + return PointerStyle::Text; + } + // Outliner should return something here... + Point aPos(pOut->LogicToPixel(rMousePos)); + PointerStyle aPointer(mpTextEditOutlinerView->GetPointer(aPos)); + if (aPointer==PointerStyle::Arrow) + { + if (mpTextEditOutliner->IsVertical()) + aPointer = PointerStyle::TextVertical; + else + aPointer = PointerStyle::Text; + } + return aPointer; + } + + SdrViewEvent aVEvt; + aVEvt.mnMouseCode = (nModifier&(KEY_SHIFT|KEY_MOD1|KEY_MOD2))|MOUSE_LEFT; // to see what would happen on MouseLeftDown + aVEvt.mbMouseDown = !bLeftDown; // What if ..? + aVEvt.mbMouseUp = bLeftDown; // What if ..? + if (pOut!=nullptr) + const_cast<SdrView*>(this)->SetActualWin(pOut); + SdrHitKind eHit=PickAnything(rMousePos,aVEvt); + SdrEventKind eEvent = aVEvt.meEvent; + switch (eEvent) + { + case SdrEventKind::BeginCreateObj: + return maCurrentCreatePointer; + case SdrEventKind::MarkObj: + return PointerStyle::Move; + case SdrEventKind::BeginMark: + return PointerStyle::Arrow; + case SdrEventKind::MarkPoint: + case SdrEventKind::MarkGluePoint: + return PointerStyle::MovePoint; + case SdrEventKind::BeginInsertObjPoint: + case SdrEventKind::BeginInsertGluePoint: + return PointerStyle::Cross; + case SdrEventKind::ExecuteUrl: + return PointerStyle::RefHand; + case SdrEventKind::BeginMacroObj: + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = aVEvt.maLogicPos; + aHitRec.nTol=mnHitTolLog; + aHitRec.pVisiLayer = &aVEvt.mpPV->GetVisibleLayers(); + aHitRec.pPageView = aVEvt.mpPV; + return aVEvt.mpObj->GetMacroPointer(aHitRec); + } + default: break; + } // switch + + switch(eHit) + { + case SdrHitKind::Cell: + return PointerStyle::Arrow; + case SdrHitKind::HelpLine : + return aVEvt.mpPV->GetHelpLines()[aVEvt.mnHlplIdx].GetPointer(); + case SdrHitKind::Gluepoint: + return PointerStyle::MovePoint; + case SdrHitKind::TextEdit : + case SdrHitKind::TextEditObj: + { + SdrTextObj* pText = DynCastSdrTextObj(aVEvt.mpObj); + if(pText && pText->HasText()) + { + OutlinerParaObject* pParaObj = pText->GetOutlinerParaObject(); + if(pParaObj && pParaObj->IsEffectivelyVertical()) + return PointerStyle::TextVertical; + } + return PointerStyle::Text; + } + default: break; + } + + bool bMarkHit=eHit==SdrHitKind::MarkedObject; + SdrHdl* pHdl = aVEvt.mpHdl; + // now check the pointers for dragging + if (pHdl!=nullptr || bMarkHit) { + SdrHdlKind eHdl= pHdl!=nullptr ? pHdl->GetKind() : SdrHdlKind::Move; + bool bCorner=pHdl!=nullptr && pHdl->IsCornerHdl(); + bool bVertex=pHdl!=nullptr && pHdl->IsVertexHdl(); + bool bMov=eHdl==SdrHdlKind::Move; + if (bMov && (meDragMode==SdrDragMode::Move || meDragMode==SdrDragMode::Resize || mbMarkedHitMovesAlways)) { + if (!IsMoveAllowed()) return PointerStyle::Arrow; // because double click or drag & drop is possible + return PointerStyle::Move; + } + switch (meDragMode) { + case SdrDragMode::Rotate: { + if ((bCorner || bMov) && !IsRotateAllowed(true)) + return PointerStyle::NotAllowed; + + // are 3D objects selected? + bool b3DObjSelected = false; + for (size_t a=0; !b3DObjSelected && a<GetMarkedObjectCount(); ++a) { + SdrObject* pObj = GetMarkedObjectByIndex(a); + if(DynCastE3dObject(pObj)) + b3DObjSelected = true; + } + // If we have a 3D object, go on despite !IsShearAllowed, + // because then we have a rotation instead of a shear. + if (bVertex && !IsShearAllowed() && !b3DObjSelected) + return PointerStyle::NotAllowed; + if (bMov) + return PointerStyle::Rotate; + } break; + case SdrDragMode::Shear: { + if (bCorner) { + if (!IsDistortAllowed(true) && !IsDistortAllowed()) return PointerStyle::NotAllowed; + else return PointerStyle::RefHand; + } + if (bVertex && !IsShearAllowed()) return PointerStyle::NotAllowed; + if (bMov) { + if (!IsMoveAllowed()) return PointerStyle::Arrow; // because double click or drag & drop is possible + return PointerStyle::Move; + } + } break; + case SdrDragMode::Mirror: { + if (bCorner || bVertex || bMov) { + SdrHdl* pH1=maHdlList.GetHdl(SdrHdlKind::Ref1); + SdrHdl* pH2=maHdlList.GetHdl(SdrHdlKind::Ref2); + bool b90=false; + bool b45=false; + if (pH1!=nullptr && pH2!=nullptr) { + Point aDif = pH2->GetPos()-pH1->GetPos(); + b90=(aDif.X()==0) || aDif.Y()==0; + b45=b90 || (std::abs(aDif.X())==std::abs(aDif.Y())); + } + bool bNo=false; + if (!IsMirrorAllowed(true,true)) bNo=true; // any mirroring is forbidden + if (!IsMirrorAllowed() && !b45) bNo=true; // mirroring freely is forbidden + if (!IsMirrorAllowed(true) && !b90) bNo=true; // mirroring horizontally/vertically is allowed + if (bNo) return PointerStyle::NotAllowed; + if (b90) { + return PointerStyle::Mirror; + } + return PointerStyle::Mirror; + } + } break; + + case SdrDragMode::Transparence: + { + if(!IsTransparenceAllowed()) + return PointerStyle::NotAllowed; + + return PointerStyle::RefHand; + } + + case SdrDragMode::Gradient: + { + if(!IsGradientAllowed()) + return PointerStyle::NotAllowed; + + return PointerStyle::RefHand; + } + + case SdrDragMode::Crook: { + if (bCorner || bVertex || bMov) { + if (!IsCrookAllowed(true) && !IsCrookAllowed()) return PointerStyle::NotAllowed; + return PointerStyle::Crook; + } + break; + } + + case SdrDragMode::Crop: + { + return PointerStyle::Crop; + } + + default: { + if ((bCorner || bVertex) && !IsResizeAllowed(true)) return PointerStyle::NotAllowed; + } + } + if (pHdl!=nullptr) return pHdl->GetPointer(); + if (bMov) { + if (!IsMoveAllowed()) return PointerStyle::Arrow; // because double click or drag & drop is possible + return PointerStyle::Move; + } + } + if (meEditMode==SdrViewEditMode::Create) return maCurrentCreatePointer; + return PointerStyle::Arrow; +} + +constexpr OUString STR_NOTHING = u"nothing"_ustr; +OUString SdrView::GetStatusText() +{ + OUString aName; + OUString aStr = STR_NOTHING; + + if (mpCurrentCreate!=nullptr) + { + aStr=mpCurrentCreate->getSpecialDragComment(maDragStat); + + if(aStr.isEmpty()) + { + aName = mpCurrentCreate->TakeObjNameSingul(); + aStr = SvxResId(STR_ViewCreateObj); + } + } + else if (mpCurrentSdrDragMethod) + { + if (mbInsPolyPoint || IsInsertGluePoint()) + { + aStr=maInsPointUndoStr; + } + else + { + if (maDragStat.IsMinMoved()) + { + SAL_INFO( + "svx.svdraw", + "(" << this << ") " << mpCurrentSdrDragMethod.get()); + aStr = mpCurrentSdrDragMethod->GetSdrDragComment(); + } + } + } + else if(IsMarkObj()) + { + if(AreObjectsMarked()) + { + aStr = SvxResId(STR_ViewMarkMoreObjs); + } + else + { + aStr = SvxResId(STR_ViewMarkObjs); + } + } + else if(IsMarkPoints()) + { + if(HasMarkedPoints()) + { + aStr = SvxResId(STR_ViewMarkMorePoints); + } + else + { + aStr = SvxResId(STR_ViewMarkPoints); + } + } else if (IsMarkGluePoints()) + { + if(HasMarkedGluePoints()) + { + aStr = SvxResId(STR_ViewMarkMoreGluePoints); + } + else + { + aStr = SvxResId(STR_ViewMarkGluePoints); + } + } + else if (IsTextEdit() && mpTextEditOutlinerView != nullptr) { + aStr=SvxResId(STR_ViewTextEdit); // "TextEdit - Row y, Column x"; + ESelection aSel(mpTextEditOutlinerView->GetSelection()); + tools::Long nPar = aSel.nEndPara,nLin=0,nCol=aSel.nEndPos; + if (aSel.nEndPara>0) { + for (sal_Int32 nParaNum=0; nParaNum<aSel.nEndPara; nParaNum++) { + nLin += mpTextEditOutliner->GetLineCount(nParaNum); + } + } + // A little imperfection: + // At the end of a line of any multi-line paragraph, we display the + // position of the next line of the same paragraph, if there is one. + sal_uInt16 nParaLine = 0; + sal_uLong nParaLineCount = mpTextEditOutliner->GetLineCount(aSel.nEndPara); + bool bBrk = false; + while (!bBrk) + { + sal_uInt16 nLen = mpTextEditOutliner->GetLineLen(aSel.nEndPara, nParaLine); + bool bLastLine = (nParaLine == nParaLineCount - 1); + if (nCol>nLen || (!bLastLine && nCol == nLen)) + { + nCol -= nLen; + nLin++; + nParaLine++; + } + else + bBrk = true; + + if (nLen == 0) + bBrk = true; // to be sure + } + + aStr = aStr.replaceFirst("%1", OUString::number(nPar + 1)); + aStr = aStr.replaceFirst("%2", OUString::number(nLin + 1)); + aStr = aStr.replaceFirst("%3", OUString::number(nCol + 1)); + +#ifdef DBG_UTIL + aStr += ", Level " + OUString::number(mpTextEditOutliner->GetDepth( aSel.nEndPara )); +#endif + } + + if(aStr == STR_NOTHING) + { + if (AreObjectsMarked()) { + aStr = ImpGetDescriptionString(STR_ViewMarked); + if (IsGluePointEditMode()) { + if (HasMarkedGluePoints()) { + aStr = ImpGetDescriptionString(STR_ViewMarked, ImpGetDescriptionOptions::GLUEPOINTS); + } + } else { + if (HasMarkedPoints()) { + aStr = ImpGetDescriptionString(STR_ViewMarked, ImpGetDescriptionOptions::POINTS); + } + } + } else { + aStr.clear(); + } + } + else if(!aName.isEmpty()) + { + aStr = aStr.replaceFirst("%1", aName); + } + + if(!aStr.isEmpty()) + { + // capitalize first letter + aStr = aStr.replaceAt(0, 1, OUString(aStr[0]).toAsciiUpperCase()); + } + return aStr; +} + +SdrViewContext SdrView::GetContext() const +{ + if( IsGluePointEditMode() ) + return SdrViewContext::GluePointEdit; + + const size_t nMarkCount = GetMarkedObjectCount(); + + if( HasMarkablePoints() && !IsFrameHandles() ) + { + bool bPath=true; + for( size_t nMarkNum = 0; nMarkNum < nMarkCount && bPath; ++nMarkNum ) + if (dynamic_cast<const SdrPathObj*>(GetMarkedObjectByIndex(nMarkNum)) == nullptr) + bPath=false; + + if( bPath ) + return SdrViewContext::PointEdit; + } + + if( GetMarkedObjectCount() ) + { + bool bGraf = true, bMedia = true, bTable = true; + + for( size_t nMarkNum = 0; nMarkNum < nMarkCount && ( bGraf || bMedia ); ++nMarkNum ) + { + const SdrObject* pMarkObj = GetMarkedObjectByIndex( nMarkNum ); + DBG_ASSERT( pMarkObj, "SdrView::GetContext(), null pointer in mark list!" ); + + if( !pMarkObj ) + continue; + + if( dynamic_cast<const SdrGrafObj*>( pMarkObj) == nullptr ) + bGraf = false; + + if( dynamic_cast<const SdrMediaObj*>( pMarkObj) == nullptr ) + bMedia = false; + + if( dynamic_cast<const sdr::table::SdrTableObj* >( pMarkObj ) == nullptr ) + bTable = false; + } + + if( bGraf ) + return SdrViewContext::Graphic; + else if( bMedia ) + return SdrViewContext::Media; + else if( bTable ) + return SdrViewContext::Table; + } + + return SdrViewContext::Standard; +} + +void SdrView::MarkAll() +{ + if (IsTextEdit()) { + GetTextEditOutlinerView()->SetSelection(ESelection(0,0,EE_PARA_ALL,EE_TEXTPOS_ALL)); + } else if (IsGluePointEditMode()) MarkAllGluePoints(); + else if (HasMarkablePoints()) MarkAllPoints(); + else { + // check for table + bool bMarkAll = true; + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + { + const SdrObject* pObj(rMarkList.GetMark(0)->GetMarkedSdrObj()); + SdrView* pView = this; + if (pObj && pView && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + mxSelectionController.clear(); + mxSelectionController = sdr::table::CreateTableController( + *pView, static_cast<const sdr::table::SdrTableObj&>(*pObj), + mxLastSelectionController); + + if (mxSelectionController.is()) + { + mxLastSelectionController.clear(); + mxSelectionController->onSelectAll(); + bMarkAll = false; + } + } + } + if ( bMarkAll ) + MarkAllObj(); + } +} + +void SdrView::UnmarkAll() +{ + if (IsTextEdit()) { + ESelection eSel=GetTextEditOutlinerView()->GetSelection(); + eSel.nStartPara=eSel.nEndPara; + eSel.nStartPos=eSel.nEndPos; + GetTextEditOutlinerView()->SetSelection(eSel); + } else if (HasMarkedGluePoints()) UnmarkAllGluePoints(); + else if (HasMarkedPoints()) UnmarkAllPoints(); // Marked, not Markable! + else UnmarkAllObj(); +} + +const tools::Rectangle& SdrView::GetMarkedRect() const +{ + if (IsGluePointEditMode() && HasMarkedGluePoints()) { + return GetMarkedGluePointsRect(); + } + if (HasMarkedPoints()) { + return GetMarkedPointsRect(); + } + return GetMarkedObjRect(); +} + +void SdrView::DeleteMarked() +{ + if (IsTextEdit()) + { + SdrObjEditView::KeyInput(KeyEvent(0, vcl::KeyCode(KeyFuncType::DELETE)), mpTextEditWin); + } + else + { + if( mxSelectionController.is() && mxSelectionController->DeleteMarked() ) + { + // action already performed by current selection controller, do nothing + } + else if (IsGluePointEditMode() && HasMarkedGluePoints()) + { + DeleteMarkedGluePoints(); + } + else if (GetContext()==SdrViewContext::PointEdit && HasMarkedPoints()) + { + DeleteMarkedPoints(); + } + else + { + DeleteMarkedObj(); + } + } +} + +bool SdrView::BegMark(const Point& rPnt, bool bAddMark, bool bUnmark) +{ + if (bUnmark) bAddMark=true; + if (IsGluePointEditMode()) { + if (!bAddMark) UnmarkAllGluePoints(); + return BegMarkGluePoints(rPnt,bUnmark); + } else if (HasMarkablePoints()) { + if (!bAddMark) UnmarkAllPoints(); + return BegMarkPoints(rPnt,bUnmark); + } else { + if (!bAddMark) UnmarkAllObj(); + BegMarkObj(rPnt,bUnmark); + return true; + } +} + +bool SdrView::MoveShapeHandle(const sal_uInt32 handleNum, const Point& aEndPoint, const sal_Int32 aObjectOrdNum) +{ + if (GetHdlList().IsMoveOutside()) + return false; + + if (!GetMarkedObjectList().GetMarkCount()) + return false; + + SdrHdl * pHdl = GetHdlList().GetHdl(handleNum); + if (pHdl == nullptr) + return false; + + SdrDragStat& rDragStat = const_cast<SdrDragStat&>(GetDragStat()); + // start dragging + BegDragObj(pHdl->GetPos(), nullptr, pHdl, 0); + if (!IsDragObj()) + return false; + + bool bWasNoSnap = rDragStat.IsNoSnap(); + bool bWasSnapEnabled = IsSnapEnabled(); + + // switch snapping off + if(!bWasNoSnap) + rDragStat.SetNoSnap(); + if(bWasSnapEnabled) + SetSnapEnabled(false); + + if (aObjectOrdNum != -1) + { + rDragStat.GetGlueOptions().objectOrdNum = aObjectOrdNum; + } + MovDragObj(aEndPoint); + EndDragObj(); + + // Clear Glue Options + rDragStat.GetGlueOptions().objectOrdNum = -1; + + if (!bWasNoSnap) + rDragStat.SetNoSnap(bWasNoSnap); + if (bWasSnapEnabled) + SetSnapEnabled(bWasSnapEnabled); + + return true; +} + +void SdrView::ConfigurationChanged( ::utl::ConfigurationBroadcaster*p, ConfigurationHints nHint) +{ + onAccessibilityOptionsChanged(); + SdrCreateView::ConfigurationChanged(p, nHint); +} + + +/** method is called whenever the global SvtAccessibilityOptions is changed */ +void SdrView::onAccessibilityOptionsChanged() +{ +} + +void SdrView::SetMasterPagePaintCaching(bool bOn) +{ + if(mbMasterPagePaintCaching == bOn) + return; + + mbMasterPagePaintCaching = bOn; + + // reset at all SdrPageWindows + SdrPageView* pPageView = GetSdrPageView(); + + if(!pPageView) + return; + + for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++) + { + SdrPageWindow* pPageWindow = pPageView->GetPageWindow(b); + assert(pPageWindow && "SdrView::SetMasterPagePaintCaching: Corrupt SdrPageWindow list (!)"); + + // force deletion of ObjectContact, so at re-display all VOCs + // will be re-created with updated flag setting + pPageWindow->ResetObjectContact(); + } + + // force redraw of this view + pPageView->InvalidateAllWin(); +} + +// Default ObjectContact is ObjectContactOfPageView +sdr::contact::ObjectContact* SdrView::createViewSpecificObjectContact( + SdrPageWindow& rPageWindow, + const char* pDebugName) const +{ + return new sdr::contact::ObjectContactOfPageView(rPageWindow, pDebugName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdviter.cxx b/svx/source/svdraw/svdviter.cxx new file mode 100644 index 0000000000..65450efb42 --- /dev/null +++ b/svx/source/svdraw/svdviter.cxx @@ -0,0 +1,126 @@ +/* -*- 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/svdviter.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdsob.hxx> + +static bool ImpCheckPageView(const SdrPage* pPage, const SdrObject* pObject, SdrPageView const* pPV) +{ + if (!pPage) + return true; + + bool bMaster(pPage->IsMasterPage()); + SdrPage* pPg = pPV->GetPage(); + + if (pPg == pPage) + { + if (pObject) + { + // Looking for an object? First, determine if it visible in + // this PageView. + return pObject->isVisibleOnAnyOfTheseLayers(pPV->GetVisibleLayers()); + } + else + { + return true; + } + } + else if (bMaster && (!pObject || !pObject->IsNotVisibleAsMaster())) + { + if (pPg->TRG_HasMasterPage()) + { + SdrPage& rMasterPage = pPg->TRG_GetMasterPage(); + + if (&rMasterPage == pPage) + { + // the page we're looking for is a master page in this PageView + if (pObject) + { + // Looking for an object? First, determine if it visible in + // this PageView. + SdrLayerIDSet aObjLay = pPV->GetVisibleLayers(); + aObjLay &= pPg->TRG_GetMasterPageVisibleLayers(); + if (pObject->isVisibleOnAnyOfTheseLayers(aObjLay)) + { + return true; + } // else, look at the next master page of this page... + } + else + { + return true; + } + } + } + } + + // master page forbidden or no fitting master page found + return false; +} + +namespace SdrViewIter +{ +void ForAllViews(const SdrPage* pPage, std::function<void(SdrView*)> f) +{ + if (!pPage) + return; + const SdrModel* pModel = &pPage->getSdrModelFromSdrPage(); + + pModel->ForAllListeners([&pPage, &f](SfxListener* pLs) { + if (!pLs->IsSdrView()) + return false; + SdrView* pCurrentView = static_cast<SdrView*>(pLs); + SdrPageView* pPV = pCurrentView->GetSdrPageView(); + + if (pPV && ImpCheckPageView(pPage, nullptr, pPV)) + { + f(pCurrentView); + } + return false; + }); +} + +void ForAllViews(const SdrObject* pObject, std::function<void(SdrView*)> f) +{ + if (!pObject) + return; + const SdrModel* pModel = &pObject->getSdrModelFromSdrObject(); + const SdrPage* pPage = pObject->getSdrPageFromSdrObject(); + if (!pPage) + return; + + pModel->ForAllListeners([&pPage, &pObject, &f](SfxListener* pLs) { + if (!pLs->IsSdrView()) + return false; + SdrView* pCurrentView = static_cast<SdrView*>(pLs); + SdrPageView* pPV = pCurrentView->GetSdrPageView(); + + if (pPV && ImpCheckPageView(pPage, pObject, pPV)) + { + f(pCurrentView); + } + return false; + }); +} +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/svdxcgv.cxx b/svx/source/svdraw/svdxcgv.cxx new file mode 100644 index 0000000000..050a997077 --- /dev/null +++ b/svx/source/svdraw/svdxcgv.cxx @@ -0,0 +1,791 @@ +/* -*- 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 <vector> +#include <unordered_set> +#include <editeng/editdata.hxx> +#include <rtl/strbuf.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xlineit0.hxx> +#include <svx/svdxcgv.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdomedia.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdopage.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdtrans.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <tools/bigint.hxx> +#include <clonelist.hxx> +#include <vcl/virdev.hxx> +#include <svl/style.hxx> +#include <fmobj.hxx> +#include <vcl/vectorgraphicdata.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/converters.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <sdr/contact/objectcontactofobjlistpainter.hxx> +#include <svx/sdr/contact/displayinfo.hxx> +#include <svx/svdotable.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <comphelper/lok.hxx> + +using namespace com::sun::star; + +SdrExchangeView::SdrExchangeView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: SdrObjEditView(rSdrModel, pOut) +{ +} + +bool SdrExchangeView::ImpLimitToWorkArea(Point& rPt) const +{ + bool bRet(false); + + if(!maMaxWorkArea.IsEmpty()) + { + if(rPt.X()<maMaxWorkArea.Left()) + { + rPt.setX( maMaxWorkArea.Left() ); + bRet = true; + } + + if(rPt.X()>maMaxWorkArea.Right()) + { + rPt.setX( maMaxWorkArea.Right() ); + bRet = true; + } + + if(rPt.Y()<maMaxWorkArea.Top()) + { + rPt.setY( maMaxWorkArea.Top() ); + bRet = true; + } + + if(rPt.Y()>maMaxWorkArea.Bottom()) + { + rPt.setY( maMaxWorkArea.Bottom() ); + bRet = true; + } + } + return bRet; +} + +void SdrExchangeView::ImpGetPasteObjList(Point& /*rPos*/, SdrObjList*& rpLst) +{ + if (rpLst==nullptr) + { + SdrPageView* pPV = GetSdrPageView(); + + if (pPV!=nullptr) { + rpLst=pPV->GetObjList(); + } + } +} + +bool SdrExchangeView::ImpGetPasteLayer(const SdrObjList* pObjList, SdrLayerID& rLayer) const +{ + bool bRet=false; + rLayer=SdrLayerID(0); + if (pObjList!=nullptr) { + const SdrPage* pPg=pObjList->getSdrPageFromSdrObjList(); + if (pPg!=nullptr) { + rLayer=pPg->GetLayerAdmin().GetLayerID(maActualLayer); + if (rLayer==SDRLAYER_NOTFOUND) rLayer=SdrLayerID(0); + SdrPageView* pPV = GetSdrPageView(); + if (pPV!=nullptr) { + bRet=!pPV->GetLockedLayers().IsSet(rLayer) && pPV->GetVisibleLayers().IsSet(rLayer); + } + } + } + return bRet; +} + +bool SdrExchangeView::Paste(const OUString& rStr, const Point& rPos, SdrObjList* pLst, SdrInsertFlags nOptions) +{ + if (rStr.isEmpty()) + return false; + + Point aPos(rPos); + ImpGetPasteObjList(aPos,pLst); + ImpLimitToWorkArea( aPos ); + if (pLst==nullptr) return false; + SdrLayerID nLayer; + if (!ImpGetPasteLayer(pLst,nLayer)) return false; + bool bUnmark = (nOptions & (SdrInsertFlags::DONTMARK|SdrInsertFlags::ADDMARK))==SdrInsertFlags::NONE && !IsTextEdit(); + if (bUnmark) UnmarkAllObj(); + tools::Rectangle aTextRect(0,0,500,500); + SdrPage* pPage=pLst->getSdrPageFromSdrObjList(); + if (pPage!=nullptr) { + aTextRect.SetSize(pPage->GetSize()); + } + rtl::Reference<SdrRectObj> pObj = new SdrRectObj( + getSdrModelFromSdrView(), + SdrObjKind::Text, + aTextRect); + + pObj->SetLayer(nLayer); + pObj->NbcSetText(rStr); // SetText before SetAttr, else SetAttr doesn't work! + if (mpDefaultStyleSheet!=nullptr) pObj->NbcSetStyleSheet(mpDefaultStyleSheet, false); + + pObj->SetMergedItemSet(maDefaultAttr); + + SfxItemSet aTempAttr(GetModel().GetItemPool()); // no fill, no line + aTempAttr.Put(XLineStyleItem(drawing::LineStyle_NONE)); + aTempAttr.Put(XFillStyleItem(drawing::FillStyle_NONE)); + + pObj->SetMergedItemSet(aTempAttr); + + pObj->FitFrameToTextSize(); + Size aSiz(pObj->GetLogicRect().GetSize()); + MapUnit eMap = GetModel().GetScaleUnit(); + ImpPasteObject(pObj.get(), *pLst, aPos, aSiz, MapMode(eMap), nOptions); + return true; +} + +bool SdrExchangeView::Paste(SvStream& rInput, EETextFormat eFormat, const Point& rPos, SdrObjList* pLst, SdrInsertFlags nOptions) +{ + Point aPos(rPos); + ImpGetPasteObjList(aPos,pLst); + ImpLimitToWorkArea( aPos ); + if (pLst==nullptr) return false; + SdrLayerID nLayer; + if (!ImpGetPasteLayer(pLst,nLayer)) return false; + bool bUnmark=(nOptions&(SdrInsertFlags::DONTMARK|SdrInsertFlags::ADDMARK))==SdrInsertFlags::NONE && !IsTextEdit(); + if (bUnmark) UnmarkAllObj(); + tools::Rectangle aTextRect(0,0,500,500); + SdrPage* pPage=pLst->getSdrPageFromSdrObjList(); + if (pPage!=nullptr) { + aTextRect.SetSize(pPage->GetSize()); + } + rtl::Reference<SdrRectObj> pObj = new SdrRectObj( + getSdrModelFromSdrView(), + SdrObjKind::Text, + aTextRect); + + pObj->SetLayer(nLayer); + if (mpDefaultStyleSheet!=nullptr) pObj->NbcSetStyleSheet(mpDefaultStyleSheet, false); + + pObj->SetMergedItemSet(maDefaultAttr); + + SfxItemSet aTempAttr(GetModel().GetItemPool()); // no fill, no line + aTempAttr.Put(XLineStyleItem(drawing::LineStyle_NONE)); + aTempAttr.Put(XFillStyleItem(drawing::FillStyle_NONE)); + + pObj->SetMergedItemSet(aTempAttr); + + pObj->NbcSetText(rInput,OUString(),eFormat); + pObj->FitFrameToTextSize(); + Size aSiz(pObj->GetLogicRect().GetSize()); + MapUnit eMap = GetModel().GetScaleUnit(); + ImpPasteObject(pObj.get(), *pLst, aPos, aSiz, MapMode(eMap), nOptions); + + // b4967543 + if(pObj->GetOutlinerParaObject()) + { + SdrOutliner& rOutliner = pObj->getSdrModelFromSdrObject().GetHitTestOutliner(); + rOutliner.SetText(*pObj->GetOutlinerParaObject()); + + if(1 == rOutliner.GetParagraphCount()) + { + SfxStyleSheet* pCandidate = rOutliner.GetStyleSheet(0); + + if(pCandidate) + { + if(pObj->getSdrModelFromSdrObject().GetStyleSheetPool() == pCandidate->GetPool()) + { + pObj->NbcSetStyleSheet(pCandidate, true); + } + } + } + } + + return true; +} + +bool SdrExchangeView::Paste( + const SdrModel& rMod, const Point& rPos, SdrObjList* pLst, SdrInsertFlags nOptions) +{ + const SdrModel* pSrcMod=&rMod; + if (pSrcMod == &GetModel()) + return false; // this can't work, right? + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(SvxResId(STR_ExchangePaste)); + + if( mxSelectionController.is() && mxSelectionController->PasteObjModel( rMod ) ) + { + if( bUndo ) + EndUndo(); + return true; + } + + Point aPos(rPos); + ImpGetPasteObjList(aPos,pLst); + SdrPageView* pMarkPV=nullptr; + SdrPageView* pPV = GetSdrPageView(); + + if(pPV && pPV->GetObjList() == pLst ) + pMarkPV=pPV; + + ImpLimitToWorkArea( aPos ); + if (pLst==nullptr) + return false; + + bool bUnmark=(nOptions&(SdrInsertFlags::DONTMARK|SdrInsertFlags::ADDMARK))==SdrInsertFlags::NONE && !IsTextEdit(); + if (bUnmark) + UnmarkAllObj(); + + // Rescale, if the Model uses a different MapUnit. + // Calculate the necessary factors first. + MapUnit eSrcUnit = pSrcMod->GetScaleUnit(); + MapUnit eDstUnit = GetModel().GetScaleUnit(); + bool bResize=eSrcUnit!=eDstUnit; + Fraction aXResize,aYResize; + Point aPt0; + if (bResize) + { + FrPair aResize(GetMapFactor(eSrcUnit,eDstUnit)); + aXResize=aResize.X(); + aYResize=aResize.Y(); + } + SdrObjList* pDstLst=pLst; + sal_uInt16 nPg,nPgCount=pSrcMod->GetPageCount(); + for (nPg=0; nPg<nPgCount; nPg++) + { + const SdrPage* pSrcPg=pSrcMod->GetPage(nPg); + + // Use SnapRect, not BoundRect here + tools::Rectangle aR=pSrcPg->GetAllObjSnapRect(); + + if (bResize) + ResizeRect(aR,aPt0,aXResize,aYResize); + Point aDist(aPos-aR.Center()); + Size aSiz(aDist.X(),aDist.Y()); + size_t nCloneErrCnt = 0; + const size_t nObjCount = pSrcPg->GetObjCount(); + bool bMark = pMarkPV!=nullptr && !IsTextEdit() && (nOptions&SdrInsertFlags::DONTMARK)==SdrInsertFlags::NONE; + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + CloneList aCloneList; + std::unordered_set<rtl::OUString> aNameSet; + for (size_t nOb=0; nOb<nObjCount; ++nOb) + { + const SdrObject* pSrcOb=pSrcPg->GetObj(nOb); + + rtl::Reference<SdrObject> pNewObj(pSrcOb->CloneSdrObject(GetModel())); + + if (pNewObj!=nullptr) + { + if(bResize) + { + pNewObj->getSdrModelFromSdrObject().SetPasteResize(true); + pNewObj->NbcResize(aPt0,aXResize,aYResize); + pNewObj->getSdrModelFromSdrObject().SetPasteResize(false); + } + + // #i39861# + pNewObj->NbcMove(aSiz); + + const SdrPage* pPg = pDstLst->getSdrPageFromSdrObjList(); + + if(pPg) + { + // #i72535# + const SdrLayerAdmin& rAd = pPg->GetLayerAdmin(); + SdrLayerID nLayer(0); + + if(dynamic_cast<const FmFormObj*>( pNewObj.get()) != nullptr) + { + // for FormControls, force to form layer + nLayer = rAd.GetLayerID(rAd.GetControlLayerName()); + } + else + { + nLayer = rAd.GetLayerID(maActualLayer); + } + + if(SDRLAYER_NOTFOUND == nLayer) + { + nLayer = SdrLayerID(0); + } + + pNewObj->SetLayer(nLayer); + } + + pDstLst->InsertObjectThenMakeNameUnique(pNewObj.get(), aNameSet); + + if( bUndo ) + AddUndo(getSdrModelFromSdrView().GetSdrUndoFactory().CreateUndoNewObject(*pNewObj)); + + if (bMark) { + // Don't already set Markhandles! + // That is instead being done by ModelHasChanged in MarkView. + MarkObj(pNewObj.get(),pMarkPV,false,true); + } + + // #i13033# + aCloneList.AddPair(pSrcOb, pNewObj.get()); + } + else + { + nCloneErrCnt++; + } + } + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + aCloneList.CopyConnections(); + + if(0 != nCloneErrCnt) + { +#ifdef DBG_UTIL + OStringBuffer aStr("SdrExchangeView::Paste(): Error when cloning "); + + if(nCloneErrCnt == 1) + { + aStr.append("a drawing object."); + } + else + { + aStr.append(OString::number(static_cast<sal_Int32>(nCloneErrCnt)) + + " drawing objects."); + } + + aStr.append(" Not copying object connectors."); + + OSL_FAIL(aStr.getStr()); +#endif + } + } + + if( bUndo ) + EndUndo(); + + return true; +} + +void SdrExchangeView::ImpPasteObject(SdrObject* pObj, SdrObjList& rLst, const Point& rCenter, const Size& rSiz, const MapMode& rMap, SdrInsertFlags nOptions) +{ + BigInt nSizX(rSiz.Width()); + BigInt nSizY(rSiz.Height()); + MapUnit eSrcMU=rMap.GetMapUnit(); + MapUnit eDstMU = GetModel().GetScaleUnit(); + FrPair aMapFact(GetMapFactor(eSrcMU,eDstMU)); + nSizX *= double(aMapFact.X() * rMap.GetScaleX()); + nSizY *= double(aMapFact.Y() * rMap.GetScaleY()); + tools::Long xs=nSizX; + tools::Long ys=nSizY; + // set the pos to 0, 0 for online case + bool isLOK = comphelper::LibreOfficeKit::isActive(); + Point aPos(isLOK ? 0 : rCenter.X()-xs/2, isLOK ? 0 : rCenter.Y()-ys/2); + tools::Rectangle aR(aPos.X(),aPos.Y(),aPos.X()+xs,aPos.Y()+ys); + pObj->SetLogicRect(aR); + rLst.InsertObject(pObj, SAL_MAX_SIZE); + + if( IsUndoEnabled() ) + AddUndo(getSdrModelFromSdrView().GetSdrUndoFactory().CreateUndoNewObject(*pObj)); + + SdrPageView* pMarkPV=nullptr; + SdrPageView* pPV = GetSdrPageView(); + + if(pPV && pPV->GetObjList()==&rLst) + pMarkPV=pPV; + + bool bMark = pMarkPV!=nullptr && !IsTextEdit() && (nOptions&SdrInsertFlags::DONTMARK)==SdrInsertFlags::NONE; + if (bMark) + { // select object the first PageView we found + MarkObj(pObj,pMarkPV); + } +} + +BitmapEx SdrExchangeView::GetMarkedObjBitmapEx(bool bNoVDevIfOneBmpMarked, const sal_uInt32 nMaximumQuadraticPixels, const std::optional<Size>& rTargetDPI) const +{ + BitmapEx aBmp; + + if( AreObjectsMarked() ) + { + if(1 == GetMarkedObjectCount()) + { + if(bNoVDevIfOneBmpMarked) + { + SdrObject* pGrafObjTmp = GetMarkedObjectByIndex( 0 ); + SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>( pGrafObjTmp ); + + if( pGrafObj && ( pGrafObj->GetGraphicType() == GraphicType::Bitmap ) ) + { + aBmp = pGrafObj->GetTransformedGraphic().GetBitmapEx(); + } + } + else + { + const SdrGrafObj* pSdrGrafObj = dynamic_cast< const SdrGrafObj* >(GetMarkedObjectByIndex(0)); + + if(pSdrGrafObj && pSdrGrafObj->isEmbeddedVectorGraphicData()) + { + aBmp = pSdrGrafObj->GetGraphic().getVectorGraphicData()->getReplacement(); + } + } + } + + if( aBmp.IsEmpty() ) + { + // choose conversion directly using primitives to bitmap to avoid + // rendering errors with tiled bitmap fills (these will be tiled in a + // in-between metafile, but tend to show 'gaps' since the target is *no* + // bitmap rendering) + ::std::vector< SdrObject* > aSdrObjects(GetMarkedObjects()); + const sal_uInt32 nCount(aSdrObjects.size()); + + if(nCount) + { + // collect sub-primitives as group objects, thus no expensive append + // to existing sequence is needed + drawinglayer::primitive2d::Primitive2DContainer xPrimitives(nCount); + + for(sal_uInt32 a(0); a < nCount; a++) + { + SdrObject* pCandidate = aSdrObjects[a]; + SdrGrafObj* pSdrGrafObj = dynamic_cast< SdrGrafObj* >(pCandidate); + + if(pSdrGrafObj) + { + // #122753# To ensure existence of graphic content, force swap in + pSdrGrafObj->ForceSwapIn(); + } + + drawinglayer::primitive2d::Primitive2DContainer xRetval; + pCandidate->GetViewContact().getViewIndependentPrimitive2DContainer(xRetval); + xPrimitives[a] = new drawinglayer::primitive2d::GroupPrimitive2D( + std::move(xRetval)); + } + + // get logic range + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D)); + + if(!aRange.isEmpty()) + { + o3tl::Length eRangeUnit = o3tl::Length::mm100; + + if (GetModel().IsWriter()) + { + eRangeUnit = o3tl::Length::twip; + } + + // if we have geometry and it has a range, convert to BitmapEx using + // common tooling + aBmp = drawinglayer::convertPrimitive2DContainerToBitmapEx( + std::move(xPrimitives), + aRange, + nMaximumQuadraticPixels, + eRangeUnit, + rTargetDPI); + } + } + } + } + + return aBmp; +} + + +GDIMetaFile SdrExchangeView::GetMarkedObjMetaFile(bool bNoVDevIfOneMtfMarked) const +{ + GDIMetaFile aMtf; + + if( AreObjectsMarked() ) + { + tools::Rectangle aBound( GetMarkedObjBoundRect() ); + Size aBoundSize( aBound.GetWidth(), aBound.GetHeight() ); + MapMode aMap(GetModel().GetScaleUnit()); + + if( bNoVDevIfOneMtfMarked ) + { + SdrObject* pGrafObjTmp = GetMarkedObjectByIndex( 0 ); + SdrGrafObj* pGrafObj = ( GetMarkedObjectCount() ==1 ) ? dynamic_cast<SdrGrafObj*>( pGrafObjTmp ) : nullptr; + + if( pGrafObj ) + { + Graphic aGraphic( pGrafObj->GetTransformedGraphic() ); + + // #119735# just use GetGDIMetaFile, it will create a buffered version of contained bitmap now automatically + aMtf = aGraphic.GetGDIMetaFile(); + } + } + + if( !aMtf.GetActionSize() ) + { + ScopedVclPtrInstance< VirtualDevice > pOut; + const Size aDummySize(2, 2); + + pOut->SetOutputSizePixel(aDummySize); + pOut->EnableOutput(false); + pOut->SetMapMode(aMap); + aMtf.Clear(); + aMtf.Record(pOut); + + DrawMarkedObj(*pOut); + + aMtf.Stop(); + aMtf.WindStart(); + + // moving the result is more reliable then setting a relative MapMode at the VDev (used + // before), also see #i99268# in GetObjGraphic() below. Some draw actions at + // the OutDev are simply not handled correctly when a MapMode is set at the + // target device, e.g. MetaFloatTransparentAction. Even the Move for this action + // was missing the manipulation of the embedded Metafile + aMtf.Move(-aBound.Left(), -aBound.Top()); + + aMtf.SetPrefMapMode( aMap ); + + // removed PrefSize extension. It is principally wrong to set a reduced size at + // the created MetaFile. The mentioned errors occur at output time since the integer + // MapModes from VCL lead to errors. It is now corrected in the VCLRenderer for + // primitives (and may later be done in breaking up a MetaFile to primitives) + aMtf.SetPrefSize(aBoundSize); + } + } + + return aMtf; +} + + +Graphic SdrExchangeView::GetAllMarkedGraphic() const +{ + Graphic aRet; + + if( AreObjectsMarked() ) + { + if( ( 1 == GetMarkedObjectCount() ) && GetSdrMarkByIndex( 0 ) ) + aRet = SdrExchangeView::GetObjGraphic(*GetMarkedObjectByIndex(0)); + else + aRet = GetMarkedObjMetaFile(); + } + + return aRet; +} + + +// tdf#155479 bSVG: need to know it's SVG export, default is false +Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject, bool bSVG) +{ + Graphic aRet; + + // try to get a graphic from the object first + const SdrGrafObj* pSdrGrafObj(dynamic_cast< const SdrGrafObj* >(&rSdrObject)); + const SdrOle2Obj* pSdrOle2Obj(dynamic_cast< const SdrOle2Obj* >(&rSdrObject)); + + if(pSdrGrafObj) + { + if(pSdrGrafObj->isEmbeddedVectorGraphicData()) + { + // get Metafile for Svg content + aRet = pSdrGrafObj->getMetafileFromEmbeddedVectorGraphicData(); + } + else + { + // Make behaviour coherent with metafile + // recording below (which of course also takes + // view-transformed objects) + aRet = pSdrGrafObj->GetTransformedGraphic(); + } + } + else if(pSdrOle2Obj) + { + if(pSdrOle2Obj->GetGraphic()) + { + aRet = *pSdrOle2Obj->GetGraphic(); + } + } + else + { + // Support extracting a snapshot from video media, if possible. + const SdrMediaObj* pSdrMediaObj = dynamic_cast<const SdrMediaObj*>(&rSdrObject); + if (pSdrMediaObj) + { + const css::uno::Reference<css::graphic::XGraphic>& xGraphic + = pSdrMediaObj->getSnapshot(); + if (xGraphic.is()) + aRet = Graphic(xGraphic); + } + } + + // if graphic could not be retrieved => go the hard way and create a MetaFile + if((GraphicType::NONE == aRet.GetType()) || (GraphicType::Default == aRet.GetType())) + { + ScopedVclPtrInstance< VirtualDevice > pOut; + GDIMetaFile aMtf; + const tools::Rectangle aBoundRect(rSdrObject.GetCurrentBoundRect()); + const MapMode aMap(rSdrObject.getSdrModelFromSdrObject().GetScaleUnit()); + + pOut->EnableOutput(false); + pOut->SetMapMode(aMap); + aMtf.Record(pOut); + aMtf.setSVG(bSVG); + rSdrObject.SingleObjectPainter(*pOut); + aMtf.Stop(); + aMtf.WindStart(); + + // #i99268# replace the original offset from using XOutDev's SetOffset + // NOT (as tried with #i92760#) with another MapMode which gets recorded + // by the Metafile itself (what always leads to problems), but by + // moving the result directly + aMtf.Move(-aBoundRect.Left(), -aBoundRect.Top()); + aMtf.SetPrefMapMode(aMap); + aMtf.SetPrefSize(aBoundRect.GetSize()); + + if(aMtf.GetActionSize()) + { + aRet = aMtf; + } + } + + return aRet; +} + + +::std::vector< SdrObject* > SdrExchangeView::GetMarkedObjects() const +{ + SortMarkedObjects(); + ::std::vector< SdrObject* > aRetval; + + ::std::vector< ::std::vector< SdrMark* > > aObjVectors( 2 ); + ::std::vector< SdrMark* >& rObjVector1 = aObjVectors[ 0 ]; + ::std::vector< SdrMark* >& rObjVector2 = aObjVectors[ 1 ]; + const SdrLayerAdmin& rLayerAdmin = GetModel().GetLayerAdmin(); + const SdrLayerID nControlLayerId = rLayerAdmin.GetLayerID( rLayerAdmin.GetControlLayerName() ); + + for( size_t n = 0, nCount = GetMarkedObjectCount(); n < nCount; ++n ) + { + SdrMark* pMark = GetSdrMarkByIndex( n ); + + // paint objects on control layer on top of all other objects + if( nControlLayerId == pMark->GetMarkedSdrObj()->GetLayer() ) + rObjVector2.push_back( pMark ); + else + rObjVector1.push_back( pMark ); + } + + for(const std::vector<SdrMark*> & rObjVector : aObjVectors) + { + for(SdrMark* pMark : rObjVector) + { + aRetval.push_back(pMark->GetMarkedSdrObj()); + } + } + + return aRetval; +} + + +void SdrExchangeView::DrawMarkedObj(OutputDevice& rOut) const +{ + ::std::vector< SdrObject* > aSdrObjects(GetMarkedObjects()); + + if(!aSdrObjects.empty()) + { + sdr::contact::ObjectContactOfObjListPainter aPainter(rOut, std::move(aSdrObjects), aSdrObjects[0]->getSdrPageFromSdrObject()); + sdr::contact::DisplayInfo aDisplayInfo; + + // do processing + aPainter.ProcessDisplay(aDisplayInfo); + } +} + +std::unique_ptr<SdrModel> SdrExchangeView::CreateMarkedObjModel() const +{ + // Sorting the MarkList here might be problematic in the future, so + // use a copy. + SortMarkedObjects(); + std::unique_ptr<SdrModel> pNewModel(GetModel().AllocModel()); + rtl::Reference<SdrPage> pNewPage = pNewModel->AllocPage(false); + pNewModel->InsertPage(pNewPage.get()); + ::std::vector< SdrObject* > aSdrObjects(GetMarkedObjects()); + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + CloneList aCloneList; + + for(SdrObject* pObj : aSdrObjects) + { + rtl::Reference<SdrObject> pNewObj; + + if(nullptr != dynamic_cast< const SdrPageObj* >(pObj)) + { + // convert SdrPageObj's to a graphic representation, because + // virtual connection to referenced page gets lost in new model + pNewObj = new SdrGrafObj( + *pNewModel, + GetObjGraphic(*pObj), + pObj->GetLogicRect()); + } + else if(nullptr != dynamic_cast< const sdr::table::SdrTableObj* >(pObj)) + { + // check if we have a valid selection *different* from whole table + // being selected + if(mxSelectionController.is()) + { + pNewObj = mxSelectionController->GetMarkedSdrObjClone(*pNewModel); + } + } + + if(!pNewObj) + { + // not cloned yet + if(pObj->GetObjIdentifier() == SdrObjKind::OLE2 && nullptr == GetModel().GetPersist()) + { + // tdf#125520 - former fix was wrong, the SdrModel + // has to have a GetPersist() already, see task. + // We can still warn here when this is not the case + SAL_WARN( "svx", "OLE gets cloned Persist, EmbeddedObjectContainer will not be copied" ); + } + + // use default way + pNewObj = pObj->CloneSdrObject(*pNewModel); + } + + if(pNewObj) + { + pNewPage->InsertObject(pNewObj.get(), SAL_MAX_SIZE); + + // #i13033# + aCloneList.AddPair(pObj, pNewObj.get()); + } + } + + // #i13033# + // New mechanism to re-create the connections of cloned connectors + aCloneList.CopyConnections(); + + return pNewModel; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/textchain.cxx b/svx/source/svdraw/textchain.cxx new file mode 100644 index 0000000000..32c1f9b4e3 --- /dev/null +++ b/svx/source/svdraw/textchain.cxx @@ -0,0 +1,128 @@ +/* -*- 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 <textchain.hxx> +#include <svx/svdotext.hxx> + +/* + * Definition of Properties Interface +*/ + +CursorChainingEvent const & TextChain::GetCursorEvent(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aCursorEvent; +} +void TextChain::SetCursorEvent(const SdrTextObj *pTarget, CursorChainingEvent const & rPropParam) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aCursorEvent = rPropParam; +} + +bool TextChain::GetNilChainingEvent(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aNilChainingEvent; +} +void TextChain::SetNilChainingEvent(const SdrTextObj *pTarget, bool b) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aNilChainingEvent = b; +} + +ESelection const & TextChain::GetPreChainingSel(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aPreChainingSel; +} +void TextChain::SetPreChainingSel(const SdrTextObj *pTarget, ESelection const & rPropParam) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aPreChainingSel = rPropParam; +} + +ESelection const & TextChain::GetPostChainingSel(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aPostChainingSel; +} +void TextChain::SetPostChainingSel(const SdrTextObj *pTarget, ESelection const & rPropParam) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aPostChainingSel = rPropParam; +} + +bool TextChain::GetIsPartOfLastParaInNextLink(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aIsPartOfLastParaInNextLink; +} +void TextChain::SetIsPartOfLastParaInNextLink(const SdrTextObj *pTarget, bool b) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aIsPartOfLastParaInNextLink = b; +} + +bool TextChain::GetSwitchingToNextBox(const SdrTextObj *pTarget) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + return pLinkProperties->aSwitchingToNextBox; +} +void TextChain::SetSwitchingToNextBox(const SdrTextObj *pTarget, bool b) +{ + ImpChainLinkProperties *pLinkProperties = GetLinkProperties(pTarget); + pLinkProperties->aSwitchingToNextBox = b; +} + +/* End Definition of Properties Interface */ + +/* TextChain */ + +// NOTE: All getters in the class assume that the guy is in the chain + +TextChain::TextChain() +{ +} + +TextChain::~TextChain() +{ + // XXX: Should free all LinkProperties +} + +namespace { + +ChainLinkId GetId(const SdrTextObj *pLink) +{ + return pLink->GetName(); +} + +} + +ImpChainLinkProperties *TextChain::GetLinkProperties(const SdrTextObj *pLink) +{ + // if the guy does not already have properties in the map make them + ChainLinkId aLinkId = GetId(pLink); + if (maLinkPropertiesMap.find(aLinkId) == maLinkPropertiesMap.end()) { + maLinkPropertiesMap[aLinkId] = new ImpChainLinkProperties; + } + + return maLinkPropertiesMap[aLinkId]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/textchaincursor.cxx b/svx/source/svdraw/textchaincursor.cxx new file mode 100644 index 0000000000..51c4d1d8ef --- /dev/null +++ b/svx/source/svdraw/textchaincursor.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <textchain.hxx> +#include <textchaincursor.hxx> +#include <svx/svdedxv.hxx> +#include <svx/svdoutl.hxx> +#include <vcl/event.hxx> + +// XXX: Possible duplication of code in behavior with stuff in ImpEditView (or ImpEditEngine) and OutlinerView + +// XXX: We violate Demeter's Law several times here, I'm afraid + +TextChainCursorManager::TextChainCursorManager(SdrObjEditView *pEditView, const SdrTextObj *pTextObj) : + mpEditView(pEditView), + mpTextObj(pTextObj), + mbHandlingDel(false) +{ + assert(mpEditView); + assert(mpTextObj); + +} + +bool TextChainCursorManager::HandleKeyEvent( const KeyEvent& rKEvt ) +{ + ESelection aNewSel; + CursorChainingEvent aCursorEvent; + + // check what the cursor/event situation looks like + bool bCompletelyHandled = false; + impDetectEvent(rKEvt, aCursorEvent, aNewSel, bCompletelyHandled); + + if (aCursorEvent == CursorChainingEvent::NULL_EVENT) + return false; + else { + HandleCursorEvent(aCursorEvent, aNewSel); + // return value depends on the situation we are in + return bCompletelyHandled; + } +} + +void TextChainCursorManager::impDetectEvent(const KeyEvent& rKEvt, + CursorChainingEvent& rOutCursorEvt, + ESelection& rOutSel, + bool& rOutHandled) +{ + SdrOutliner *pOutl = mpEditView->GetTextEditOutliner(); + OutlinerView *pOLV = mpEditView->GetTextEditOutlinerView(); + + SdrTextObj *pNextLink = mpTextObj->GetNextLinkInChain(); + SdrTextObj *pPrevLink = mpTextObj->GetPrevLinkInChain(); + + KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction(); + + // We need to have this KeyFuncType + if (eFunc != KeyFuncType::DONTKNOW && eFunc != KeyFuncType::DELETE) + { + rOutCursorEvt = CursorChainingEvent::NULL_EVENT; + return; + } + + sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode(); + ESelection aCurSel = pOLV->GetSelection(); + + ESelection aEndSelPrevBox(100000, 100000); + + sal_Int32 nLastPara = pOutl->GetParagraphCount()-1; + OUString aLastParaText = pOutl->GetText(pOutl->GetParagraph(nLastPara)); + sal_Int32 nLastParaLen = aLastParaText.getLength(); + + ESelection aEndSel(nLastPara, nLastParaLen); + bool bAtEndOfTextContent = aCurSel == aEndSel; + + // Possibility: Are we "pushing" at the end of the object? + if (nCode == KEY_RIGHT && bAtEndOfTextContent && pNextLink) + { + rOutCursorEvt = CursorChainingEvent::TO_NEXT_LINK; + // Selection unchanged: we are at the beginning of the box + rOutHandled = true; // Nothing more to do than move cursor + return; + } + + // Possibility: Are we "pushing" at the end of the object? + if (eFunc == KeyFuncType::DELETE && bAtEndOfTextContent && pNextLink) + { + rOutCursorEvt = CursorChainingEvent::TO_NEXT_LINK; + // Selection unchanged: we are at the beginning of the box + rOutHandled = false; // We still need to delete the characters + mbHandlingDel = true; + return; + } + + ESelection aStartSel(0, 0); + bool bAtStartOfTextContent = aCurSel == aStartSel; + + // Possibility: Are we "pushing" at the start of the object? + if (nCode == KEY_LEFT && bAtStartOfTextContent && pPrevLink) + { + rOutCursorEvt = CursorChainingEvent::TO_PREV_LINK; + rOutSel = aEndSelPrevBox; // Set at end of selection + rOutHandled = true; // Nothing more to do than move cursor + return; + } + + // Possibility: Are we "pushing" at the start of the object and deleting left? + if (nCode == KEY_BACKSPACE && bAtStartOfTextContent && pPrevLink) + { + rOutCursorEvt = CursorChainingEvent::TO_PREV_LINK; + rOutSel = aEndSelPrevBox; // Set at end of selection + rOutHandled = false; // We need to delete characters after moving cursor + return; + } + + // If arrived here there is no event detected + rOutCursorEvt = CursorChainingEvent::NULL_EVENT; + +} + +void TextChainCursorManager::HandleCursorEventAfterChaining( + const CursorChainingEvent aCurEvt, + const ESelection& aNewSel) + +{ + // Special case for DELETE handling: we need to get back at the end of the prev box + if (mbHandlingDel) { + // reset flag + mbHandlingDel = false; + + // Move to end of prev box + SdrTextObj *pPrevLink = mpTextObj->GetPrevLinkInChain(); + ESelection aEndSel(100000, 100000); + impChangeEditingTextObj(pPrevLink, aEndSel); + return; + } + + // Standard handling + HandleCursorEvent(aCurEvt, aNewSel); +} + + +void TextChainCursorManager::HandleCursorEvent( + const CursorChainingEvent aCurEvt, + const ESelection& aNewSel) + +{ + + OutlinerView* pOLV = mpEditView->GetTextEditOutlinerView(); + SdrTextObj *pNextLink = mpTextObj->GetNextLinkInChain(); + SdrTextObj *pPrevLink = mpTextObj->GetPrevLinkInChain(); + + + switch ( aCurEvt ) { + case CursorChainingEvent::UNCHANGED: + // Set same selection as before the chaining (which is saved as PostChainingSel) + // We need an explicit set because the Outliner is messed up + // after text transfer and otherwise it brings us at arbitrary positions. + pOLV->SetSelection(aNewSel); + break; + case CursorChainingEvent::TO_NEXT_LINK: + mpTextObj->GetTextChain()->SetSwitchingToNextBox(mpTextObj, true); + impChangeEditingTextObj(pNextLink, aNewSel); + break; + case CursorChainingEvent::TO_PREV_LINK: + impChangeEditingTextObj(pPrevLink, aNewSel); + break; + case CursorChainingEvent::NULL_EVENT: + // Do nothing here + break; + } + +} + +void TextChainCursorManager::impChangeEditingTextObj(SdrTextObj *pTargetTextObj, ESelection aNewSel) +{ + assert(pTargetTextObj); + + mpEditView->SdrEndTextEdit(); + mpEditView->SdrBeginTextEdit(pTargetTextObj); + // OutlinerView has changed, so we update the pointer + OutlinerView *pOLV = mpEditView->GetTextEditOutlinerView(); + pOLV->SetSelection(aNewSel); + + // Update reference text obj + mpTextObj = pTargetTextObj; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/textchainflow.cxx b/svx/source/svdraw/textchainflow.cxx new file mode 100644 index 0000000000..9763ea5015 --- /dev/null +++ b/svx/source/svdraw/textchainflow.cxx @@ -0,0 +1,314 @@ +/* -*- 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/svdotext.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editobj.hxx> +#include <editeng/overflowingtxt.hxx> +#include <textchainflow.hxx> +#include <sal/log.hxx> + +TextChainFlow::TextChainFlow(SdrTextObj *pChainTarget) + : mpTargetLink(pChainTarget) +{ + SAL_INFO("svx.chaining", "[TEXTCHAINFLOW] Creating a new TextChainFlow"); + + mpTextChain = mpTargetLink->GetTextChain(); + mpNextLink = mpTargetLink->GetNextLinkInChain(); + + bUnderflow = bOverflow = false; + + mbOFisUFinduced = false; + + mpOverflChText = nullptr; + mpUnderflChText = nullptr; + + mbPossiblyCursorOut = false; +} + + +TextChainFlow::~TextChainFlow() +{ + mpOverflChText.reset(); + mpUnderflChText.reset(); +} + +void TextChainFlow::impSetFlowOutlinerParams(SdrOutliner *, SdrOutliner *) +{ + // Nothing to do if not in editing mode +} + +/* + * Check for overflow in the state of pFlowOutl. + * If pParamOutl is not NULL sets some parameters from there. + * This is useful in case the outliner is not set for overflow + * (e.g. in editing mode we check for overflow in drawing outl but + * parameters come from editing outliner) +*/ +void TextChainFlow::impCheckForFlowEvents(SdrOutliner *pFlowOutl, SdrOutliner *pParamOutl) +{ + bool bOldUpdateMode = pFlowOutl->IsUpdateLayout(); + + // XXX: This could be reorganized moving most of this stuff inside EditingTextChainFlow + if (pParamOutl != nullptr) + { + // We need this since it's required to check overflow + pFlowOutl->SetUpdateLayout(true); + + // XXX: does this work if you do it before setting the text? Seems so. + impSetFlowOutlinerParams(pFlowOutl, pParamOutl); + } + + bool bIsPageOverflow = pFlowOutl->IsPageOverflow(); + + // NOTE: overflow and underflow cannot be both true + bOverflow = bIsPageOverflow && mpNextLink; + bUnderflow = !bIsPageOverflow && mpNextLink && mpNextLink->HasText(); + + // Get old state on whether to merge para-s or not + // NOTE: We handle UF/OF using the _old_ state. The new one is simply saved + bool bMustMergeParaAmongLinks = GetTextChain()->GetIsPartOfLastParaInNextLink(mpTargetLink); + + // Set (Non)OverflowingTxt here (if any) + + // If we had an underflow before we have to deep merge paras anyway + bool bMustMergeParaOF = bMustMergeParaAmongLinks || mbOFisUFinduced; + + mpOverflChText.reset( bOverflow ? + new OFlowChainedText(pFlowOutl, bMustMergeParaOF) : + nullptr ); + + // Set current underflowing text (if any) + mpUnderflChText.reset( bUnderflow ? + new UFlowChainedText(pFlowOutl, bMustMergeParaAmongLinks) : + nullptr ); + + // Reset update mode // Reset it here because we use WriteRTF (needing updatemode = true) in the two constructors above + if (!bOldUpdateMode) // Reset only if the old value was false + pFlowOutl->SetUpdateLayout(bOldUpdateMode); + + // NOTE: Must be called after mp*ChText abd b*flow have been set but before mbOFisUFinduced is reset + impUpdateCursorInfo(); + + // To check whether an overflow is underflow induced or not (useful in cursor checking) + mbOFisUFinduced = bUnderflow; +} + +void TextChainFlow::impUpdateCursorInfo() +{ + // XXX: Maybe we can get rid of mbOFisUFinduced by not allowing handling of more than one event by the same TextChainFlow + // if this is not an OF triggered during an UF + + mbPossiblyCursorOut = bOverflow; + + if(mbPossiblyCursorOut ) { + maOverflowPosSel = mpOverflChText->GetOverflowPointSel(); + ESelection aSelAtUFTime = GetTextChain()->GetPreChainingSel(GetLinkTarget()); + // Might be an invalid selection if the cursor at UF time was before + // the (possibly UF-induced) Overflowing point but we don't use it in that case + maPostChainingSel = ESelection(aSelAtUFTime.nStartPara-maOverflowPosSel.nStartPara, + aSelAtUFTime.nStartPos-maOverflowPosSel.nStartPos ); + } + + // XXX: It may not be necessary anymore to keep this method separated from EditingTextChainFlow::impBroadcastCursorInfo +} + +void TextChainFlow::CheckForFlowEvents(SdrOutliner *pFlowOutl) +{ + impCheckForFlowEvents(pFlowOutl, nullptr); +} + + +bool TextChainFlow::IsOverflow() const +{ + return bOverflow; +} + +bool TextChainFlow::IsUnderflow() const +{ + return bUnderflow; +} + + +// XXX: In editing mode you need to get "underflowing" text from editing outliner, so it's kinda separate from the drawing one! + +// XXX:Would it be possible to unify underflow and its possibly following overflow? +void TextChainFlow::ExecuteUnderflow(SdrOutliner *pOutl) +{ + //GetTextChain()->SetNilChainingEvent(mpTargetLink, true); + // making whole text + // merges underflowing text with the one in the next box + std::optional<OutlinerParaObject> pNewText = mpUnderflChText->CreateMergedUnderflowParaObject(pOutl, mpNextLink->GetOutlinerParaObject()); + + // Set the other box empty; it will be replaced by the rest of the text if overflow occurs + if (!mpTargetLink->GetPreventChainable()) + mpNextLink->NbcSetOutlinerParaObject(pOutl->GetEmptyParaObject()); + + // We store the size since NbcSetOutlinerParaObject can change it + //Size aOldSize = pOutl->GetMaxAutoPaperSize(); + + // This should not be done in editing mode!! //XXX + if (!mpTargetLink->IsInEditMode()) + { + mpTargetLink->NbcSetOutlinerParaObject(pNewText); + } + + // Restore size and set new text + //pOutl->SetMaxAutoPaperSize(aOldSize); // XXX (it seems to be working anyway without this) + pOutl->SetText(*pNewText); + + //GetTextChain()->SetNilChainingEvent(mpTargetLink, false); + + // Check for new overflow + CheckForFlowEvents(pOutl); +} + +void TextChainFlow::ExecuteOverflow(SdrOutliner *pNonOverflOutl, SdrOutliner *pOverflOutl) +{ + //GetTextChain()->SetNilChainingEvent(mpTargetLink, true); + // Leave only non overflowing text + impLeaveOnlyNonOverflowingText(pNonOverflOutl); + + // Transfer of text to next link + if (!mpTargetLink->GetPreventChainable() ) // we don't transfer text while dragging because of resizing + { + impMoveChainedTextToNextLink(pOverflOutl); + } + + //GetTextChain()->SetNilChainingEvent(mpTargetLink, false); +} + +void TextChainFlow::impLeaveOnlyNonOverflowingText(SdrOutliner *pNonOverflOutl) +{ + std::optional<OutlinerParaObject> pNewText = mpOverflChText->RemoveOverflowingText(pNonOverflOutl); + + SAL_INFO("svx.chaining", "[TEXTCHAINFLOW - OF] SOURCE box set to " + << pNewText->GetTextObject().GetParagraphCount() << " paras"); + + // adds it to current outliner anyway (useful in static decomposition) + pNonOverflOutl->SetText(*pNewText); + + mpTargetLink->NbcSetOutlinerParaObject(std::move(pNewText)); + // For some reason the paper size is lost after last instruction, so we set it. + pNonOverflOutl->SetPaperSize(Size(pNonOverflOutl->GetPaperSize().Width(), + pNonOverflOutl->GetTextHeight())); + +} + +void TextChainFlow::impMoveChainedTextToNextLink(SdrOutliner *pOverflOutl) +{ + // prevent copying text in same box + if ( mpNextLink == mpTargetLink ) { + SAL_INFO("svx.chaining", "[CHAINING] Trying to copy text for next link in same object"); + return; + } + + std::optional<OutlinerParaObject> pNewText = + mpOverflChText->InsertOverflowingText(pOverflOutl, + mpNextLink->GetOutlinerParaObject()); + SAL_INFO("svx.chaining", "[TEXTCHAINFLOW - OF] DEST box set to " + << pNewText->GetTextObject().GetParagraphCount() << " paras"); + + if (pNewText) + mpNextLink->NbcSetOutlinerParaObject(std::move(pNewText)); + + // Set Deep Merge status + SAL_INFO("svx.chaining", "[DEEPMERGE] Setting deepMerge to " + << mpOverflChText->IsLastParaInterrupted()); + GetTextChain()->SetIsPartOfLastParaInNextLink( + mpTargetLink, + mpOverflChText->IsLastParaInterrupted()); +} + +SdrTextObj *TextChainFlow::GetLinkTarget() const +{ + return mpTargetLink; +} + +TextChain *TextChainFlow::GetTextChain() const +{ + return mpTextChain; +} + +// EditingTextChainFlow + +EditingTextChainFlow::EditingTextChainFlow(SdrTextObj *pLinkTarget) : + TextChainFlow(pLinkTarget) +{ + SAL_INFO("svx.chaining", "[TEXTCHAINFLOW] Creating a new EditingTextChainFlow"); +} + +void EditingTextChainFlow::CheckForFlowEvents(SdrOutliner *pFlowOutl) +{ + // if this is editing outliner no need to set parameters + if (pFlowOutl == GetLinkTarget()->mpEditingOutliner) + impCheckForFlowEvents(pFlowOutl, nullptr); + else + impCheckForFlowEvents(pFlowOutl, GetLinkTarget()->mpEditingOutliner); + + // Broadcast events for cursor handling + impBroadcastCursorInfo(); +} + +void EditingTextChainFlow::impLeaveOnlyNonOverflowingText(SdrOutliner *pNonOverflOutl) +{ + mpOverflChText->RemoveOverflowingText(pNonOverflOutl); + //impSetTextForEditingOutliner(pNewText); //XXX: Don't call it since we do everything with NonOverflowingText::ToParaObject // XXX: You may need this for Underflow + + // XXX: I'm not sure whether we need this (after all operations such as Paste don't change this - as far as I understand) + //GetLinkTarget()->NbcSetOutlinerParaObject(pNewText); +} + +void EditingTextChainFlow::impSetFlowOutlinerParams(SdrOutliner *pFlowOutl, SdrOutliner *pParamOutl) +{ + // Set right size for overflow + pFlowOutl->SetMaxAutoPaperSize(pParamOutl->GetMaxAutoPaperSize()); + pFlowOutl->SetMinAutoPaperSize(pParamOutl->GetMinAutoPaperSize()); + pFlowOutl->SetPaperSize(pParamOutl->GetPaperSize()); +} + +void EditingTextChainFlow::impBroadcastCursorInfo() const +{ + ESelection aPreChainingSel = GetTextChain()->GetPreChainingSel(GetLinkTarget()) ; + + // Test whether the cursor is out of the box. + bool bCursorOut = mbPossiblyCursorOut && maOverflowPosSel < aPreChainingSel; + + // NOTE: I handled already the stuff for the comments below. They will be kept temporarily till stuff settles down. + // Possibility: 1) why don't we stop passing the actual event to the TextChain and instead we pass + // the overflow pos and mbPossiblyCursorOut + // 2) We pass the current selection before anything happens and we make impBroadcastCursorInfo compute it. + + + if (bCursorOut) { + //maCursorEvent = CursorChainingEvent::TO_NEXT_LINK; + GetTextChain()->SetPostChainingSel(GetLinkTarget(), maPostChainingSel); + GetTextChain()->SetCursorEvent(GetLinkTarget(), CursorChainingEvent::TO_NEXT_LINK); + } else { + //maCursorEvent = CursorChainingEvent::UNCHANGED; + GetTextChain()->SetPostChainingSel(GetLinkTarget(), aPreChainingSel); + GetTextChain()->SetCursorEvent(GetLinkTarget(), CursorChainingEvent::UNCHANGED); + } + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |