diff options
Diffstat (limited to 'sc/source/core/data/drwlayer.cxx')
-rw-r--r-- | sc/source/core/data/drwlayer.cxx | 2676 |
1 files changed, 2676 insertions, 0 deletions
diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx new file mode 100644 index 000000000..3f41a017e --- /dev/null +++ b/sc/source/core/data/drwlayer.cxx @@ -0,0 +1,2676 @@ +/* -*- 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/Reference.hxx> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/embed/XClassifiedObject.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <sot/exchange.hxx> +#include <svx/objfac3d.hxx> +#include <svx/xtable.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svditer.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdomeas.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdundo.hxx> +#include <svx/sdsxyitm.hxx> +#include <svx/svxids.hrc> +#include <i18nlangtag/mslangid.hxx> +#include <editeng/unolingu.hxx> +#include <svx/drawitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <sfx2/objsh.hxx> +#include <svl/itempool.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <tools/globname.hxx> +#include <tools/UnitConversion.hxx> +#include <osl/diagnose.h> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> + +#include <drwlayer.hxx> +#include <drawpage.hxx> +#include <global.hxx> +#include <document.hxx> +#include <userdat.hxx> +#include <markdata.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scmod.hxx> +#include <postit.hxx> +#include <attrib.hxx> +#include <charthelper.hxx> +#include <table.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <memory> +#include <algorithm> +#include <cstdlib> + +namespace com::sun::star::embed { class XEmbeddedObject; } + +#define DET_ARROW_OFFSET 1000 + +using namespace ::com::sun::star; + +static E3dObjFactory* pF3d = nullptr; +static sal_uInt16 nInst = 0; + +SfxObjectShell* ScDrawLayer::pGlobalDrawPersist = nullptr; + +bool bDrawIsInUndo = false; //TODO: Member + +ScUndoObjData::ScUndoObjData( SdrObject* pObjP, const ScAddress& rOS, const ScAddress& rOE, + const ScAddress& rNS, const ScAddress& rNE ) : + SdrUndoObj( *pObjP ), + aOldStt( rOS ), + aOldEnd( rOE ), + aNewStt( rNS ), + aNewEnd( rNE ) +{ +} + +ScUndoObjData::~ScUndoObjData() +{ +} + +void ScUndoObjData::Undo() +{ + ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj ); + OSL_ENSURE(pData,"ScUndoObjData: Data missing"); + if (pData) + { + pData->maStart = aOldStt; + pData->maEnd = aOldEnd; + } + + // Undo also an untransformed anchor + pData = ScDrawLayer::GetNonRotatedObjData( pObj ); + if (pData) + { + pData->maStart = aOldStt; + pData->maEnd = aOldEnd; + } +} + +void ScUndoObjData::Redo() +{ + ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj ); + OSL_ENSURE(pData,"ScUndoObjData: Data missing"); + if (pData) + { + pData->maStart = aNewStt; + pData->maEnd = aNewEnd; + } + + // Redo also an untransformed anchor + pData = ScDrawLayer::GetNonRotatedObjData( pObj ); + if (pData) + { + pData->maStart = aNewStt; + pData->maEnd = aNewEnd; + } +} + +ScUndoAnchorData::ScUndoAnchorData( SdrObject* pObjP, ScDocument* pDoc, SCTAB nTab ) : + SdrUndoObj( *pObjP ), + mpDoc( pDoc ), + mnTab( nTab ) +{ + mbWasCellAnchored = ScDrawLayer::IsCellAnchored( *pObjP ); + mbWasResizeWithCell = ScDrawLayer::IsResizeWithCell( *pObjP ); +} + +ScUndoAnchorData::~ScUndoAnchorData() +{ +} + +void ScUndoAnchorData::Undo() +{ + // Trigger Object Change + if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject()) + { + SdrHint aHint(SdrHintKind::ObjectChange, *pObj); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } + + if (mbWasCellAnchored) + ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell); + else + ScDrawLayer::SetPageAnchored( *pObj ); +} + +void ScUndoAnchorData::Redo() +{ + if (mbWasCellAnchored) + ScDrawLayer::SetPageAnchored( *pObj ); + else + ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell); + + // Trigger Object Change + if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject()) + { + SdrHint aHint(SdrHintKind::ObjectChange, *pObj); + pObj->getSdrModelFromSdrObject().Broadcast(aHint); + } +} + +ScTabDeletedHint::ScTabDeletedHint( SCTAB nTabNo ) : + nTab( nTabNo ) +{ +} + +ScTabDeletedHint::~ScTabDeletedHint() +{ +} + +ScTabSizeChangedHint::ScTabSizeChangedHint( SCTAB nTabNo ) : + nTab( nTabNo ) +{ +} + +ScTabSizeChangedHint::~ScTabSizeChangedHint() +{ +} + +#define MAXMM 10000000 + + +static void lcl_ReverseTwipsToMM( tools::Rectangle& rRect ) +{ + rRect = o3tl::convert(rRect, o3tl::Length::mm100, o3tl::Length::twip); +} + +static ScRange lcl_getClipRangeFromClipDoc(ScDocument* pClipDoc, SCTAB nClipTab) +{ + if (!pClipDoc) + return ScRange(); + + SCCOL nClipStartX; + SCROW nClipStartY; + SCCOL nClipEndX; + SCROW nClipEndY; + pClipDoc->GetClipStart(nClipStartX, nClipStartY); + pClipDoc->GetClipArea(nClipEndX, nClipEndY, true); + nClipEndX = nClipEndX + nClipStartX; + nClipEndY += nClipStartY; // GetClipArea returns the difference + + return ScRange(nClipStartX, nClipStartY, nClipTab, nClipEndX, nClipEndY, nClipTab); +} + +ScDrawLayer::ScDrawLayer( ScDocument* pDocument, const OUString& rName ) : + FmFormModel( + nullptr, + pGlobalDrawPersist ? pGlobalDrawPersist : (pDocument ? pDocument->GetDocumentShell() : nullptr)), + aName( rName ), + pDoc( pDocument ), + bRecording( false ), + bAdjustEnabled( true ), + bHyphenatorSet( false ) +{ + SetVOCInvalidationIsReliable(true); + + pGlobalDrawPersist = nullptr; // Only use once + + SfxObjectShell* pObjSh = pDocument ? pDocument->GetDocumentShell() : nullptr; + XColorListRef pXCol = XColorList::GetStdColorList(); + if ( pObjSh ) + { + SetObjectShell( pObjSh ); + + // set color table + const SvxColorListItem* pColItem = pObjSh->GetItem( SID_COLOR_TABLE ); + if ( pColItem ) + pXCol = pColItem->GetColorList(); + } + SetPropertyList( static_cast<XPropertyList *> (pXCol.get()) ); + + SetSwapGraphics(); + + SetScaleUnit(MapUnit::Map100thMM); + SfxItemPool& rPool = GetItemPool(); + rPool.SetDefaultMetric(MapUnit::Map100thMM); + SvxFrameDirectionItem aModeItem( SvxFrameDirection::Environment, EE_PARA_WRITINGDIR ); + rPool.SetPoolDefaultItem( aModeItem ); + + // #i33700# + // Set shadow distance defaults as PoolDefaultItems. Details see bug. + rPool.SetPoolDefaultItem(makeSdrShadowXDistItem(300)); + rPool.SetPoolDefaultItem(makeSdrShadowYDistItem(300)); + + // default for script spacing depends on locale, see SdDrawDocument ctor in sd + LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType(); + if (MsLangId::isKorean(eOfficeLanguage) || eOfficeLanguage == LANGUAGE_JAPANESE) + { + // secondary is edit engine pool + rPool.GetSecondaryPool()->SetPoolDefaultItem( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) ); + } + + rPool.FreezeIdRanges(); // the pool is also used directly + + SdrLayerAdmin& rAdmin = GetLayerAdmin(); + rAdmin.NewLayer("vorne", SC_LAYER_FRONT.get()); + rAdmin.NewLayer("hinten", SC_LAYER_BACK.get()); + rAdmin.NewLayer("intern", SC_LAYER_INTERN.get()); + // tdf#140252 use same name as in ctor of SdrLayerAdmin + rAdmin.NewLayer(rAdmin.GetControlLayerName(), SC_LAYER_CONTROLS.get()); + rAdmin.NewLayer("hidden", SC_LAYER_HIDDEN.get()); + + // Set link for URL-Fields + ScModule* pScMod = SC_MOD(); + Outliner& rOutliner = GetDrawOutliner(); + rOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) ); + + Outliner& rHitOutliner = GetHitTestOutliner(); + rHitOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) ); + + // set FontHeight pool defaults without changing static SdrEngineDefaults + SfxItemPool* pOutlinerPool = rOutliner.GetEditTextObjectPool(); + if ( pOutlinerPool ) + { + m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt + m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt + m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt + } + SfxItemPool* pHitOutlinerPool = rHitOutliner.GetEditTextObjectPool(); + if ( pHitOutlinerPool ) + { + pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt + pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt + pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt + } + + // initial undo mode as in Calc document + if( pDoc ) + EnableUndo( pDoc->IsUndoEnabled() ); + + // URL-Buttons have no handler anymore, all is done by themselves + + if( !nInst++ ) + { + pF3d = new E3dObjFactory; + } +} + +ScDrawLayer::~ScDrawLayer() +{ + Broadcast(SdrHint(SdrHintKind::ModelCleared)); + + ClearModel(true); + + pUndoGroup.reset(); + if( !--nInst ) + { + delete pF3d; + pF3d = nullptr; + } +} + +void ScDrawLayer::UseHyphenator() +{ + if (!bHyphenatorSet) + { + css::uno::Reference< css::linguistic2::XHyphenator > + xHyphenator = LinguMgr::GetHyphenator(); + + GetDrawOutliner().SetHyphenator( xHyphenator ); + GetHitTestOutliner().SetHyphenator( xHyphenator ); + + bHyphenatorSet = true; + } +} + +rtl::Reference<SdrPage> ScDrawLayer::AllocPage(bool bMasterPage) +{ + return new ScDrawPage(*this, bMasterPage); +} + +bool ScDrawLayer::HasObjects() const +{ + bool bFound = false; + + sal_uInt16 nCount = GetPageCount(); + for (sal_uInt16 i=0; i<nCount && !bFound; i++) + if (GetPage(i)->GetObjCount()) + bFound = true; + + return bFound; +} + +SdrModel* ScDrawLayer::AllocModel() const +{ + // Allocated model (for clipboard etc) must not have a pointer + // to the original model's document, pass NULL as document: + + return new ScDrawLayer( nullptr, aName ); +} + +bool ScDrawLayer::ScAddPage( SCTAB nTab ) +{ + if (bDrawIsInUndo) + return false; // not inserted + + rtl::Reference<ScDrawPage> pPage = static_cast<ScDrawPage*>(AllocPage( false ).get()); + InsertPage(pPage.get(), static_cast<sal_uInt16>(nTab)); + if (bRecording) + AddCalcUndo(std::make_unique<SdrUndoNewPage>(*pPage)); + + ResetTab(nTab, pDoc->GetTableCount()-1); + return true; // inserted +} + +void ScDrawLayer::ScRemovePage( SCTAB nTab ) +{ + if (bDrawIsInUndo) + return; + + Broadcast( ScTabDeletedHint( nTab ) ); + if (bRecording) + { + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + AddCalcUndo(std::make_unique<SdrUndoDelPage>(*pPage)); // Undo-Action becomes the page owner + RemovePage( static_cast<sal_uInt16>(nTab) ); // just deliver, not deleting + } + else + DeletePage( static_cast<sal_uInt16>(nTab) ); // just get rid of it + + ResetTab(nTab, pDoc->GetTableCount()-1); +} + +void ScDrawLayer::ScRenamePage( SCTAB nTab, const OUString& rNewName ) +{ + ScDrawPage* pPage = static_cast<ScDrawPage*>( GetPage(static_cast<sal_uInt16>(nTab)) ); + if (pPage) + pPage->SetName(rNewName); +} + +void ScDrawLayer::ScMovePage( sal_uInt16 nOldPos, sal_uInt16 nNewPos ) +{ + MovePage( nOldPos, nNewPos ); + sal_uInt16 nMinPos = std::min(nOldPos, nNewPos); + ResetTab(nMinPos, pDoc->GetTableCount()-1); +} + +void ScDrawLayer::ScCopyPage( sal_uInt16 nOldPos, sal_uInt16 nNewPos ) +{ + if (bDrawIsInUndo) + return; + + SdrPage* pOldPage = GetPage(nOldPos); + SdrPage* pNewPage = GetPage(nNewPos); + + // Copying + + if (pOldPage && pNewPage) + { + SCTAB nOldTab = static_cast<SCTAB>(nOldPos); + SCTAB nNewTab = static_cast<SCTAB>(nNewPos); + + SdrObjListIter aIter( pOldPage, SdrIterMode::Flat ); + SdrObject* pOldObject = aIter.Next(); + while (pOldObject) + { + ScDrawObjData* pOldData = GetObjData(pOldObject); + if (pOldData) + { + pOldData->maStart.SetTab(nOldTab); + pOldData->maEnd.SetTab(nOldTab); + } + + // Clone to target SdrModel + SdrObject* pNewObject(pOldObject->CloneSdrObject(*this)); + pNewObject->NbcMove(Size(0,0)); + pNewPage->InsertObject( pNewObject ); + ScDrawObjData* pNewData = GetObjData(pNewObject); + if (pNewData) + { + pNewData->maStart.SetTab(nNewTab); + pNewData->maEnd.SetTab(nNewTab); + } + + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) ); + + pOldObject = aIter.Next(); + } + } + + ResetTab(static_cast<SCTAB>(nNewPos), pDoc->GetTableCount()-1); +} + +void ScDrawLayer::ResetTab( SCTAB nStart, SCTAB nEnd ) +{ + SCTAB nPageSize = static_cast<SCTAB>(GetPageCount()); + if (nPageSize < 0) + // No drawing pages exist. + return; + + if (nEnd >= nPageSize) + // Avoid iterating beyond the last existing page. + nEnd = nPageSize - 1; + + for (SCTAB i = nStart; i <= nEnd; ++i) + { + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(i)); + if (!pPage) + continue; + + SdrObjListIter aIter(pPage, SdrIterMode::Flat); + for (SdrObject* pObj = aIter.Next(); pObj; pObj = aIter.Next()) + { + ScDrawObjData* pData = GetObjData(pObj); + if (!pData) + continue; + + pData->maStart.SetTab(i); + pData->maEnd.SetTab(i); + } + } +} + +static bool IsInBlock( const ScAddress& rPos, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2 ) +{ + return rPos.Col() >= nCol1 && rPos.Col() <= nCol2 && + rPos.Row() >= nRow1 && rPos.Row() <= nRow2; +} + +void ScDrawLayer::MoveCells( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2, + SCCOL nDx,SCROW nDy, bool bUpdateNoteCaptionPos ) +{ + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page not found"); + if (!pPage) + return; + + bool bNegativePage = pDoc && pDoc->IsNegativePage( nTab ); + + const size_t nCount = pPage->GetObjCount(); + for ( size_t i = 0; i < nCount; ++i ) + { + SdrObject* pObj = pPage->GetObj( i ); + ScDrawObjData* pData = GetObjDataTab( pObj, nTab ); + if( pData ) + { + const ScAddress aOldStt = pData->maStart; + const ScAddress aOldEnd = pData->maEnd; + bool bChange = false; + if ( aOldStt.IsValid() && IsInBlock( aOldStt, nCol1,nRow1, nCol2,nRow2 ) ) + { + pData->maStart.IncCol( nDx ); + pData->maStart.IncRow( nDy ); + bChange = true; + } + if ( aOldEnd.IsValid() && IsInBlock( aOldEnd, nCol1,nRow1, nCol2,nRow2 ) ) + { + pData->maEnd.IncCol( nDx ); + pData->maEnd.IncRow( nDy ); + bChange = true; + } + if (bChange) + { + if ( dynamic_cast<const SdrRectObj*>( pObj) != nullptr && pData->maStart.IsValid() && pData->maEnd.IsValid() ) + pData->maStart.PutInOrder( pData->maEnd ); + + // Update also an untransformed anchor that's what we stored ( and still do ) to xml + ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData( pObj ); + if ( pNoRotatedAnchor ) + { + const ScAddress aOldSttNoRotatedAnchor = pNoRotatedAnchor->maStart; + const ScAddress aOldEndNoRotatedAnchor = pNoRotatedAnchor->maEnd; + if ( aOldSttNoRotatedAnchor.IsValid() && IsInBlock( aOldSttNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) ) + { + pNoRotatedAnchor->maStart.IncCol(nDx); + pNoRotatedAnchor->maStart.IncRow(nDy); + } + if ( aOldEndNoRotatedAnchor.IsValid() && IsInBlock( aOldEndNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) ) + { + pNoRotatedAnchor->maEnd.IncCol(nDx); + pNoRotatedAnchor->maEnd.IncRow(nDy); + } + } + + AddCalcUndo( std::make_unique<ScUndoObjData>( pObj, aOldStt, aOldEnd, pData->maStart, pData->maEnd ) ); + RecalcPos( pObj, *pData, bNegativePage, bUpdateNoteCaptionPos ); + } + } + } +} + +void ScDrawLayer::SetPageSize(sal_uInt16 nPageNo, const Size& rSize, bool bUpdateNoteCaptionPos, + const ScObjectHandling eObjectHandling) +{ + SdrPage* pPage = GetPage(nPageNo); + if (!pPage) + return; + + if ( rSize != pPage->GetSize() ) + { + pPage->SetSize( rSize ); + Broadcast( ScTabSizeChangedHint( static_cast<SCTAB>(nPageNo) ) ); // SetWorkArea() on the views + } + + // Do not call RecalcPos while loading, because row height is not finished, when SetPageSize + // is called first time. Instead the objects are initialized from ScXMLImport::endDocument() and + // RecalcPos is called from there. + if (!pDoc || pDoc->IsImportingXML()) + return; + + // Implement Detective lines (adjust to new heights / widths) + // even if size is still the same + // (individual rows/columns can have been changed)) + + bool bNegativePage = pDoc && pDoc->IsNegativePage( static_cast<SCTAB>(nPageNo) ); + + // Disable mass broadcasts from drawing objects' position changes. + bool bWasLocked = isLocked(); + setLock(true); + + const size_t nCount = pPage->GetObjCount(); + for ( size_t i = 0; i < nCount; ++i ) + { + SdrObject* pObj = pPage->GetObj( i ); + ScDrawObjData* pData = GetObjDataTab( pObj, static_cast<SCTAB>(nPageNo) ); + if( pData ) // cell anchored + { + if (pData->meType == ScDrawObjData::DrawingObject + || pData->meType == ScDrawObjData::ValidationCircle) + { + switch (eObjectHandling) + { + case ScObjectHandling::RecalcPosMode: + RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos); + break; + case ScObjectHandling::MoveRTLMode: + MoveRTL(pObj); + break; + case ScObjectHandling::MirrorRTLMode: + MirrorRTL(pObj); + break; + } + } + else // DetectiveArrow and CellNote + RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos); + } + else // page anchored + { + switch (eObjectHandling) + { + case ScObjectHandling::MoveRTLMode: + MoveRTL(pObj); + break; + case ScObjectHandling::MirrorRTLMode: + MirrorRTL(pObj); + break; + case ScObjectHandling::RecalcPosMode: // does not occur for page anchored shapes + break; + } + } + } + + setLock(bWasLocked); +} + +namespace +{ + //Can't have a zero width dimension + tools::Rectangle lcl_makeSafeRectangle(const tools::Rectangle &rNew) + { + tools::Rectangle aRect = rNew; + if (aRect.Bottom() == aRect.Top()) + aRect.SetBottom( aRect.Top()+1 ); + if (aRect.Right() == aRect.Left()) + aRect.SetRight( aRect.Left()+1 ); + return aRect; + } + + Point lcl_calcAvailableDiff(const ScDocument &rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, const Point &aWantedDiff) + { + Point aAvailableDiff(aWantedDiff); + tools::Long nHeight = o3tl::convert(rDoc.GetRowHeight( nRow, nTab ), o3tl::Length::twip, o3tl::Length::mm100); + tools::Long nWidth = o3tl::convert(rDoc.GetColWidth( nCol, nTab ), o3tl::Length::twip, o3tl::Length::mm100); + if (aAvailableDiff.Y() > nHeight) + aAvailableDiff.setY( nHeight ); + if (aAvailableDiff.X() > nWidth) + aAvailableDiff.setX( nWidth ); + return aAvailableDiff; + } + + tools::Rectangle lcl_UpdateCalcPoly(basegfx::B2DPolygon &rCalcPoly, int nWhichPoint, const Point &rPos) + { + rCalcPoly.setB2DPoint(nWhichPoint, basegfx::B2DPoint(rPos.X(), rPos.Y())); + basegfx::B2DRange aRange(basegfx::utils::getRange(rCalcPoly)); + return tools::Rectangle(static_cast<tools::Long>(aRange.getMinX()), static_cast<tools::Long>(aRange.getMinY()), + static_cast<tools::Long>(aRange.getMaxX()), static_cast<tools::Long>(aRange.getMaxY())); + } + +bool lcl_AreRectanglesApproxEqual(const tools::Rectangle& rRectA, const tools::Rectangle& rRectB) +{ + // Twips <-> Hmm conversions introduce +-1 differences although there are no real changes in the object. + // Therefore test with == is not appropriate in some cases. + if (std::abs(rRectA.Left() - rRectB.Left()) > 1) + return false; + if (std::abs(rRectA.Top() - rRectB.Top()) > 1) + return false; + if (std::abs(rRectA.Right() - rRectB.Right()) > 1) + return false; + if (std::abs(rRectA.Bottom() - rRectB.Bottom()) > 1) + return false; + return true; +} + +bool lcl_NeedsMirrorYCorrection(const SdrObject* pObj) +{ + return pObj->GetObjIdentifier() == SdrObjKind::CustomShape + && static_cast<const SdrObjCustomShape*>(pObj)->IsMirroredY(); +} + +void lcl_SetLogicRectFromAnchor(SdrObject* pObj, const ScDrawObjData& rAnchor, const ScDocument* pDoc) +{ + // This is only used during initialization. At that time, shape handling is always LTR. No need + // to consider negative page. + if (!pObj || !pDoc || !rAnchor.maEnd.IsValid() || !rAnchor.maStart.IsValid()) + return; + + // In case of a vertical mirrored custom shape, LibreOffice uses internally an additional 180deg + // in aGeo.nRotationAngle and in turn has a different logic rectangle position. We remove flip, + // set the logic rectangle, and apply flip again. You cannot simple use a 180deg-rotated + // rectangle, because custom shape mirroring is internally applied after the other + // transformations. + const bool bNeedsMirrorYCorrection = lcl_NeedsMirrorYCorrection(pObj); // remember state + if (bNeedsMirrorYCorrection) + { + const tools::Rectangle aRect(pObj->GetSnapRect()); + const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1); + const Point aRight(aLeft.X() + 1000, aLeft.Y()); + pObj->NbcMirror(aLeft, aRight); + } + + // Build full sized logic rectangle from start and end given in anchor. + const tools::Rectangle aStartCellRect( + pDoc->GetMMRect(rAnchor.maStart.Col(), rAnchor.maStart.Row(), rAnchor.maStart.Col(), + rAnchor.maStart.Row(), rAnchor.maStart.Tab(), false /*bHiddenAsZero*/)); + Point aStartPoint(aStartCellRect.Left(), aStartCellRect.Top()); + aStartPoint.AdjustX(rAnchor.maStartOffset.getX()); + aStartPoint.AdjustY(rAnchor.maStartOffset.getY()); + + const tools::Rectangle aEndCellRect( + pDoc->GetMMRect(rAnchor.maEnd.Col(), rAnchor.maEnd.Row(), rAnchor.maEnd.Col(), + rAnchor.maEnd.Row(), rAnchor.maEnd.Tab(), false /*bHiddenAsZero*/)); + Point aEndPoint(aEndCellRect.Left(), aEndCellRect.Top()); + aEndPoint.AdjustX(rAnchor.maEndOffset.getX()); + aEndPoint.AdjustY(rAnchor.maEndOffset.getY()); + + // Set this as new, full sized logical rectangle + tools::Rectangle aNewRectangle(aStartPoint, aEndPoint); + aNewRectangle.Justify(); + if (!lcl_AreRectanglesApproxEqual(pObj->GetLogicRect(), aNewRectangle)) + pObj->NbcSetLogicRect(lcl_makeSafeRectangle(aNewRectangle)); + + // The shape has the correct logical rectangle now. Reapply the above removed mirroring. + if (bNeedsMirrorYCorrection) + { + const tools::Rectangle aRect(pObj->GetSnapRect()); + const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1); + const Point aRight(aLeft.X() + 1000, aLeft.Y()); + pObj->NbcMirror(aLeft, aRight); + } +} + +} // namespace + +void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData& rData, + bool bNegativePage, bool bCanResize) +{ + tools::Rectangle aRect = pObj->GetSnapRect(); + SCCOL nCol1 = rData.maStart.Col(); + SCROW nRow1 = rData.maStart.Row(); + SCTAB nTab1 = rData.maStart.Tab(); + SCCOL nCol2 = rData.maEnd.Col(); + SCROW nRow2 = rData.maEnd.Row(); + SCTAB nTab2 = rData.maEnd.Tab(); + Point aPos(pDoc->GetColOffset(nCol1, nTab1, /*bHiddenAsZero*/true), + pDoc->GetRowOffset(nRow1, nTab1, /*bHiddenAsZero*/true)); + aPos.setX(convertTwipToMm100(aPos.X())); + aPos.setY(convertTwipToMm100(aPos.Y())); + aPos += lcl_calcAvailableDiff(*pDoc, nCol1, nRow1, nTab1, rData.maStartOffset); + + // this sets the needed changed position (translation) + aRect.SetPos(aPos); + + if (bCanResize) + { + // all this stuff is additional stuff to evtl. not only translate the + // range (Rectangle), but also check for and evtl. do corrections for it's size + const tools::Rectangle aLastCellRect(rData.getLastCellRect()); + + // If the row was hidden before, or we don't have a valid cell rect, calculate the + // new rect based on the end point. + // Also when the end point is set, we need to consider it. + if (rData.mbWasInHiddenRow || aLastCellRect.IsEmpty() || nRow1 != nRow2 || nCol1 != nCol2) + { + Point aEnd(pDoc->GetColOffset(nCol2, nTab2, /*bHiddenAsZero*/true), + pDoc->GetRowOffset(nRow2, nTab2, /*bHiddenAsZero*/true)); + aEnd.setX(convertTwipToMm100(aEnd.X())); + aEnd.setY(convertTwipToMm100(aEnd.Y())); + aEnd += lcl_calcAvailableDiff(*pDoc, nCol2, nRow2, nTab2, rData.maEndOffset); + + aRect = tools::Rectangle(aPos, aEnd); + } + else if (!aLastCellRect.IsEmpty()) + { + // We calculate based on the last cell rect to be able to scale the image + // as much as the cell was scaled. + // Still, we keep the image in its current cell (to keep start anchor == end anchor) + const tools::Rectangle aCurrentCellRect(GetCellRect(*GetDocument(), rData.maStart, true)); + tools::Long nCurrentWidth(aCurrentCellRect.GetWidth()); + tools::Long nCurrentHeight(aCurrentCellRect.GetHeight()); + const tools::Long nLastWidth(aLastCellRect.GetWidth()); + const tools::Long nLastHeight(aLastCellRect.GetHeight()); + + // tdf#116931 Avoid and correct nifty numerical problems with the integer + // based and converted values (GetCellRect uses multiplies with HMM_PER_TWIPS) + if(nCurrentWidth + 1 == nLastWidth || nCurrentWidth == nLastWidth + 1) + { + nCurrentWidth = nLastWidth; + } + + if(nCurrentHeight + 1 == nLastHeight || nCurrentHeight == nLastHeight + 1) + { + nCurrentHeight = nLastHeight; + } + + // get initial ScalingFactors + double fWidthFactor(nCurrentWidth == nLastWidth || 0 == nLastWidth + ? 1.0 + : static_cast<double>(nCurrentWidth) / static_cast<double>(nLastWidth)); + double fHeightFactor(nCurrentHeight == nLastHeight || 0 == nLastHeight + ? 1.0 + : static_cast<double>(nCurrentHeight) / static_cast<double>(nLastHeight)); + + // check if we grow or shrink - and at all + const bool bIsGrowing(nCurrentWidth > nLastWidth || nCurrentHeight > nLastHeight); + const bool bIsShrinking(nCurrentWidth < nLastWidth || nCurrentHeight < nLastHeight); + const bool bIsSizeChanged(bIsGrowing || bIsShrinking); + + // handle AspectRatio, only needed if size does change + if(bIsSizeChanged && pObj->shouldKeepAspectRatio()) + { + tools::Rectangle aRectIncludingOffset = aRect; + aRectIncludingOffset.setWidth(aRect.GetWidth() + rData.maStartOffset.X()); + aRectIncludingOffset.setHeight(aRect.GetHeight() + rData.maStartOffset.Y()); + tools::Long nWidth = aRectIncludingOffset.GetWidth(); + assert(nWidth && "div-by-zero"); + double fMaxWidthFactor = static_cast<double>(nCurrentWidth) + / static_cast<double>(nWidth); + tools::Long nHeight = aRectIncludingOffset.GetHeight(); + assert(nHeight && "div-by-zero"); + double fMaxHeightFactor = static_cast<double>(nCurrentHeight) + / static_cast<double>(nHeight); + double fMaxFactor = std::min(fMaxHeightFactor, fMaxWidthFactor); + + if(bIsGrowing) // cell is growing larger + { + // To actually grow the image, we need to take the max + fWidthFactor = std::max(fWidthFactor, fHeightFactor); + } + else if(bIsShrinking) // cell is growing smaller, take the min + { + fWidthFactor = std::min(fWidthFactor, fHeightFactor); + } + + // We don't want the image to become larger than the current cell + fWidthFactor = fHeightFactor = std::min(fWidthFactor, fMaxFactor); + } + + if(bIsSizeChanged) + { + // tdf#116931 re-organized scaling (if needed) + // Check if we need to scale at all. Always scale on growing. + bool bNeedToScale(bIsGrowing); + + if(!bNeedToScale && bIsShrinking) + { + // Check if original still fits into space. Do *not* forget to + // compare with evtl. numerically corrected aCurrentCellRect + const bool bFitsInX(aRect.Right() <= aCurrentCellRect.Left() + nCurrentWidth); + const bool bFitsInY(aRect.Bottom() <= aCurrentCellRect.Top() + nCurrentHeight); + + // If the image still fits in the smaller cell, don't resize it at all + bNeedToScale = (!bFitsInX || !bFitsInY); + } + + if(bNeedToScale) + { + // tdf#116931 use transformations now. Translation is already applied + // (see aRect.SetPos above), so only scale needs to be applied - relative + // to *new* CellRect (which is aCurrentCellRect). + // Prepare scale relative to top-left of aCurrentCellRect + basegfx::B2DHomMatrix aChange; + + aChange.translate(-aCurrentCellRect.Left(), -aCurrentCellRect.Top()); + aChange.scale(fWidthFactor, fHeightFactor); + aChange.translate(aCurrentCellRect.Left(), aCurrentCellRect.Top()); + + // create B2DRange and transform by prepared scale + basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aRect); + + aNewRange.transform(aChange); + + // apply to aRect + aRect = tools::Rectangle( + basegfx::fround(aNewRange.getMinX()), basegfx::fround(aNewRange.getMinY()), + basegfx::fround(aNewRange.getMaxX()), basegfx::fround(aNewRange.getMaxY())); + } + } + } + } + + if (bNegativePage) + MirrorRectRTL(aRect); + + rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect), pObj->IsVisible()); +} + +void ScDrawLayer::InitializeCellAnchoredObj(SdrObject* pObj, ScDrawObjData& rData) +{ + // This is called from ScXMLImport::endDocument() + if (!pDoc || !pObj) + return; + if (!rData.getShapeRect().IsEmpty()) + return; // already initialized, should not happen + if (rData.meType == ScDrawObjData::CellNote || rData.meType == ScDrawObjData::ValidationCircle + || rData.meType == ScDrawObjData::DetectiveArrow) + return; // handled in RecalcPos + + // Prevent multiple broadcasts during the series of changes. + bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked(); + pObj->getSdrModelFromSdrObject().setLock(true); + + // rNoRotatedAnchor refers in its start and end addresses and its start and end offsets to + // the logic rectangle of the object. The values are so, as if no hidden columns and rows + // exists and if it is a LTR sheet. These values are directly used for XML in ODF file. + ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData(pObj, true /*bCreate*/); + + // From XML import, rData contains temporarily the anchor information as they are given in + // XML. Copy it to rNoRotatedAnchor, where it belongs. rData will later contain the anchor + // of the transformed object as visible on screen. + rNoRotatedAnchor.maStart = rData.maStart; + rNoRotatedAnchor.maEnd = rData.maEnd; + rNoRotatedAnchor.maStartOffset = rData.maStartOffset; + rNoRotatedAnchor.maEndOffset = rData.maEndOffset; + + SCCOL nCol1 = rNoRotatedAnchor.maStart.Col(); + SCROW nRow1 = rNoRotatedAnchor.maStart.Row(); + SCTAB nTab1 = rNoRotatedAnchor.maStart.Tab(); // Used as parameter several times + + // Object has coordinates relative to left/top of containing cell in XML. Change object to + // absolute coordinates as internally used. + const tools::Rectangle aRect( + pDoc->GetMMRect(nCol1, nRow1, nCol1, nRow1, nTab1, false /*bHiddenAsZero*/)); + const Size aShift(aRect.Left(), aRect.Top()); + pObj->NbcMove(aShift); + + const ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObj); + if (aAnchorType == SCA_CELL_RESIZE) + { + if (pObj->GetObjIdentifier() == SdrObjKind::Line) + { + // Horizontal lines might have wrong start and end anchor because of erroneously applied + // 180deg rotation (tdf#137446). Other lines have wrong end anchor. Coordinates in + // object are correct. Use them for recreating the anchor. + const basegfx::B2DPolygon aPoly( + static_cast<SdrPathObj*>(pObj)->GetPathPoly().getB2DPolygon(0)); + const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0)); + const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1)); + const Point aPointLT(FRound(std::min(aB2DPoint0.getX(), aB2DPoint1.getX())), + FRound(std::min(aB2DPoint0.getY(), aB2DPoint1.getY()))); + const Point aPointRB(FRound(std::max(aB2DPoint0.getX(), aB2DPoint1.getX())), + FRound(std::max(aB2DPoint0.getY(), aB2DPoint1.getY()))); + const tools::Rectangle aObjRect(aPointLT, aPointRB); + GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, nTab1, + false /*bHiddenAsZero*/); + } + else if (pObj->GetObjIdentifier() == SdrObjKind::Measure) + { + // Measure lines might have got wrong start and end anchor from XML import. Recreate + // anchor from start and end point. + SdrMeasureObj* pMeasureObj = static_cast<SdrMeasureObj*>(pObj); + // tdf#137576. The logic rectangle has likely no current values here, but only the + // 1cm x 1cm default size. The call of TakeUnrotatedSnapRect is currently (LO 7.2) + // the only way to force a recalc of the logic rectangle. + tools::Rectangle aObjRect; + pMeasureObj->TakeUnrotatedSnapRect(aObjRect); + GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, rData.maStart.Tab(), + false /*bHiddenAsZero*/); + } + else + { + // In case there are hidden rows or cols, versions 7.0 and earlier have written width and + // height in file so that hidden row or col are count as zero. XML import bases the + // logical rectangle of the object on it. Shapes have at least wrong size, when row or col + // are shown. We try to regenerate the logic rectangle as far as possible from the anchor. + // ODF specifies anyway, that the size has to be ignored, if end cell attributes exist. + lcl_SetLogicRectFromAnchor(pObj, rNoRotatedAnchor, pDoc); + } + } + else // aAnchorType == SCA_CELL, other types will not occur here. + { + // XML has no end cell address in this case. We generate it from position. + UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1, + true /*bUseLogicRect*/); + } + + // Make sure maShapeRect of rNoRotatedAnchor is not empty. Method ScDrawView::Notify() + // needs it to detect a change in object geometry. For example a 180deg rotation effects only + // logic rect. + rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), true); + + // Start and end addresses and offsets in rData refer to the actual snap rectangle of the + // shape. We initialize them here based on the "full" sized object. Adaptation to reduced size + // (by hidden row/col) is done later in RecalcPos. + GetCellAnchorFromPosition(pObj->GetSnapRect(), rData, *pDoc, nTab1, false /*bHiddenAsZero*/); + + // As of ODF 1.3 strict there is no attribute to store whether an object is hidden. So a "visible" + // object might actually be hidden by being in hidden row or column. We detect it here. + // Note, that visibility by hidden row or column refers to the snap rectangle. + if (pObj->IsVisible() + && (pDoc->RowHidden(rData.maStart.Row(), rData.maStart.Tab()) + || pDoc->ColHidden(rData.maStart.Col(), rData.maStart.Tab()))) + pObj->SetVisible(false); + + // Set visibility. ToDo: Really used? + rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible()); + + // And set maShapeRect in rData. It stores not only the current rectangles, but currently, + // existence of maShapeRect is the flag for initialization is done. + rData.setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible()); + + pObj->getSdrModelFromSdrObject().setLock(bWasLocked); +} + +void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegativePage, bool bUpdateNoteCaptionPos ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::RecalcPos - missing document" ); + if( !pDoc ) + return; + + if (rData.meType == ScDrawObjData::CellNote) + { + OSL_ENSURE( rData.maStart.IsValid(), "ScDrawLayer::RecalcPos - invalid position for cell note" ); + /* #i109372# On insert/remove rows/columns/cells: Updating the caption + position must not be done, if the cell containing the note has not + been moved yet in the document. The calling code now passes an + additional boolean stating if the cells are already moved. */ + /* tdf #152081 Do not change hidden objects. That would produce zero height + or width and loss of caption.*/ + if (bUpdateNoteCaptionPos && pObj->IsVisible()) + { + /* When inside an undo action, there may be pending note captions + where cell note is already deleted (thus document cannot find + the note object anymore). The caption will be deleted later + with drawing undo. */ + if( ScPostIt* pNote = pDoc->GetNote( rData.maStart ) ) + pNote->UpdateCaptionPos( rData.maStart ); + } + return; + } + + bool bValid1 = rData.maStart.IsValid(); + SCCOL nCol1 = rData.maStart.Col(); + SCROW nRow1 = rData.maStart.Row(); + SCTAB nTab1 = rData.maStart.Tab(); + bool bValid2 = rData.maEnd.IsValid(); + SCCOL nCol2 = rData.maEnd.Col(); + SCROW nRow2 = rData.maEnd.Row(); + SCTAB nTab2 = rData.maEnd.Tab(); + + if (rData.meType == ScDrawObjData::ValidationCircle) + { + // Validation circle for detective. + rData.setShapeRect(GetDocument(), pObj->GetLogicRect()); + + // rData.maStart should contain the address of the be validated cell. + tools::Rectangle aRect = GetCellRect(*GetDocument(), rData.maStart, true); + aRect.AdjustLeft( -250 ); + aRect.AdjustRight(250 ); + aRect.AdjustTop( -70 ); + aRect.AdjustBottom(70 ); + if ( bNegativePage ) + MirrorRectRTL( aRect ); + + if ( pObj->GetLogicRect() != aRect ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect)); + // maStart has the meaning of "to be validated cell" in a validation circle. For usual + // drawing objects it has the meaning "left/top of logic/snap rect". Because the rectangle + // is expanded above, SetLogicRect() will set maStart to one cell left and one cell above + // of the to be validated cell. We need to backup the old value and restore it. + ScAddress aBackup(rData.maStart); + pObj->SetLogicRect(rData.getShapeRect()); + rData.maStart = aBackup; + } + } + else if (rData.meType == ScDrawObjData::DetectiveArrow) + { + rData.setShapeRect(GetDocument(), pObj->GetLogicRect()); + basegfx::B2DPolygon aCalcPoly; + Point aOrigStartPos(pObj->GetPoint(0)); + Point aOrigEndPos(pObj->GetPoint(1)); + aCalcPoly.append(basegfx::B2DPoint(aOrigStartPos.X(), aOrigStartPos.Y())); + aCalcPoly.append(basegfx::B2DPoint(aOrigEndPos.X(), aOrigEndPos.Y())); + //TODO: do not create multiple Undos for one object (last one can be omitted then) + + SCCOL nLastCol; + SCROW nLastRow; + if( bValid1 ) + { + Point aPos( pDoc->GetColOffset( nCol1, nTab1 ), pDoc->GetRowOffset( nRow1, nTab1 ) ); + if (!pDoc->ColHidden(nCol1, nTab1, nullptr, &nLastCol)) + aPos.AdjustX(pDoc->GetColWidth( nCol1, nTab1 ) / 4 ); + if (!pDoc->RowHidden(nRow1, nTab1, nullptr, &nLastRow)) + aPos.AdjustY(pDoc->GetRowHeight( nRow1, nTab1 ) / 2 ); + aPos.setX(convertTwipToMm100(aPos.X())); + aPos.setY(convertTwipToMm100(aPos.Y())); + Point aStartPos = aPos; + if ( bNegativePage ) + aStartPos.setX( -aStartPos.X() ); // don't modify aPos - used below + if ( pObj->GetPoint( 0 ) != aStartPos ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + + rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos)); + pObj->SetPoint( aStartPos, 0 ); + } + + if( !bValid2 ) + { + Point aEndPos( aPos.X() + DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET ); + if (aEndPos.Y() < 0) + aEndPos.AdjustY(2 * DET_ARROW_OFFSET); + if ( bNegativePage ) + aEndPos.setX( -aEndPos.X() ); + if ( pObj->GetPoint( 1 ) != aEndPos ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + + rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos)); + pObj->SetPoint( aEndPos, 1 ); + } + } + } + if( bValid2 ) + { + Point aPos( pDoc->GetColOffset( nCol2, nTab2 ), pDoc->GetRowOffset( nRow2, nTab2 ) ); + if (!pDoc->ColHidden(nCol2, nTab2, nullptr, &nLastCol)) + aPos.AdjustX(pDoc->GetColWidth( nCol2, nTab2 ) / 4 ); + if (!pDoc->RowHidden(nRow2, nTab2, nullptr, &nLastRow)) + aPos.AdjustY(pDoc->GetRowHeight( nRow2, nTab2 ) / 2 ); + aPos.setX(convertTwipToMm100(aPos.X())); + aPos.setY(convertTwipToMm100(aPos.Y())); + Point aEndPos = aPos; + if ( bNegativePage ) + aEndPos.setX( -aEndPos.X() ); // don't modify aPos - used below + if ( pObj->GetPoint( 1 ) != aEndPos ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + + rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos)); + pObj->SetPoint( aEndPos, 1 ); + } + + if( !bValid1 ) + { + Point aStartPos( aPos.X() - DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET ); + if (aStartPos.X() < 0) + aStartPos.AdjustX(2 * DET_ARROW_OFFSET); + if (aStartPos.Y() < 0) + aStartPos.AdjustY(2 * DET_ARROW_OFFSET); + if ( bNegativePage ) + aStartPos.setX( -aStartPos.X() ); + if ( pObj->GetPoint( 0 ) != aStartPos ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + + rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos)); + pObj->SetPoint( aStartPos, 0 ); + } + } + } + } // end ScDrawObjData::DetectiveArrow + else // start ScDrawObjData::DrawingObject + { + // Do not change hidden objects. That would produce zero height or width and loss of offsets. + if (!pObj->IsVisible()) + return; + + // Prevent multiple broadcasts during the series of changes. + bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked(); + pObj->getSdrModelFromSdrObject().setLock(true); + + bool bCanResize = bValid2 && !pObj->IsResizeProtect() && rData.mbResizeWithCell; + + // update anchor with snap rect + ResizeLastRectFromAnchor( pObj, rData, bNegativePage, bCanResize ); + + ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData( pObj, true /*bCreate*/); + + if( bCanResize ) + { + tools::Rectangle aNew = rData.getShapeRect(); + tools::Rectangle aOld(pObj->GetSnapRect()); + if (!lcl_AreRectanglesApproxEqual(aNew, aOld)) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + + // ToDo: Implement NbcSetSnapRect of SdrMeasureObj. Then this can be removed. + tools::Long nOldWidth = aOld.GetWidth(); + tools::Long nOldHeight = aOld.GetHeight(); + if (pObj->IsPolyObj() && nOldWidth && nOldHeight) + { + // Polyline objects need special treatment. + Size aSizeMove(aNew.Left()-aOld.Left(), aNew.Top()-aOld.Top()); + pObj->NbcMove(aSizeMove); + + double fXFrac = static_cast<double>(aNew.GetWidth()) / static_cast<double>(nOldWidth); + double fYFrac = static_cast<double>(aNew.GetHeight()) / static_cast<double>(nOldHeight); + pObj->NbcResize(aNew.TopLeft(), Fraction(fXFrac), Fraction(fYFrac)); + } + + rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(rData.getShapeRect()), pObj->IsVisible()); + if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape) + pObj->AdjustToMaxRect(rData.getShapeRect()); + else + pObj->SetSnapRect(rData.getShapeRect()); + + // The shape rectangle in the 'unrotated' anchor needs to be updated to the changed + // object geometry. It is used in adjustAnchoredPosition() in ScDrawView::Notify(). + rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible()); + } + } + else + { + const Point aPos(rData.getShapeRect().TopLeft()); + if ( pObj->GetRelativePos() != aPos ) + { + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + pObj->SetRelativePos( aPos ); + rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible()); + } + } + /* + * If we were not allowed resize the object, then the end cell anchor + * is possibly incorrect now, and if the object has no end-cell (e.g. + * missing in original .xml) we are also forced to generate one + */ + bool bEndAnchorIsBad = !bValid2 || pObj->IsResizeProtect(); + if (bEndAnchorIsBad) + { + // update 'rotated' anchor + ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rData, *pDoc, nTab1, false); + // update 'unrotated' anchor + ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1 ); + } + + // End prevent multiple broadcasts during the series of changes. + pObj->getSdrModelFromSdrObject().setLock(bWasLocked); + if (!bWasLocked) + pObj->BroadcastObjectChange(); + } // end ScDrawObjData::DrawingObject +} + +bool ScDrawLayer::GetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const +{ + OSL_ENSURE( pDoc, "ScDrawLayer::GetPrintArea without document" ); + if ( !pDoc ) + return false; + + SCTAB nTab = rRange.aStart.Tab(); + OSL_ENSURE( rRange.aEnd.Tab() == nTab, "GetPrintArea: Tab differ" ); + + bool bNegativePage = pDoc->IsNegativePage( nTab ); + + bool bAny = false; + tools::Long nEndX = 0; + tools::Long nEndY = 0; + tools::Long nStartX = LONG_MAX; + tools::Long nStartY = LONG_MAX; + + // Calculate borders + + if (!bSetHor) + { + nStartX = 0; + SCCOL nStartCol = rRange.aStart.Col(); + SCCOL i; + for (i=0; i<nStartCol; i++) + nStartX +=pDoc->GetColWidth(i,nTab); + nEndX = nStartX; + SCCOL nEndCol = rRange.aEnd.Col(); + for (i=nStartCol; i<=nEndCol; i++) + nEndX += pDoc->GetColWidth(i,nTab); + nStartX = convertTwipToMm100(nStartX); + nEndX = convertTwipToMm100(nEndX); + } + if (!bSetVer) + { + nStartY = pDoc->GetRowHeight( 0, rRange.aStart.Row()-1, nTab); + nEndY = nStartY + pDoc->GetRowHeight( rRange.aStart.Row(), + rRange.aEnd.Row(), nTab); + nStartY = convertTwipToMm100(nStartY); + nEndY = convertTwipToMm100(nEndY); + } + + if ( bNegativePage ) + { + nStartX = -nStartX; // positions are negative, swap start/end so the same comparisons work + nEndX = -nEndX; + ::std::swap( nStartX, nEndX ); + } + + const SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page not found"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + //TODO: test Flags (hidden?) + + tools::Rectangle aObjRect = pObject->GetCurrentBoundRect(); + bool bFit = true; + if ( !bSetHor && ( aObjRect.Right() < nStartX || aObjRect.Left() > nEndX ) ) + bFit = false; + if ( !bSetVer && ( aObjRect.Bottom() < nStartY || aObjRect.Top() > nEndY ) ) + bFit = false; + // #i104716# don't include hidden note objects + if ( bFit && pObject->GetLayer() != SC_LAYER_HIDDEN ) + { + if (bSetHor) + { + if (aObjRect.Left() < nStartX) nStartX = aObjRect.Left(); + if (aObjRect.Right() > nEndX) nEndX = aObjRect.Right(); + } + if (bSetVer) + { + if (aObjRect.Top() < nStartY) nStartY = aObjRect.Top(); + if (aObjRect.Bottom() > nEndY) nEndY = aObjRect.Bottom(); + } + bAny = true; + } + + pObject = aIter.Next(); + } + } + + if ( bNegativePage ) + { + nStartX = -nStartX; // reverse transformation, so the same cell address calculation works + nEndX = -nEndX; + ::std::swap( nStartX, nEndX ); + } + + if (bAny) + { + OSL_ENSURE( nStartX<=nEndX && nStartY<=nEndY, "Start/End wrong in ScDrawLayer::GetPrintArea" ); + + if (bSetHor) + { + nStartX = o3tl::toTwips(nStartX, o3tl::Length::mm100); + nEndX = o3tl::toTwips(nEndX, o3tl::Length::mm100); + tools::Long nWidth; + + nWidth = 0; + rRange.aStart.SetCol( 0 ); + if (nWidth <= nStartX) + { + for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol())) + { + nWidth += pDoc->GetColWidth(nCol,nTab); + if (nWidth > nStartX) + { + rRange.aStart.SetCol( nCol ); + break; + } + } + } + + nWidth = 0; + rRange.aEnd.SetCol( 0 ); + if (nWidth <= nEndX) + { + for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol())) //TODO: start at Start + { + nWidth += pDoc->GetColWidth(nCol,nTab); + if (nWidth > nEndX) + { + rRange.aEnd.SetCol( nCol ); + break; + } + } + } + } + + if (bSetVer) + { + nStartY = o3tl::toTwips(nStartY, o3tl::Length::mm100); + nEndY = o3tl::toTwips(nEndY, o3tl::Length::mm100); + SCROW nRow = pDoc->GetRowForHeight( nTab, nStartY); + rRange.aStart.SetRow( nRow>0 ? (nRow-1) : 0); + nRow = pDoc->GetRowForHeight( nTab, nEndY); + rRange.aEnd.SetRow( nRow == pDoc->MaxRow() ? pDoc->MaxRow() : + (nRow>0 ? (nRow-1) : 0)); + } + } + else + { + if (bSetHor) + { + rRange.aStart.SetCol(0); + rRange.aEnd.SetCol(0); + } + if (bSetVer) + { + rRange.aStart.SetRow(0); + rRange.aEnd.SetRow(0); + } + } + return bAny; +} + +void ScDrawLayer::AddCalcUndo( std::unique_ptr<SdrUndoAction> pUndo ) +{ + if (bRecording) + { + if (!pUndoGroup) + pUndoGroup.reset(new SdrUndoGroup(*this)); + + pUndoGroup->AddAction( std::move(pUndo) ); + } +} + +void ScDrawLayer::BeginCalcUndo(bool bDisableTextEditUsesCommonUndoManager) +{ + SetDisableTextEditUsesCommonUndoManager(bDisableTextEditUsesCommonUndoManager); + pUndoGroup.reset(); + bRecording = true; +} + +std::unique_ptr<SdrUndoGroup> ScDrawLayer::GetCalcUndo() +{ + std::unique_ptr<SdrUndoGroup> pRet = std::move(pUndoGroup); + bRecording = false; + SetDisableTextEditUsesCommonUndoManager(false); + return pRet; +} + +void ScDrawLayer::MoveArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2, + SCCOL nDx,SCROW nDy, bool bInsDel, bool bUpdateNoteCaptionPos ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::MoveArea without document" ); + if ( !pDoc ) + return; + + if (!bAdjustEnabled) + return; + + bool bNegativePage = pDoc->IsNegativePage( nTab ); + + tools::Rectangle aRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab ); + lcl_ReverseTwipsToMM( aRect ); + //TODO: use twips directly? + + Point aMove; + + if (nDx > 0) + for (SCCOL s=0; s<nDx; s++) + aMove.AdjustX(pDoc->GetColWidth(s+nCol1,nTab) ); + else + for (SCCOL s=-1; s>=nDx; s--) + aMove.AdjustX( -(pDoc->GetColWidth(s+nCol1,nTab)) ); + if (nDy > 0) + aMove.AdjustY(pDoc->GetRowHeight( nRow1, nRow1+nDy-1, nTab) ); + else + aMove.AdjustY( -sal_Int16(pDoc->GetRowHeight( nRow1+nDy, nRow1-1, nTab)) ); + + if ( bNegativePage ) + aMove.setX( -aMove.X() ); + + Point aTopLeft = aRect.TopLeft(); // Beginning when zoomed out + if (bInsDel) + { + if ( aMove.X() != 0 && nDx < 0 ) // nDx counts cells, sign is independent of RTL + aTopLeft.AdjustX(aMove.X() ); + if ( aMove.Y() < 0 ) + aTopLeft.AdjustY(aMove.Y() ); + } + + // Detectiv arrows: Adjust cell position + + MoveCells( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy, bUpdateNoteCaptionPos ); +} + +bool ScDrawLayer::HasObjectsInRows( SCTAB nTab, SCROW nStartRow, SCROW nEndRow ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::HasObjectsInRows without document" ); + if ( !pDoc ) + return false; + + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page not found"); + if (!pPage) + return false; + + // for an empty page, there's no need to calculate the row heights + if (!pPage->GetObjCount()) + return false; + + tools::Rectangle aTestRect; + + aTestRect.AdjustTop(pDoc->GetRowHeight( 0, nStartRow-1, nTab) ); + + if (nEndRow==pDoc->MaxRow()) + aTestRect.SetBottom( MAXMM ); + else + { + aTestRect.SetBottom( aTestRect.Top() ); + aTestRect.AdjustBottom(pDoc->GetRowHeight( nStartRow, nEndRow, nTab) ); + aTestRect.SetBottom(convertTwipToMm100(aTestRect.Bottom())); + } + + aTestRect.SetTop(convertTwipToMm100(aTestRect.Top())); + + aTestRect.SetLeft( 0 ); + aTestRect.SetRight( MAXMM ); + + bool bNegativePage = pDoc->IsNegativePage( nTab ); + if ( bNegativePage ) + MirrorRectRTL( aTestRect ); + + bool bFound = false; + + tools::Rectangle aObjRect; + SdrObjListIter aIter( pPage ); + SdrObject* pObject = aIter.Next(); + while ( pObject && !bFound ) + { + aObjRect = pObject->GetSnapRect(); //TODO: GetLogicRect ? + if (aTestRect.Contains(aObjRect.TopLeft()) || aTestRect.Contains(aObjRect.BottomLeft())) + bFound = true; + + pObject = aIter.Next(); + } + + return bFound; +} + +void ScDrawLayer::DeleteObjectsInArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1, + SCCOL nCol2,SCROW nRow2, bool bAnchored ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInArea without document" ); + if ( !pDoc ) + return; + + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) + return; + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + tools::Rectangle aDelRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab ); + tools::Rectangle aDelCircle = aDelRect; + aDelCircle.AdjustLeft(-250); + aDelCircle.AdjustRight(250); + aDelCircle.AdjustTop(-70); + aDelCircle.AdjustBottom(70); + + std::vector<SdrObject*> ppObj; + ppObj.reserve(nObjCount); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + // do not delete note caption, they are always handled by the cell note + // TODO: detective objects are still deleted, is this desired? + if (!IsNoteCaption( pObject )) + { + tools::Rectangle aObjRect; + ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject); + if (pObjData && pObjData->meType == ScDrawObjData::ValidationCircle) + { + aObjRect = pObject->GetLogicRect(); + if(aDelCircle.Contains(aObjRect)) + ppObj.push_back(pObject); + } + else + { + aObjRect = pObject->GetCurrentBoundRect(); + if (aDelRect.Contains(aObjRect)) + { + if (bAnchored) + { + ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject); + if (aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE) + ppObj.push_back(pObject); + } + else + ppObj.push_back(pObject); + } + } + } + + pObject = aIter.Next(); + } + + if (bRecording) + for (auto p : ppObj) + AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p)); + + for (auto p : ppObj) + pPage->RemoveObject(p->GetOrdNum()); +} + +void ScDrawLayer::DeleteObjectsInSelection( const ScMarkData& rMark ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInSelection without document" ); + if ( !pDoc ) + return; + + if ( !rMark.IsMultiMarked() ) + return; + + const ScRange& aMarkRange = rMark.GetMultiMarkArea(); + + SCTAB nTabCount = pDoc->GetTableCount(); + for (const SCTAB nTab : rMark) + { + if (nTab >= nTabCount) + break; + + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + if (pPage) + { + pPage->RecalcObjOrdNums(); + const size_t nObjCount = pPage->GetObjCount(); + if (nObjCount) + { + // Rectangle around the whole selection + tools::Rectangle aMarkBound = pDoc->GetMMRect( + aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), + aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab ); + + std::vector<SdrObject*> ppObj; + ppObj.reserve(nObjCount); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + // do not delete note caption, they are always handled by the cell note + // TODO: detective objects are still deleted, is this desired? + if (!IsNoteCaption( pObject )) + { + tools::Rectangle aObjRect = pObject->GetCurrentBoundRect(); + ScRange aRange = pDoc->GetRange(nTab, aObjRect); + bool bObjectInMarkArea = + aMarkBound.Contains(aObjRect) && rMark.IsAllMarked(aRange); + const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject); + ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject); + bool bObjectAnchoredToMarkedCell + = ((aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE) + && pObjData && pObjData->maStart.IsValid() + && rMark.IsCellMarked(pObjData->maStart.Col(), + pObjData->maStart.Row())); + if (bObjectInMarkArea || bObjectAnchoredToMarkedCell) + { + ppObj.push_back(pObject); + } + } + + pObject = aIter.Next(); + } + + // Delete objects (backwards) + + if (bRecording) + for (auto p : ppObj) + AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p)); + + for (auto p : ppObj) + pPage->RemoveObject(p->GetOrdNum()); + } + } + else + { + OSL_FAIL("pPage?"); + } + } +} + +void ScDrawLayer::CopyToClip( ScDocument* pClipDoc, SCTAB nTab, const tools::Rectangle& rRange ) +{ + // copy everything in the specified range into the same page (sheet) in the clipboard doc + + SdrPage* pSrcPage = GetPage(static_cast<sal_uInt16>(nTab)); + if (!pSrcPage) + return; + + ScDrawLayer* pDestModel = nullptr; + SdrPage* pDestPage = nullptr; + + SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat ); + SdrObject* pOldObject = aIter.Next(); + while (pOldObject) + { + tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect(); + + bool bObjectInArea = rRange.Contains(aObjRect); + const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject); + if (pObjData) + { + ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nTab); + bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart); + } + + // do not copy internal objects (detective) and note captions + if (bObjectInArea && pOldObject->GetLayer() != SC_LAYER_INTERN + && !IsNoteCaption(pOldObject)) + { + if ( !pDestModel ) + { + pDestModel = pClipDoc->GetDrawLayer(); // does the document already have a drawing layer? + if ( !pDestModel ) + { + // allocate drawing layer in clipboard document only if there are objects to copy + + pClipDoc->InitDrawLayer(); //TODO: create contiguous pages + pDestModel = pClipDoc->GetDrawLayer(); + } + if (pDestModel) + pDestPage = pDestModel->GetPage( static_cast<sal_uInt16>(nTab) ); + } + + OSL_ENSURE( pDestPage, "no page" ); + if (pDestPage) + { + // Clone to target SdrModel + SdrObject* pNewObject(pOldObject->CloneSdrObject(*pDestModel)); + + uno::Reference< chart2::XChartDocument > xOldChart( ScChartHelper::GetChartFromSdrObject( pOldObject ) ); + if(!xOldChart.is())//#i110034# do not move charts as they lose all their data references otherwise + pNewObject->NbcMove(Size(0,0)); + pDestPage->InsertObject( pNewObject ); + + // no undo needed in clipboard document + // charts are not updated + } + } + + pOldObject = aIter.Next(); + } +} + +static bool lcl_IsAllInRange( const ::std::vector< ScRangeList >& rRangesVector, const ScRange& rClipRange ) +{ + // check if every range of rRangesVector is completely in rClipRange + + for( const ScRangeList& rRanges : rRangesVector ) + { + for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ ) + { + const ScRange & rRange = rRanges[ i ]; + if ( !rClipRange.Contains( rRange ) ) + { + return false; // at least one range is not valid + } + } + } + + return true; // everything is fine +} + +static bool lcl_MoveRanges( ::std::vector< ScRangeList >& rRangesVector, const ScRange& rSourceRange, + const ScAddress& rDestPos, const ScDocument& rDoc ) +{ + bool bChanged = false; + + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + for( ScRangeList& rRanges : rRangesVector ) + { + for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ ) + { + ScRange & rRange = rRanges[ i ]; + if ( rSourceRange.Contains( rRange ) ) + { + SCCOL nDiffX = rDestPos.Col() - rSourceRange.aStart.Col(); + SCROW nDiffY = rDestPos.Row() - rSourceRange.aStart.Row(); + SCTAB nDiffZ = rDestPos.Tab() - rSourceRange.aStart.Tab(); + if (!rRange.Move( nDiffX, nDiffY, nDiffZ, aErrorRange, rDoc )) + { + assert(!"can't move range"); + } + bChanged = true; + } + } + } + + return bChanged; +} + +void ScDrawLayer::CopyFromClip( ScDrawLayer* pClipModel, SCTAB nSourceTab, const tools::Rectangle& rSourceRange, + const ScAddress& rDestPos, const tools::Rectangle& rDestRange ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::CopyFromClip without document" ); + if ( !pDoc ) + return; + + if (!pClipModel) + return; + + if (bDrawIsInUndo) //TODO: can this happen? + { + OSL_FAIL("CopyFromClip, bDrawIsInUndo"); + return; + } + + bool bMirrorObj = ( rSourceRange.Left() < 0 && rSourceRange.Right() < 0 && + rDestRange.Left() > 0 && rDestRange.Right() > 0 ) || + ( rSourceRange.Left() > 0 && rSourceRange.Right() > 0 && + rDestRange.Left() < 0 && rDestRange.Right() < 0 ); + tools::Rectangle aMirroredSource = rSourceRange; + if ( bMirrorObj ) + MirrorRectRTL( aMirroredSource ); + + SCTAB nDestTab = rDestPos.Tab(); + + SdrPage* pSrcPage = pClipModel->GetPage(static_cast<sal_uInt16>(nSourceTab)); + SdrPage* pDestPage = GetPage(static_cast<sal_uInt16>(nDestTab)); + OSL_ENSURE( pSrcPage && pDestPage, "draw page missing" ); + if ( !pSrcPage || !pDestPage ) + return; + + SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat ); + SdrObject* pOldObject = aIter.Next(); + + ScDocument* pClipDoc = pClipModel->GetDocument(); + // a clipboard document and its source share the same document item pool, + // so the pointers can be compared to see if this is copy&paste within + // the same document + bool bSameDoc = pDoc && pClipDoc && pDoc->GetPool() == pClipDoc->GetPool(); + bool bDestClip = pDoc && pDoc->IsClipboard(); + + //#i110034# charts need correct sheet names for xml range conversion during load + //so the target sheet name is temporarily renamed (if we have any SdrObjects) + OUString aDestTabName; + bool bRestoreDestTabName = false; + if( pOldObject && !bSameDoc && !bDestClip ) + { + if( pDoc && pClipDoc ) + { + OUString aSourceTabName; + if( pClipDoc->GetName( nSourceTab, aSourceTabName ) + && pDoc->GetName( nDestTab, aDestTabName ) ) + { + if( aSourceTabName != aDestTabName && + pDoc->ValidNewTabName(aSourceTabName) ) + { + bRestoreDestTabName = pDoc->RenameTab( nDestTab, aSourceTabName ); + } + } + } + } + + // first mirror, then move + Size aMove( rDestRange.Left() - aMirroredSource.Left(), rDestRange.Top() - aMirroredSource.Top() ); + + tools::Long nDestWidth = rDestRange.GetWidth(); + tools::Long nDestHeight = rDestRange.GetHeight(); + tools::Long nSourceWidth = rSourceRange.GetWidth(); + tools::Long nSourceHeight = rSourceRange.GetHeight(); + + tools::Long nWidthDiff = nDestWidth - nSourceWidth; + tools::Long nHeightDiff = nDestHeight - nSourceHeight; + + Fraction aHorFract(1,1); + Fraction aVerFract(1,1); + bool bResize = false; + // sizes can differ by 1 from twips->1/100mm conversion for equal cell sizes, + // don't resize to empty size when pasting into hidden columns or rows + if ( std::abs(nWidthDiff) > 1 && nDestWidth > 1 && nSourceWidth > 1 ) + { + aHorFract = Fraction( nDestWidth, nSourceWidth ); + bResize = true; + } + if ( std::abs(nHeightDiff) > 1 && nDestHeight > 1 && nSourceHeight > 1 ) + { + aVerFract = Fraction( nDestHeight, nSourceHeight ); + bResize = true; + } + Point aRefPos = rDestRange.TopLeft(); // for resizing (after moving) + + while (pOldObject) + { + tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect(); + // do not copy internal objects (detective) and note captions + + SCTAB nClipTab = bRestoreDestTabName ? nDestTab : nSourceTab; + ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nClipTab); + + bool bObjectInArea = rSourceRange.Contains(aObjRect); + const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject); + if (pObjData) // Consider images anchored to the copied cell + bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart); + if (bObjectInArea && (pOldObject->GetLayer() != SC_LAYER_INTERN) + && !IsNoteCaption(pOldObject)) + { + // Clone to target SdrModel + SdrObject* pNewObject(pOldObject->CloneSdrObject(*this)); + + if ( bMirrorObj ) + MirrorRTL( pNewObject ); // first mirror, then move + + pNewObject->NbcMove( aMove ); + if ( bResize ) + pNewObject->NbcResize( aRefPos, aHorFract, aVerFract ); + + pDestPage->InsertObject( pNewObject ); + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) ); + + //#i110034# handle chart data references (after InsertObject) + + if ( pNewObject->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pNewObject)->GetObjRef(); + uno::Reference< embed::XClassifiedObject > xClassified = xIPObj; + SvGlobalName aObjectClassName; + if ( xClassified.is() ) + { + try { + aObjectClassName = SvGlobalName( xClassified->getClassID() ); + } catch( uno::Exception& ) + { + // TODO: handle error? + } + } + + if ( xIPObj.is() && SotExchange::IsChart( aObjectClassName ) ) + { + uno::Reference< chart2::XChartDocument > xNewChart( ScChartHelper::GetChartFromSdrObject( pNewObject ) ); + if( xNewChart.is() && !xNewChart->hasInternalDataProvider() ) + { + OUString aChartName = static_cast<SdrOle2Obj*>(pNewObject)->GetPersistName(); + ::std::vector< ScRangeList > aRangesVector; + pDoc->GetChartRanges( aChartName, aRangesVector, *pDoc ); + if( !aRangesVector.empty() ) + { + bool bInSourceRange = false; + if ( pClipDoc ) + { + bInSourceRange = lcl_IsAllInRange( aRangesVector, aClipRange ); + } + + // always lose references when pasting into a clipboard document (transpose) + if ( ( bInSourceRange || bSameDoc ) && !bDestClip ) + { + if ( bInSourceRange ) + { + if ( rDestPos != aClipRange.aStart ) + { + // update the data ranges to the new (copied) position + if ( lcl_MoveRanges( aRangesVector, aClipRange, rDestPos, *pDoc ) ) + pDoc->SetChartRanges( aChartName, aRangesVector ); + } + } + else + { + // leave the ranges unchanged + } + } + else + { + // pasting into a new document without the complete source data + // -> break connection to source data and switch to own data + + uno::Reference< chart::XChartDocument > xOldChartDoc( ScChartHelper::GetChartFromSdrObject( pOldObject ), uno::UNO_QUERY ); + uno::Reference< chart::XChartDocument > xNewChartDoc( xNewChart, uno::UNO_QUERY ); + if( xOldChartDoc.is() && xNewChartDoc.is() ) + xNewChartDoc->attachData( xOldChartDoc->getData() ); + + // (see ScDocument::UpdateChartListenerCollection, PastingDrawFromOtherDoc) + } + } + } + } + } + } + + pOldObject = aIter.Next(); + } + + if( bRestoreDestTabName ) + pDoc->RenameTab( nDestTab, aDestTabName ); +} + +void ScDrawLayer::MirrorRTL( SdrObject* pObj ) +{ + OSL_ENSURE( pDoc, "ScDrawLayer::MirrorRTL - missing document" ); + if( !pDoc ) + return; + + SdrObjKind nIdent = pObj->GetObjIdentifier(); + + // don't mirror OLE or graphics, otherwise ask the object + // if it can be mirrored + bool bCanMirror = ( nIdent != SdrObjKind::Graphic && nIdent != SdrObjKind::OLE2 ); + if (bCanMirror) + { + SdrObjTransformInfoRec aInfo; + pObj->TakeObjInfo( aInfo ); + bCanMirror = aInfo.bMirror90Allowed; + } + + if (bCanMirror) + { + ScDrawObjData* pData = GetObjData(pObj); + if (pData) // cell anchored + { + // Remember values from positive side. + const tools::Rectangle aOldSnapRect = pObj->GetSnapRect(); + const tools::Rectangle aOldLogicRect = pObj->GetLogicRect(); + // Generate noRotate anchor if missing. + ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj); + if (!pNoRotatedAnchor) + { + ScDrawObjData aNoRotateAnchor; + const tools::Rectangle aLogicRect(pObj->GetLogicRect()); + GetCellAnchorFromPosition(aLogicRect, aNoRotateAnchor, + *pDoc, pData->maStart.Tab()); + aNoRotateAnchor.mbResizeWithCell = pData->mbResizeWithCell; + SetNonRotatedAnchor(*pObj, aNoRotateAnchor); + pNoRotatedAnchor = GetNonRotatedObjData(pObj); + assert(pNoRotatedAnchor); + } + // Mirror object at vertical axis + Point aRef1( 0, 0 ); + Point aRef2( 0, 1 ); + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + pObj->Mirror( aRef1, aRef2 ); + + // Adapt offsets in pNoRotatedAnchor so, that object will be moved to current position in + // save and reload. + const tools::Long nInverseShift = aOldSnapRect.Left() + aOldSnapRect.Right(); + const Point aLogicLT = pObj->GetLogicRect().TopLeft(); + const Point aMirroredLogicLT = aLogicLT + Point(nInverseShift, 0); + const Point aOffsetDiff = aMirroredLogicLT - aOldLogicRect.TopLeft(); + // new Offsets + pNoRotatedAnchor->maStartOffset += aOffsetDiff; + pNoRotatedAnchor->maEndOffset += aOffsetDiff; + } + else // page anchored + { + Point aRef1( 0, 0 ); + Point aRef2( 0, 1 ); + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) ); + pObj->Mirror( aRef1, aRef2 ); + } + } + else + { + // Move instead of mirroring: + // New start position is negative of old end position + // -> move by sum of start and end position + tools::Rectangle aObjRect = pObj->GetSnapRect(); + Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 ); + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) ); + pObj->Move( aMoveSize ); + } + + // for cell anchored objects adapt rectangles in anchors + ScDrawObjData* pData = GetObjData(pObj); + if (pData) + { + pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible()); + ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/); + pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible()); + } +} + +void ScDrawLayer::MoveRTL(SdrObject* pObj) +{ + tools::Rectangle aObjRect = pObj->GetSnapRect(); + Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 ); + if (bRecording) + AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) ); + pObj->Move( aMoveSize ); + + // for cell anchored objects adapt rectangles in anchors + ScDrawObjData* pData = GetObjData(pObj); + if (pData) + { + pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible()); + ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/); + pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible()); + } +} + +void ScDrawLayer::MirrorRectRTL( tools::Rectangle& rRect ) +{ + // mirror and swap left/right + tools::Long nTemp = rRect.Left(); + rRect.SetLeft( -rRect.Right() ); + rRect.SetRight( -nTemp ); +} + +tools::Rectangle ScDrawLayer::GetCellRect( const ScDocument& rDoc, const ScAddress& rPos, bool bMergedCell ) +{ + tools::Rectangle aCellRect; + OSL_ENSURE( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ), "ScDrawLayer::GetCellRect - invalid cell address" ); + if( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ) ) + { + // find top left position of passed cell address + Point aTopLeft; + for( SCCOL nCol = 0; nCol < rPos.Col(); ++nCol ) + aTopLeft.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) ); + if( rPos.Row() > 0 ) + aTopLeft.AdjustY(rDoc.GetRowHeight( 0, rPos.Row() - 1, rPos.Tab() ) ); + + // find bottom-right position of passed cell address + ScAddress aEndPos = rPos; + if( bMergedCell ) + { + const ScMergeAttr* pMerge = rDoc.GetAttr( rPos, ATTR_MERGE ); + if( pMerge->GetColMerge() > 1 ) + aEndPos.IncCol( pMerge->GetColMerge() - 1 ); + if( pMerge->GetRowMerge() > 1 ) + aEndPos.IncRow( pMerge->GetRowMerge() - 1 ); + } + Point aBotRight = aTopLeft; + for( SCCOL nCol = rPos.Col(); nCol <= aEndPos.Col(); ++nCol ) + aBotRight.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) ); + aBotRight.AdjustY(rDoc.GetRowHeight( rPos.Row(), aEndPos.Row(), rPos.Tab() ) ); + + // twips -> 1/100 mm + aTopLeft = o3tl::convert(aTopLeft, o3tl::Length::twip, o3tl::Length::mm100); + aBotRight = o3tl::convert(aBotRight, o3tl::Length::twip, o3tl::Length::mm100); + + aCellRect = tools::Rectangle( aTopLeft, aBotRight ); + if( rDoc.IsNegativePage( rPos.Tab() ) ) + MirrorRectRTL( aCellRect ); + } + return aCellRect; +} + +OUString ScDrawLayer::GetVisibleName( const SdrObject* pObj ) +{ + OUString aName = pObj->GetName(); + if ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + // For OLE, the user defined name (GetName) is used + // if it's not empty (accepting possibly duplicate names), + // otherwise the persist name is used so every object appears + // in the Navigator at all. + + if ( aName.isEmpty() ) + aName = static_cast<const SdrOle2Obj*>(pObj)->GetPersistName(); + } + return aName; +} + +static bool IsNamedObject( const SdrObject* pObj, std::u16string_view rName ) +{ + // sal_True if rName is the object's Name or PersistName + // (used to find a named object) + + return ( pObj->GetName() == rName || + ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 && + static_cast<const SdrOle2Obj*>(pObj)->GetPersistName() == rName ) ); +} + +SdrObject* ScDrawLayer::GetNamedObject( std::u16string_view rName, SdrObjKind nId, SCTAB& rFoundTab ) const +{ + sal_uInt16 nTabCount = GetPageCount(); + for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++) + { + const SdrPage* pPage = GetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( nId == SdrObjKind::NONE || pObject->GetObjIdentifier() == nId ) + if ( IsNamedObject( pObject, rName ) ) + { + rFoundTab = static_cast<SCTAB>(nTab); + return pObject; + } + + pObject = aIter.Next(); + } + } + } + + return nullptr; +} + +OUString ScDrawLayer::GetNewGraphicName( tools::Long* pnCounter ) const +{ + OUString aBase = ScResId(STR_GRAPHICNAME) + " "; + + bool bThere = true; + OUString aGraphicName; + SCTAB nDummy; + tools::Long nId = pnCounter ? *pnCounter : 0; + while (bThere) + { + ++nId; + aGraphicName = aBase + OUString::number( nId ); + bThere = ( GetNamedObject( aGraphicName, SdrObjKind::NONE, nDummy ) != nullptr ); + } + + if ( pnCounter ) + *pnCounter = nId; + + return aGraphicName; +} + +void ScDrawLayer::EnsureGraphicNames() +{ + // make sure all graphic objects have names (after Excel import etc.) + + sal_uInt16 nTabCount = GetPageCount(); + for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++) + { + SdrPage* pPage = GetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups ); + SdrObject* pObject = aIter.Next(); + + /* The index passed to GetNewGraphicName() will be set to + the used index in each call. This prevents the repeated search + for all names from 1 to current index. */ + tools::Long nCounter = 0; + + while (pObject) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::Graphic && pObject->GetName().isEmpty()) + pObject->SetName( GetNewGraphicName( &nCounter ) ); + + pObject = aIter.Next(); + } + } + } +} + +namespace +{ + SdrObjUserData* GetFirstUserDataOfType(const SdrObject *pObj, sal_uInt16 nId) + { + sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0; + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + SdrObjUserData* pData = pObj->GetUserData( i ); + if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId ) + return pData; + } + return nullptr; + } + + void DeleteFirstUserDataOfType(SdrObject *pObj, sal_uInt16 nId) + { + sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0; + for( sal_uInt16 i = nCount; i > 0; i-- ) + { + SdrObjUserData* pData = pObj->GetUserData( i-1 ); + if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId ) + pObj->DeleteUserData(i-1); + } + } +} + +void ScDrawLayer::SetNonRotatedAnchor(SdrObject& rObj, const ScDrawObjData& rAnchor) +{ + ScDrawObjData* pAnchor = GetNonRotatedObjData( &rObj, true ); + pAnchor->maStart = rAnchor.maStart; + pAnchor->maEnd = rAnchor.maEnd; + pAnchor->maStartOffset = rAnchor.maStartOffset; + pAnchor->maEndOffset = rAnchor.maEndOffset; + pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell; +} + +void ScDrawLayer::SetCellAnchored( SdrObject &rObj, const ScDrawObjData &rAnchor ) +{ + ScDrawObjData* pAnchor = GetObjData( &rObj, true ); + pAnchor->maStart = rAnchor.maStart; + pAnchor->maEnd = rAnchor.maEnd; + pAnchor->maStartOffset = rAnchor.maStartOffset; + pAnchor->maEndOffset = rAnchor.maEndOffset; + pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell; +} + +void ScDrawLayer::SetCellAnchoredFromPosition( SdrObject &rObj, const ScDocument &rDoc, SCTAB nTab, + bool bResizeWithCell ) +{ + if (!rObj.IsVisible()) + return; + ScDrawObjData aAnchor; + // set anchor in terms of the visual ( SnapRect ) + // object ( e.g. for when object is rotated ) + const tools::Rectangle aObjRect(rObj.GetSnapRect()); + GetCellAnchorFromPosition( + aObjRect, + aAnchor, + rDoc, + nTab); + + aAnchor.mbResizeWithCell = bResizeWithCell; + SetCellAnchored( rObj, aAnchor ); + + // absolutely necessary to set flag, ScDrawLayer::RecalcPos expects it. + if ( ScDrawObjData* pAnchor = GetObjData( &rObj ) ) + { + pAnchor->setShapeRect(&rDoc, rObj.GetSnapRect()); + } + + // - keep also an anchor in terms of the Logic ( untransformed ) object + // because that's what we stored ( and still do ) to xml + + // Vertical flipped custom shapes need special treatment, see comment in + // lcl_SetLogicRectFromAnchor + tools::Rectangle aObjRect2; + if (lcl_NeedsMirrorYCorrection(&rObj)) + { + const tools::Rectangle aRect(rObj.GetSnapRect()); + const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1); + const Point aRight(aLeft.X() + 1000, aLeft.Y()); + rObj.NbcMirror(aLeft, aRight); + aObjRect2 = rObj.GetLogicRect(); + rObj.NbcMirror(aLeft, aRight); + } + else if (rObj.GetObjIdentifier() == SdrObjKind::Measure) + { + // tdf#137576. A SdrMeasureObj might have a wrong logic rect here. TakeUnrotatedSnapRect + // calculates the current unrotated snap rectangle, sets logic rectangle and returns it. + static_cast<SdrMeasureObj&>(rObj).TakeUnrotatedSnapRect(aObjRect2); + } + else + aObjRect2 = rObj.GetLogicRect(); + + // Values in XML are so as if it is a LTR sheet. The object is shifted to negative page on loading + // so that the snap rectangle appears mirrored. For transformed objects the shifted logic rectangle + // is not the mirrored LTR rectangle. We calculate the mirrored LTR rectangle here. + if (rDoc.IsNegativePage(nTab)) + { + const tools::Rectangle aSnapRect(rObj.GetSnapRect()); + aObjRect2.Move(Size(-aSnapRect.Left() - aSnapRect.Right(), 0)); + MirrorRectRTL(aObjRect2); + } + + ScDrawObjData aNoRotatedAnchor; + GetCellAnchorFromPosition( + aObjRect2, + aNoRotatedAnchor, + rDoc, + nTab); + + aNoRotatedAnchor.mbResizeWithCell = bResizeWithCell; + SetNonRotatedAnchor( rObj, aNoRotatedAnchor); + // And update maShapeRect. It is used in adjustAnchoredPosition() in ScDrawView::Notify(). + if (ScDrawObjData* pAnchor = GetNonRotatedObjData(&rObj)) + { + pAnchor->setShapeRect(&rDoc, rObj.GetLogicRect()); + } +} + +void ScDrawLayer::GetCellAnchorFromPosition( + const tools::Rectangle &rObjRect, + ScDrawObjData &rAnchor, + const ScDocument &rDoc, + SCTAB nTab, + bool bHiddenAsZero) +{ + ScRange aRange = rDoc.GetRange( nTab, rObjRect, bHiddenAsZero ); + + tools::Rectangle aCellRect; + + rAnchor.maStart = aRange.aStart; + aCellRect = rDoc.GetMMRect( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aStart.Col(), aRange.aStart.Row(), aRange.aStart.Tab(), bHiddenAsZero ); + rAnchor.maStartOffset.setY( rObjRect.Top()-aCellRect.Top() ); + if (!rDoc.IsNegativePage(nTab)) + rAnchor.maStartOffset.setX( rObjRect.Left()-aCellRect.Left() ); + else + rAnchor.maStartOffset.setX( aCellRect.Right()-rObjRect.Right() ); + + rAnchor.maEnd = aRange.aEnd; + aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab(), bHiddenAsZero ); + if (!rObjRect.IsEmpty()) + rAnchor.maEndOffset.setY( rObjRect.Bottom()-aCellRect.Top() ); + if (!rDoc.IsNegativePage(nTab)) + { + if (!rObjRect.IsEmpty()) + rAnchor.maEndOffset.setX( rObjRect.Right()-aCellRect.Left() ); + } + else + rAnchor.maEndOffset.setX( aCellRect.Right()-rObjRect.Left() ); +} + +void ScDrawLayer::UpdateCellAnchorFromPositionEnd( const SdrObject &rObj, ScDrawObjData &rAnchor, const ScDocument &rDoc, SCTAB nTab, bool bUseLogicRect ) +{ + tools::Rectangle aObjRect(bUseLogicRect ? rObj.GetLogicRect() : rObj.GetSnapRect()); + ScRange aRange = rDoc.GetRange( nTab, aObjRect ); + + ScDrawObjData* pAnchor = &rAnchor; + pAnchor->maEnd = aRange.aEnd; + + tools::Rectangle aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab() ); + pAnchor->maEndOffset.setY( aObjRect.Bottom()-aCellRect.Top() ); + if (!rDoc.IsNegativePage(nTab)) + pAnchor->maEndOffset.setX( aObjRect.Right()-aCellRect.Left() ); + else + pAnchor->maEndOffset.setX( aCellRect.Right()-aObjRect.Left() ); +} + +bool ScDrawLayer::IsCellAnchored( const SdrObject& rObj ) +{ + // Cell anchored object always has a user data, to store the anchor cell + // info. If it doesn't then it's page-anchored. + return GetFirstUserDataOfType(&rObj, SC_UD_OBJDATA) != nullptr; +} + +bool ScDrawLayer::IsResizeWithCell( const SdrObject& rObj ) +{ + // Cell anchored object always has a user data, to store the anchor cell + // info. If it doesn't then it's page-anchored. + ScDrawObjData* pDrawObjData = GetObjData(const_cast<SdrObject*>(&rObj)); + if (!pDrawObjData) + return false; + + return pDrawObjData->mbResizeWithCell; +} + +void ScDrawLayer::SetPageAnchored( SdrObject &rObj ) +{ + DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA); + DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA); +} + +ScAnchorType ScDrawLayer::GetAnchorType( const SdrObject &rObj ) +{ + //If this object has a cell anchor associated with it + //then it's cell-anchored, otherwise it's page-anchored + const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(const_cast<SdrObject*>(&rObj)); + + // When there is no cell anchor, it is page anchored. + if (!pObjData) + return SCA_PAGE; + + // It's cell-anchored, check if the object resizes with the cell + if (pObjData->mbResizeWithCell) + return SCA_CELL_RESIZE; + + return SCA_CELL; +} + +std::vector<SdrObject*> +ScDrawLayer::GetObjectsAnchoredToRows(SCTAB nTab, SCROW nStartRow, SCROW nEndRow) +{ + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + if (!pPage || pPage->GetObjCount() < 1) + return std::vector<SdrObject*>(); + + std::vector<SdrObject*> aObjects; + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + ScRange aRange( 0, nStartRow, nTab, pDoc->MaxCol(), nEndRow, nTab); + while (pObject) + { + ScDrawObjData* pObjData = GetObjData(pObject); + if (pObjData && aRange.Contains(pObjData->maStart)) + aObjects.push_back(pObject); + pObject = aIter.Next(); + } + return aObjects; +} + +std::map<SCROW, std::vector<SdrObject*>> +ScDrawLayer::GetObjectsAnchoredToRange(SCTAB nTab, SCCOL nCol, SCROW nStartRow, SCROW nEndRow) +{ + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + if (!pPage || pPage->GetObjCount() < 1) + return std::map<SCROW, std::vector<SdrObject*>>(); + + std::map<SCROW, std::vector<SdrObject*>> aRowObjects; + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + ScRange aRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab); + while (pObject) + { + if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently + { + ScDrawObjData* pObjData = GetObjData(pObject); + if (pObjData && aRange.Contains(pObjData->maStart)) + aRowObjects[pObjData->maStart.Row()].push_back(pObject); + } + pObject = aIter.Next(); + } + return aRowObjects; +} + +bool ScDrawLayer::HasObjectsAnchoredInRange(const ScRange& rRange) +{ + // This only works for one table at a time + assert(rRange.aStart.Tab() == rRange.aEnd.Tab()); + + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(rRange.aStart.Tab())); + if (!pPage || pPage->GetObjCount() < 1) + return false; + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently + { + ScDrawObjData* pObjData = GetObjData(pObject); + if (pObjData && rRange.Contains(pObjData->maStart)) // Object is in given range + return true; + } + pObject = aIter.Next(); + } + return false; +} + +std::vector<SdrObject*> ScDrawLayer::GetObjectsAnchoredToCols(SCTAB nTab, SCCOL nStartCol, + SCCOL nEndCol) +{ + SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab)); + if (!pPage || pPage->GetObjCount() < 1) + return std::vector<SdrObject*>(); + + std::vector<SdrObject*> aObjects; + SdrObjListIter aIter(pPage, SdrIterMode::Flat); + SdrObject* pObject = aIter.Next(); + ScRange aRange(nStartCol, 0, nTab, nEndCol, pDoc->MaxRow(), nTab); + while (pObject) + { + ScDrawObjData* pObjData = GetObjData(pObject); + if (pObjData && aRange.Contains(pObjData->maStart)) + aObjects.push_back(pObject); + pObject = aIter.Next(); + } + return aObjects; +} + +void ScDrawLayer::MoveObject(SdrObject* pObject, const ScAddress& rNewPosition) +{ + // Get anchor data + ScDrawObjData* pObjData = GetObjData(pObject, false); + if (!pObjData) + return; + const ScAddress aOldStart = pObjData->maStart; + const ScAddress aOldEnd = pObjData->maEnd; + + // Set start address + pObjData->maStart = rNewPosition; + + // Set end address + const SCCOL nObjectColSpan = aOldEnd.Col() - aOldStart.Col(); + const SCROW nObjectRowSpan = aOldEnd.Row() - aOldStart.Row(); + ScAddress aNewEnd = rNewPosition; + aNewEnd.IncRow(nObjectRowSpan); + aNewEnd.IncCol(nObjectColSpan); + pObjData->maEnd = aNewEnd; + + // Update draw object according to new anchor + RecalcPos(pObject, *pObjData, false, false); +} + +ScDrawObjData* ScDrawLayer::GetNonRotatedObjData( SdrObject* pObj, bool bCreate ) +{ + sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0; + sal_uInt16 nFound = 0; + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + SdrObjUserData* pData = pObj->GetUserData( i ); + if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == SC_UD_OBJDATA && ++nFound == 2 ) + return static_cast<ScDrawObjData*>(pData); + } + if( pObj && bCreate ) + { + ScDrawObjData* pData = new ScDrawObjData; + pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData)); + return pData; + } + return nullptr; +} + +ScDrawObjData* ScDrawLayer::GetObjData( SdrObject* pObj, bool bCreate ) +{ + if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_OBJDATA)) + return static_cast<ScDrawObjData*>(pData); + + if( pObj && bCreate ) + { + ScDrawObjData* pData = new ScDrawObjData; + pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData)); + return pData; + } + return nullptr; +} + +ScDrawObjData* ScDrawLayer::GetObjDataTab( SdrObject* pObj, SCTAB nTab ) +{ + ScDrawObjData* pData = GetObjData( pObj ); + if ( pData ) + { + if ( pData->maStart.IsValid() ) + pData->maStart.SetTab( nTab ); + if ( pData->maEnd.IsValid() ) + pData->maEnd.SetTab( nTab ); + } + return pData; +} + +bool ScDrawLayer::IsNoteCaption( SdrObject* pObj ) +{ + ScDrawObjData* pData = pObj ? GetObjData( pObj ) : nullptr; + return pData && pData->meType == ScDrawObjData::CellNote; +} + +ScDrawObjData* ScDrawLayer::GetNoteCaptionData( SdrObject* pObj, SCTAB nTab ) +{ + ScDrawObjData* pData = pObj ? GetObjDataTab( pObj, nTab ) : nullptr; + return (pData && pData->meType == ScDrawObjData::CellNote) ? pData : nullptr; +} + +ScMacroInfo* ScDrawLayer::GetMacroInfo( SdrObject* pObj, bool bCreate ) +{ + if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_MACRODATA)) + return static_cast<ScMacroInfo*>(pData); + + if ( bCreate ) + { + ScMacroInfo* pData = new ScMacroInfo; + pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData)); + return pData; + } + return nullptr; +} + +void ScDrawLayer::SetGlobalDrawPersist(SfxObjectShell* pPersist) +{ + OSL_ENSURE(!pGlobalDrawPersist,"Multiple SetGlobalDrawPersist"); + pGlobalDrawPersist = pPersist; +} + +void ScDrawLayer::SetChanged( bool bFlg /* = true */ ) +{ + if ( bFlg && pDoc ) + pDoc->SetChartListenerCollectionNeedsUpdate( true ); + FmFormModel::SetChanged( bFlg ); +} + +css::uno::Reference< css::uno::XInterface > ScDrawLayer::createUnoModel() +{ + css::uno::Reference< css::uno::XInterface > xRet; + if( pDoc && pDoc->GetDocumentShell() ) + xRet = pDoc->GetDocumentShell()->GetModel(); + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |