diff options
Diffstat (limited to 'sc/source/core/data/drwlayer.cxx')
-rw-r--r-- | sc/source/core/data/drwlayer.cxx | 358 |
1 files changed, 271 insertions, 87 deletions
diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx index 87b29f5749dc..302b76036384 100644 --- a/sc/source/core/data/drwlayer.cxx +++ b/sc/source/core/data/drwlayer.cxx @@ -32,9 +32,13 @@ #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/svdograf.hxx> #include <svx/svdoole2.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdtrans.hxx> #include <svx/svdundo.hxx> #include <svx/sdsxyitm.hxx> #include <svx/svxids.hrc> @@ -70,6 +74,7 @@ #include <vcl/fieldvalues.hxx> #include <memory> +#include <algorithm> namespace com::sun::star::embed { class XEmbeddedObject; } @@ -579,6 +584,12 @@ void ScDrawLayer::SetPageSize( sal_uInt16 nPageNo, const Size& rSize, bool bUpda // even if size is still the same // (individual rows/columns can have been changed)) + // 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; + bool bNegativePage = pDoc && pDoc->IsNegativePage( static_cast<SCTAB>(nPageNo) ); // Disable mass broadcasts from drawing objects' position changes. @@ -593,6 +604,7 @@ void ScDrawLayer::SetPageSize( sal_uInt16 nPageNo, const Size& rSize, bool bUpda RecalcPos( pObj, *pData, bNegativePage, bUpdateNoteCaptionPos ); } setLock(bWasLocked); + } namespace @@ -627,8 +639,81 @@ namespace 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::labs(rRectA.Left() - rRectB.Left()) > 1) + return false; + if (std::labs(rRectA.Top() - rRectB.Top()) > 1) + return false; + if (std::labs(rRectA.Right() - rRectB.Right()) > 1) + return false; + if (std::labs(rRectA.Bottom() - rRectB.Bottom()) > 1) + return false; + return true; +} + +bool lcl_NeedsMirrorYCorrection(SdrObject* pObj) +{ + return pObj->GetObjIdentifier() == OBJ_CUSTOMSHAPE + && static_cast<SdrObjCustomShape*>(pObj)->IsMirroredY(); +} + +void lcl_SetLogicRectFromAnchor(SdrObject* pObj, ScDrawObjData& rAnchor, 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. + if (lcl_NeedsMirrorYCorrection(pObj)) + { + 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 (lcl_NeedsMirrorYCorrection(pObj)) + { + 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 bUseLogicRect, bool bNegativePage, bool bCanResize, bool bHiddenAsZero) @@ -783,6 +868,124 @@ void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData& 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. + // ToDO: Check whether its maShapeRect member is actually used. + 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() == OBJ_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( + reinterpret_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() == OBJ_MEASURE) + { + // Measure lines might have got wrong start and end anchor from XML import. Recreate + // them from start and end point. + const Point aPoint0(pObj->GetPoint(0)); + const Point aPoint1(pObj->GetPoint(1)); + const Point aPointLT(std::min(aPoint0.X(), aPoint1.X()), + std::min(aPoint0.Y(), aPoint1.Y())); + const Point aPointRB(std::max(aPoint0.X(), aPoint1.X()), + std::max(aPoint0.Y(), aPoint1.Y())); + const tools::Rectangle aObjRect(aPointLT, aPointRB); + 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 occure 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. ToDo: Really used? + // Currently ScExportTest::testMoveCellAnchoredShapesODS checks it. + 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. Adaption 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 visibiliy. ToDo: Really used? + rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible()); + + // And set maShapeRect in rData. It stores not only the current rectangles, but currently, + // existance 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" ); @@ -934,96 +1137,34 @@ void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegati } } } - } - else + } // 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; - //First time positioning, must be able to at least move it - ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData( pObj, true ); - if (rData.getShapeRect().IsEmpty()) - { - // Every shape it is saved with a negative offset relative to cell - ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObj); - if (aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE) - { - // tdf#117145 All that TR*etBaseGeometry does here is to translate - // the existing transformation. This can simply be applied to the existing - // matrix, no need to decompose as done before. Also doing this from - // Calc did not change metrics in any way. - const tools::Rectangle aRect(pDoc->GetMMRect(nCol1, nRow1, nCol1 , nRow1, nTab1)); - const Point aPoint(bNegativePage ? aRect.Right() : aRect.Left(), aRect.Top()); - basegfx::B2DPolyPolygon aPolyPolygon; - basegfx::B2DHomMatrix aOriginalMatrix; - - pObj->TRGetBaseGeometry(aOriginalMatrix, aPolyPolygon); - aOriginalMatrix.translate(aPoint.X(), aPoint.Y()); - pObj->TRSetBaseGeometry(aOriginalMatrix, aPolyPolygon); - } - - // It's confusing ( but blame that we persist the anchor in terms of unrotated shape ) - // that the initial anchor we get here is in terms of an unrotated shape ( if the shape is rotated ) - // we need to save the old anchor ( for persisting ) and also track any resize or repositions that happen. - - // This is an evil hack, having an anchor that is one minute in terms of untransformed object and then later - // in terms of the transformed object is not ideal, similarly having 2 anchors per object is wasteful, can't - // see another way out of this at the moment though. - rNoRotatedAnchor.maStart = rData.maStart; - rNoRotatedAnchor.maEnd = rData.maEnd; - rNoRotatedAnchor.maStartOffset = rData.maStartOffset; - rNoRotatedAnchor.maEndOffset = rData.maEndOffset; - - // get bounding rectangle of shape ( include any hidden row/columns ), <sigh> we need to do this - // because if the shape is rotated the anchor from xml is in terms of the unrotated shape, if - // the shape is hidden ( by the rows that contain the shape being hidden ) then our hack of - // trying to infer the 'real' e.g. rotated anchor from the SnapRect will fail (because the LogicRect will - // not have the correct position or size). The only way we can possible do this is to first get the - // 'unrotated' shape dimensions from the persisted Anchor (from xml) and then 'create' an Anchor from the - // associated rotated shape (note: we do this by actually setting the LogicRect for the shape temporarily to the - // *full* size then grabbing the SnapRect (which gives the transformed rotated dimensions), it would be - // wonderful if we could do this mathematically without having to temporarily tweak the object... otoh this way - // is guaranteed to get consistent results) - ResizeLastRectFromAnchor( pObj, rData, true, bNegativePage, bCanResize, false ); - // aFullRect contains the unrotated size and position of the shape (regardless of any hidden row/columns) - tools::Rectangle aFullRect = rData.getShapeRect(); - - // get current size and position from the anchor for use later - ResizeLastRectFromAnchor( pObj, rNoRotatedAnchor, true, bNegativePage, bCanResize ); - - // resize/position the shape to *full* size e.g. how it would be ( if no hidden rows/cols affected things ) - pObj->SetLogicRect(aFullRect); - - // Ok, here is more nastiness, from xml the Anchor is in terms of the LogicRect which is the - // untransformed unrotated shape, here we swap out that initial anchor and from now on use - // an Anchor based on the SnapRect ( which is what you see on the screen ) - const tools::Rectangle aObjRect(pObj->GetSnapRect()); - ScDrawLayer::GetCellAnchorFromPosition( - aObjRect, - rData, - *pDoc, - nTab1, - false); - - // reset shape to true 'maybe affected by hidden rows/cols' size calculated previously - pObj->SetLogicRect(rNoRotatedAnchor.getShapeRect()); - } + bool bCanResize = bValid2 && !pObj->IsResizeProtect() && rData.mbResizeWithCell; // update anchor with snap rect ResizeLastRectFromAnchor( pObj, rData, false, bNegativePage, bCanResize ); + ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData( pObj, true /*bCreate*/); + if( bCanResize ) { tools::Rectangle aNew = rData.getShapeRect(); - - if ( pObj->GetSnapRect() != aNew ) + tools::Rectangle aOld(pObj->GetSnapRect()); + if (!lcl_AreRectanglesApproxEqual(aNew, aOld)) { - tools::Rectangle aOld(pObj->GetSnapRect()); - 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) @@ -1036,16 +1177,17 @@ void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegati double fYFrac = static_cast<double>(aNew.GetHeight()) / static_cast<double>(nOldHeight); pObj->NbcResize(aNew.TopLeft(), Fraction(fXFrac), Fraction(fYFrac)); } - // order of these lines is important, modify rData.maLastRect carefully it is used as both - // a value and a flag for initialisation + rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(rData.getShapeRect()), pObj->IsVisible()); if (pObj->GetObjIdentifier() == OBJ_CUSTOMSHAPE) pObj->AdjustToMaxRect(rData.getShapeRect()); else pObj->SetSnapRect(rData.getShapeRect()); + // update 'unrotated anchor' it's the anchor we persist, it must be kept in sync // with the normal Anchor - ResizeLastRectFromAnchor( pObj, rNoRotatedAnchor, true, bNegativePage, bCanResize ); + // ToDo: Is maShapeRect of rNoRotatedAnchor used at all? + ResizeLastRectFromAnchor(pObj, rNoRotatedAnchor, true, bNegativePage, bCanResize); } } else @@ -1076,7 +1218,7 @@ void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegati pObj->getSdrModelFromSdrObject().setLock(bWasLocked); if (!bWasLocked) pObj->BroadcastObjectChange(); - } + } // end ScDrawObjData::DrawingObject } bool ScDrawLayer::GetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const @@ -2040,6 +2182,8 @@ void ScDrawLayer::SetCellAnchored( SdrObject &rObj, const ScDrawObjData &rAnchor 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 ) @@ -2052,24 +2196,40 @@ void ScDrawLayer::SetCellAnchoredFromPosition( SdrObject &rObj, const ScDocument 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 + aObjRect2 = rObj.GetLogicRect(); + ScDrawObjData aVisAnchor; - const tools::Rectangle aObjRect2(rObj.GetLogicRect()); GetCellAnchorFromPosition( aObjRect2, aVisAnchor, rDoc, - nTab); + nTab, false); aVisAnchor.mbResizeWithCell = bResizeWithCell; SetVisualCellAnchored( rObj, aVisAnchor ); - // absolutely necessary to set flag that in order to prevent ScDrawLayer::RecalcPos - // doing an initialisation hack - if ( ScDrawObjData* pAnchor = GetObjData( &rObj ) ) - { - pAnchor->setShapeRect(&rDoc, rObj.GetSnapRect()); - } } void ScDrawLayer::GetCellAnchorFromPosition( @@ -2236,6 +2396,30 @@ bool ScDrawLayer::HasObjectsAnchoredInRange(const ScRange& rRange) 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, MAXROW, nTab); + while (pObject) + { + if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently + { + ScDrawObjData* pObjData = GetObjData(pObject); + if (pObjData && aRange.In(pObjData->maStart)) + aObjects.push_back(pObject); + } + pObject = aIter.Next(); + } + return aObjects; +} + void ScDrawLayer::MoveObject(SdrObject* pObject, const ScAddress& rNewPosition) { // Get anchor data |