/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // AW: For VCOfDrawVirtObj and stuff #include #include #include #include #include #include #include using namespace ::com::sun::star; static bool bInResize = false; namespace sdr::contact { namespace { /** * @see #i95264# * * currently needed since createViewIndependentPrimitive2DSequence() is called when * RecalcBoundRect() is used. There should currently no VOCs being constructed since it * gets not visualized (instead the corresponding SwVirtFlyDrawObj's referencing this one * are visualized). */ class VCOfSwFlyDrawObj : public ViewContactOfSdrObj { protected: /** This method is responsible for creating the graphical visualisation data * * @note ONLY based on model data */ virtual drawinglayer::primitive2d::Primitive2DContainer createViewIndependentPrimitive2DSequence() const override; public: /// basic constructor, used from SdrObject. explicit VCOfSwFlyDrawObj(SwFlyDrawObj& rObj) : ViewContactOfSdrObj(rObj) { } }; } drawinglayer::primitive2d::Primitive2DContainer VCOfSwFlyDrawObj::createViewIndependentPrimitive2DSequence() const { // currently gets not visualized, return empty sequence return drawinglayer::primitive2d::Primitive2DContainer(); } } // end of namespace sdr::contact std::unique_ptr SwFlyDrawObj::CreateObjectSpecificProperties() { // create default properties return std::make_unique(*this); } std::unique_ptr SwFlyDrawObj::CreateObjectSpecificViewContact() { // needs an own VC since createViewIndependentPrimitive2DSequence() // is called when RecalcBoundRect() is used return std::make_unique(*this); } SwFlyDrawObj::SwFlyDrawObj(SdrModel& rSdrModel) : SdrObject(rSdrModel), mbIsTextBox(false) { } SwFlyDrawObj::~SwFlyDrawObj() { } // SwFlyDrawObj - Factory-Methods SdrInventor SwFlyDrawObj::GetObjInventor() const { return SdrInventor::Swg; } sal_uInt16 SwFlyDrawObj::GetObjIdentifier() const { return SwFlyDrawObjIdentifier; } // TODO: Need own primitive to get the FlyFrame paint working namespace drawinglayer::primitive2d { namespace { class SwVirtFlyDrawObjPrimitive : public BufferedDecompositionPrimitive2D { private: const SwVirtFlyDrawObj& mrSwVirtFlyDrawObj; const basegfx::B2DRange maOuterRange; protected: /// method which is to be used to implement the local decomposition of a 2D primitive virtual void create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const override; public: SwVirtFlyDrawObjPrimitive( const SwVirtFlyDrawObj& rSwVirtFlyDrawObj, const basegfx::B2DRange &rOuterRange) : BufferedDecompositionPrimitive2D(), mrSwVirtFlyDrawObj(rSwVirtFlyDrawObj), maOuterRange(rOuterRange) { } virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override; // override to allow callbacks to wrap_DoPaintObject virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override; // data read access const SwVirtFlyDrawObj& getSwVirtFlyDrawObj() const { return mrSwVirtFlyDrawObj; } const basegfx::B2DRange& getOuterRange() const { return maOuterRange; } /// provide unique ID virtual sal_uInt32 getPrimitive2DID() const override; }; } } // end of namespace drawinglayer::primitive2d namespace drawinglayer::primitive2d { void SwVirtFlyDrawObjPrimitive::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const { if(!getOuterRange().isEmpty()) { // currently this SW object has no primitive representation. As long as this is the case, // create invisible geometry to allow correct HitTest and BoundRect calculations for the // object. Use a filled primitive to get 'inside' as default object hit. The special cases from // the old SwVirtFlyDrawObj::CheckHit implementation are handled now in SwDrawView::PickObj; // this removed the 'hack' to get a view from inside model data or to react on null-tolerance // as it was done in the old implementation rContainer.push_back( createHiddenGeometryPrimitives2D( true, getOuterRange())); } } bool SwVirtFlyDrawObjPrimitive::operator==(const BasePrimitive2D& rPrimitive) const { if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) { const SwVirtFlyDrawObjPrimitive& rCompare = static_cast(rPrimitive); return (&getSwVirtFlyDrawObj() == &rCompare.getSwVirtFlyDrawObj() && getOuterRange() == rCompare.getOuterRange()); } return false; } basegfx::B2DRange SwVirtFlyDrawObjPrimitive::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const { return getOuterRange(); } void SwVirtFlyDrawObjPrimitive::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { // This is the callback to keep the FlyFrame painting in SW alive as long as it // is not changed to primitives. This is the method which will be called by the processors // when they do not know this primitive (and they do not). Inside wrap_DoPaintObject // there needs to be a test that paint is only done during SW repaints (see there). // Using this mechanism guarantees the correct Z-Order of the VirtualObject-based FlyFrames. getSwVirtFlyDrawObj().wrap_DoPaintObject(rViewInformation); // call parent BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } // provide unique ID ImplPrimitive2DIDBlock(SwVirtFlyDrawObjPrimitive, PRIMITIVE2D_ID_SWVIRTFLYDRAWOBJPRIMITIVE2D) } // end of namespace drawinglayer::primitive2d // AW: own sdr::contact::ViewContact (VC) sdr::contact::ViewObjectContact (VOC) needed // since offset is defined different from SdrVirtObj's sdr::contact::ViewContactOfVirtObj. // For paint, that offset is used by setting at the OutputDevice; for primitives this is // not possible since we have no OutputDevice, but define the geometry itself. namespace sdr::contact { namespace { class VCOfSwVirtFlyDrawObj : public ViewContactOfVirtObj { protected: /** This method is responsible for creating the graphical visualisation data * * @note ONLY based on model data */ virtual drawinglayer::primitive2d::Primitive2DContainer createViewIndependentPrimitive2DSequence() const override; public: /// basic constructor, used from SdrObject. explicit VCOfSwVirtFlyDrawObj(SwVirtFlyDrawObj& rObj) : ViewContactOfVirtObj(rObj) { } /// access to SwVirtFlyDrawObj SwVirtFlyDrawObj& GetSwVirtFlyDrawObj() const { return static_cast(mrObject); } }; } } // end of namespace sdr::contact namespace sdr::contact { drawinglayer::primitive2d::Primitive2DContainer VCOfSwVirtFlyDrawObj::createViewIndependentPrimitive2DSequence() const { drawinglayer::primitive2d::Primitive2DContainer xRetval; const SdrObject& rReferencedObject = GetSwVirtFlyDrawObj().GetReferencedObj(); if(dynamic_cast( &rReferencedObject) != nullptr) { // create an own specialized primitive which is used as repaint callpoint and HitTest // for HitTest processor (see primitive implementation above) const basegfx::B2DRange aOuterRange(GetSwVirtFlyDrawObj().getOuterBound()); if(!aOuterRange.isEmpty()) { const drawinglayer::primitive2d::Primitive2DReference xPrimitive( new drawinglayer::primitive2d::SwVirtFlyDrawObjPrimitive( GetSwVirtFlyDrawObj(), aOuterRange)); xRetval = drawinglayer::primitive2d::Primitive2DContainer { xPrimitive }; } } return xRetval; } } // end of namespace sdr::contact basegfx::B2DRange SwVirtFlyDrawObj::getOuterBound() const { basegfx::B2DRange aOuterRange; const SdrObject& rReferencedObject = GetReferencedObj(); if(dynamic_cast( &rReferencedObject) != nullptr) { const SwFlyFrame* pFlyFrame = GetFlyFrame(); if(pFlyFrame) { const tools::Rectangle aOuterRectangle(pFlyFrame->getFrameArea().Pos(), pFlyFrame->getFrameArea().SSize()); if(!aOuterRectangle.IsEmpty()) { aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Left(), aOuterRectangle.Top())); aOuterRange.expand(basegfx::B2DTuple(aOuterRectangle.Right(), aOuterRectangle.Bottom())); } } } return aOuterRange; } basegfx::B2DRange SwVirtFlyDrawObj::getInnerBound() const { basegfx::B2DRange aInnerRange; const SdrObject& rReferencedObject = GetReferencedObj(); if(dynamic_cast( &rReferencedObject) != nullptr) { const SwFlyFrame* pFlyFrame = GetFlyFrame(); if(pFlyFrame) { const tools::Rectangle aInnerRectangle(pFlyFrame->getFrameArea().Pos() + pFlyFrame->getFramePrintArea().Pos(), pFlyFrame->getFramePrintArea().SSize()); if(!aInnerRectangle.IsEmpty()) { aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Left(), aInnerRectangle.Top())); aInnerRange.expand(basegfx::B2DTuple(aInnerRectangle.Right(), aInnerRectangle.Bottom())); } } } return aInnerRange; } bool SwVirtFlyDrawObj::ContainsSwGrfNode() const { // RotGrfFlyFrame: Check if this is a SwGrfNode const SwFlyFrame* pFlyFrame(GetFlyFrame()); if(nullptr != pFlyFrame && pFlyFrame->Lower() && pFlyFrame->Lower()->IsNoTextFrame()) { const SwNoTextFrame *const pNTF(static_cast(pFlyFrame->Lower())); const SwGrfNode *const pGrfNd(pNTF->GetNode()->GetGrfNode()); return nullptr != pGrfNd; } return false; } bool SwVirtFlyDrawObj::HasLimitedRotation() const { // RotGrfFlyFrame: If true, this SdrObject supports only limited rotation. // This is the case for SwGrfNode instances return ContainsSwGrfNode(); } void SwVirtFlyDrawObj::Rotate(const Point& rRef, long nAngle, double sn, double cs) { if(ContainsSwGrfNode()) { // RotGrfFlyFrame: Here is where the positively completed rotate interaction is executed. // Rotation is in 1/100th degree and may be signed (!) nAngle /= 10; while(nAngle < 0) { nAngle += 3600; } SwWrtShell *pShForAngle = nAngle ? dynamic_cast(GetFlyFrame()->getRootFrame()->GetCurrShell()) : nullptr; if (pShForAngle) { // RotGrfFlyFrame: Add transformation to placeholder object Size aSize; const sal_uInt16 nOldRot(SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(aSize)); SwFlyFrameAttrMgr aMgr(false, pShForAngle, Frmmgr_Type::NONE, nullptr); aMgr.SetRotation(nOldRot, (nOldRot + static_cast(nAngle)) % 3600, aSize); } } else { // call parent SdrVirtObj::Rotate(rRef, nAngle, sn, cs); } } std::unique_ptr SwVirtFlyDrawObj::CreateObjectSpecificViewContact() { // need an own ViewContact (VC) to allow creation of a specialized primitive // for being able to visualize the FlyFrames in primitive renderers return std::make_unique(*this); } SwVirtFlyDrawObj::SwVirtFlyDrawObj( SdrModel& rSdrModel, SdrObject& rNew, SwFlyFrame* pFly) : SdrVirtObj(rSdrModel, rNew), m_pFlyFrame(pFly) { const SvxProtectItem &rP = m_pFlyFrame->GetFormat()->GetProtect(); bMovProt = rP.IsPosProtected(); bSizProt = rP.IsSizeProtected(); } SwVirtFlyDrawObj::~SwVirtFlyDrawObj() { if ( getSdrPageFromSdrObject() ) //Withdraw SdrPage the responsibility. getSdrPageFromSdrObject()->RemoveObject( GetOrdNum() ); } const SwFrameFormat *SwVirtFlyDrawObj::GetFormat() const { return GetFlyFrame()->GetFormat(); } SwFrameFormat *SwVirtFlyDrawObj::GetFormat() { return GetFlyFrame()->GetFormat(); } // --> OD #i102707# namespace { class RestoreMapMode { public: explicit RestoreMapMode( SwViewShell const * pViewShell ) : mbMapModeRestored( false ) , mpOutDev( pViewShell->GetOut() ) { if ( pViewShell->getPrePostMapMode() != mpOutDev->GetMapMode() ) { mpOutDev->Push(PushFlags::MAPMODE); GDIMetaFile* pMetaFile = mpOutDev->GetConnectMetaFile(); if ( pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause() ) { OSL_FAIL( "MapMode restoration during meta file creation is somehow suspect - using , but not sure, if correct." ); mpOutDev->SetRelativeMapMode( pViewShell->getPrePostMapMode() ); } else { mpOutDev->SetMapMode( pViewShell->getPrePostMapMode() ); } mbMapModeRestored = true; } }; ~RestoreMapMode() { if ( mbMapModeRestored ) { mpOutDev->Pop(); } }; private: bool mbMapModeRestored; VclPtr mpOutDev; }; } // <-- void SwVirtFlyDrawObj::wrap_DoPaintObject( drawinglayer::geometry::ViewInformation2D const& rViewInformation) const { SwViewShell* pShell = m_pFlyFrame->getRootFrame()->GetCurrShell(); // Only paint when we have a current shell and a DrawingLayer paint is in progress. // This avoids evtl. problems with renderers which do processing stuff, // but no paints. IsPaintInProgress() depends on SW repaint, so, as long // as SW paints self and calls DrawLayer() for Heaven and Hell, this will // be correct if ( pShell && pShell->IsDrawingLayerPaintInProgress() ) { bool bDrawObject(true); if ( !SwFlyFrame::IsPaint( const_cast(this), pShell ) ) { bDrawObject = false; } if ( bDrawObject ) { // if there's no viewport set, all fly-frames will be painted, // which is slow, wastes memory, and can cause other trouble. (void) rViewInformation; // suppress "unused parameter" warning assert(comphelper::LibreOfficeKit::isActive() || !rViewInformation.getViewport().isEmpty()); if ( !m_pFlyFrame->IsFlyInContentFrame() ) { // it is also necessary to restore the VCL MapMode from ViewInformation since e.g. // the VCL PixelRenderer resets it at the used OutputDevice. Unfortunately, this // excludes shears and rotates which are not expressible in MapMode. // OD #i102707# // new helper class to restore MapMode - restoration, only if // needed and consideration of paint for meta file creation . RestoreMapMode aRestoreMapModeIfNeeded( pShell ); // paint the FlyFrame (use standard VCL-Paint) m_pFlyFrame->PaintSwFrame( *pShell->GetOut(), GetFlyFrame()->getFrameArea() ); } } } } void SwVirtFlyDrawObj::TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const { rInfo.bMoveAllowed = rInfo.bResizeFreeAllowed = rInfo.bResizePropAllowed = true; // RotGrfFlyFrame: Some rotation may be allowed rInfo.bRotateFreeAllowed = rInfo.bRotate90Allowed = HasLimitedRotation(); rInfo.bMirrorFreeAllowed = rInfo.bMirror45Allowed = rInfo.bMirror90Allowed = rInfo.bShearAllowed = rInfo.bCanConvToPath = rInfo.bCanConvToPoly = rInfo.bCanConvToPathLineToArea = rInfo.bCanConvToPolyLineToArea = false; } // SwVirtFlyDrawObj - Size Determination void SwVirtFlyDrawObj::SetRect() const { if ( GetFlyFrame()->getFrameArea().HasArea() ) const_cast(this)->aOutRect = GetFlyFrame()->getFrameArea().SVRect(); else const_cast(this)->aOutRect = tools::Rectangle(); } const tools::Rectangle& SwVirtFlyDrawObj::GetCurrentBoundRect() const { SetRect(); return aOutRect; } const tools::Rectangle& SwVirtFlyDrawObj::GetLastBoundRect() const { return GetCurrentBoundRect(); } void SwVirtFlyDrawObj::RecalcBoundRect() { SetRect(); } void SwVirtFlyDrawObj::RecalcSnapRect() { SetRect(); } const tools::Rectangle& SwVirtFlyDrawObj::GetSnapRect() const { SetRect(); return aOutRect; } void SwVirtFlyDrawObj::SetSnapRect(const tools::Rectangle& ) { tools::Rectangle aTmp( GetLastBoundRect() ); SetRect(); SetChanged(); BroadcastObjectChange(); if (pUserCall!=nullptr) pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp); } void SwVirtFlyDrawObj::NbcSetSnapRect(const tools::Rectangle& ) { SetRect(); } const tools::Rectangle& SwVirtFlyDrawObj::GetLogicRect() const { SetRect(); return aOutRect; } void SwVirtFlyDrawObj::SetLogicRect(const tools::Rectangle& ) { tools::Rectangle aTmp( GetLastBoundRect() ); SetRect(); SetChanged(); BroadcastObjectChange(); if (pUserCall!=nullptr) pUserCall->Changed(*this, SdrUserCallType::Resize, aTmp); } void SwVirtFlyDrawObj::NbcSetLogicRect(const tools::Rectangle& ) { SetRect(); } ::basegfx::B2DPolyPolygon SwVirtFlyDrawObj::TakeXorPoly() const { const tools::Rectangle aSourceRectangle(GetFlyFrame()->getFrameArea().SVRect()); const ::basegfx::B2DRange aSourceRange = vcl::unotools::b2DRectangleFromRectangle(aSourceRectangle); ::basegfx::B2DPolyPolygon aRetval; aRetval.append(::basegfx::utils::createPolygonFromRect(aSourceRange)); return aRetval; } // SwVirtFlyDrawObj::Move() and Resize() void SwVirtFlyDrawObj::NbcMove(const Size& rSiz) { if(GetFlyFrame()->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()) { // RotateFlyFrame3: When we have a change and are in transformed state (e.g. rotation used), // we need to fall back to the un-transformed state to keep the old code below // working properly. Restore FrameArea and use aOutRect from old FrameArea. TransformableSwFrame* pTransformableSwFrame(static_cast(GetFlyFrame())->getTransformableSwFrame()); pTransformableSwFrame->restoreFrameAreas(); aOutRect = GetFlyFrame()->getFrameArea().SVRect(); } aOutRect.Move( rSiz ); const Point aOldPos( GetFlyFrame()->getFrameArea().Pos() ); const Point aNewPos( aOutRect.TopLeft() ); const SwRect aFlyRect( aOutRect ); //If the Fly has an automatic align (right or top), //so preserve the automatic. SwFrameFormat *pFormat = GetFlyFrame()->GetFormat(); const sal_Int16 eHori = pFormat->GetHoriOrient().GetHoriOrient(); const sal_Int16 eVert = pFormat->GetVertOrient().GetVertOrient(); const sal_Int16 eRelHori = pFormat->GetHoriOrient().GetRelationOrient(); const sal_Int16 eRelVert = pFormat->GetVertOrient().GetRelationOrient(); //On paragraph bound Flys starting from the new position a new //anchor must be set. Anchor and the new RelPos is calculated and //placed by the Fly itself. if( GetFlyFrame()->IsFlyAtContentFrame() ) { static_cast(GetFlyFrame())->SetAbsPos( aNewPos ); } else { const SwFrameFormat *pTmpFormat = GetFormat(); const SwFormatVertOrient &rVert = pTmpFormat->GetVertOrient(); const SwFormatHoriOrient &rHori = pTmpFormat->GetHoriOrient(); long lXDiff = aNewPos.X() - aOldPos.X(); if( rHori.IsPosToggle() && text::HoriOrientation::NONE == eHori && !GetFlyFrame()->FindPageFrame()->OnRightPage() ) lXDiff = -lXDiff; if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() && text::HoriOrientation::NONE == eHori ) lXDiff = -lXDiff; long lYDiff = aNewPos.Y() - aOldPos.Y(); if( GetFlyFrame()->GetAnchorFrame()->IsVertical() ) { //lXDiff -= rVert.GetPos(); //lYDiff += rHori.GetPos(); if ( GetFlyFrame()->GetAnchorFrame()->IsVertLR() ) { lXDiff += rVert.GetPos(); lXDiff = -lXDiff; } else { lXDiff -= rVert.GetPos(); lYDiff += rHori.GetPos(); } } else { lXDiff += rHori.GetPos(); lYDiff += rVert.GetPos(); } if( GetFlyFrame()->GetAnchorFrame()->IsRightToLeft() && text::HoriOrientation::NONE != eHori ) lXDiff = GetFlyFrame()->GetAnchorFrame()->getFrameArea().Width() - aFlyRect.Width() - lXDiff; const Point aTmp( lXDiff, lYDiff ); GetFlyFrame()->ChgRelPos( aTmp ); } SwAttrSet aSet( pFormat->GetDoc()->GetAttrPool(), RES_VERT_ORIENT, RES_HORI_ORIENT ); SwFormatHoriOrient aHori( pFormat->GetHoriOrient() ); SwFormatVertOrient aVert( pFormat->GetVertOrient() ); bool bPut = false; if( !GetFlyFrame()->IsFlyLayFrame() && ::GetHtmlMode(pFormat->GetDoc()->GetDocShell()) ) { //In HTML-Mode only automatic aligns are allowed. //Only we can try a snap to left/right respectively left-/right border const SwFrame* pAnch = GetFlyFrame()->GetAnchorFrame(); bool bNextLine = false; if( !GetFlyFrame()->IsAutoPos() || text::RelOrientation::PAGE_FRAME != aHori.GetRelationOrient() ) { if( text::RelOrientation::CHAR == eRelHori ) { aHori.SetHoriOrient( text::HoriOrientation::LEFT ); aHori.SetRelationOrient( text::RelOrientation::CHAR ); } else { bNextLine = true; //Horizontal Align: const bool bLeftFrame = aFlyRect.Left() < pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Left(), bLeftPrt = aFlyRect.Left() + aFlyRect.Width() < pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width()/2; if ( bLeftFrame || bLeftPrt ) { aHori.SetHoriOrient( text::HoriOrientation::LEFT ); aHori.SetRelationOrient( bLeftFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); } else { const bool bRightFrame = aFlyRect.Left() > pAnch->getFrameArea().Left() + pAnch->getFramePrintArea().Width(); aHori.SetHoriOrient( text::HoriOrientation::RIGHT ); aHori.SetRelationOrient( bRightFrame ? text::RelOrientation::FRAME : text::RelOrientation::PRINT_AREA ); } } aSet.Put( aHori ); } //Vertical alignment simply is retained principally, //only on manual align will be switched over. bool bRelChar = text::RelOrientation::CHAR == eRelVert; aVert.SetVertOrient( eVert != text::VertOrientation::NONE ? eVert : GetFlyFrame()->IsFlyInContentFrame() ? text::VertOrientation::CHAR_CENTER : bRelChar && bNextLine ? text::VertOrientation::CHAR_TOP : text::VertOrientation::TOP ); if( bRelChar ) aVert.SetRelationOrient( text::RelOrientation::CHAR ); else aVert.SetRelationOrient( text::RelOrientation::PRINT_AREA ); aSet.Put( aVert ); bPut = true; } //We want preferably not to lose the automatic alignments. if ( !bPut && bInResize ) { if ( text::HoriOrientation::NONE != eHori ) { aHori.SetHoriOrient( eHori ); aHori.SetRelationOrient( eRelHori ); aSet.Put( aHori ); bPut = true; } if ( text::VertOrientation::NONE != eVert ) { aVert.SetVertOrient( eVert ); aVert.SetRelationOrient( eRelVert ); aSet.Put( aVert ); bPut = true; } } if ( bPut ) pFormat->SetFormatAttr( aSet ); } void SwVirtFlyDrawObj::NbcCrop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) { // Get Wrt Shell SwWrtShell *pSh = dynamic_cast( GetFlyFrame()->getRootFrame()->GetCurrShell() ); if (!pSh) { return; } GraphicObject const *pGraphicObject = pSh->GetGraphicObj(); if (!pGraphicObject) { return; } // Get graphic object size in 100th of mm const MapMode aMapMode100thmm(MapUnit::Map100thMM); Size aGraphicSize(pGraphicObject->GetPrefSize()); if( MapUnit::MapPixel == pGraphicObject->GetPrefMapMode().GetMapUnit() ) { aGraphicSize = Application::GetDefaultDevice()->PixelToLogic( aGraphicSize, aMapMode100thmm ); } else { aGraphicSize = OutputDevice::LogicToLogic( aGraphicSize, pGraphicObject->GetPrefMapMode(), aMapMode100thmm); } if( aGraphicSize.IsEmpty() ) { return ; } const bool bIsTransformableSwFrame( GetFlyFrame()->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()); if(bIsTransformableSwFrame) { // When we have a change and are in transformed state (e.g. rotation used), // we need to fall back to the un-transformed state to keep the old code below // working properly. Restore FrameArea and use aOutRect from old FrameArea. TransformableSwFrame* pTransformableSwFrame(static_cast(GetFlyFrame())->getTransformableSwFrame()); pTransformableSwFrame->restoreFrameAreas(); aOutRect = GetFlyFrame()->getFrameArea().SVRect(); } // Compute old and new rect. This will give us the deformation to apply to // the object to crop. OldRect is the inner frame, see getFullDragClone() // below where getFramePrintAreaTransformation is used as object geometry for Crop const tools::Rectangle aOldRect( GetFlyFrame()->getFrameArea().TopLeft() + GetFlyFrame()->getFramePrintArea().TopLeft(), GetFlyFrame()->getFramePrintArea().SSize()); const long nOldWidth(aOldRect.GetWidth()); const long nOldHeight(aOldRect.GetHeight()); if (!nOldWidth || !nOldHeight) { return; } // rRef is relative to the Crop-Action, si in X/Y-Ranges of [0.0 .. 1.0], // to get the correct absolute position, transform using the old Rect const Point aRef( aOldRect.Left() + basegfx::fround(aOldRect.GetWidth() * rRef.getX()), aOldRect.Top() + basegfx::fround(aOldRect.GetHeight() * rRef.getY())); // apply transformation, use old ResizeRect for now tools::Rectangle aNewRect( aOldRect ); ResizeRect( aNewRect, aRef, Fraction(fxFact), Fraction(fyFact)); // Get old values for crop in 10th of mm SfxItemSet aSet( pSh->GetAttrPool(), svl::Items{} ); pSh->GetCurAttr( aSet ); SwCropGrf aCrop( aSet.Get(RES_GRFATR_CROPGRF) ); tools::Rectangle aCropRectangle( convertTwipToMm100(aCrop.GetLeft()), convertTwipToMm100(aCrop.GetTop()), convertTwipToMm100(aCrop.GetRight()), convertTwipToMm100(aCrop.GetBottom()) ); // Compute delta to apply double fScaleX = ( aGraphicSize.Width() - aCropRectangle.Left() - aCropRectangle.Right() ) / static_cast(nOldWidth); double fScaleY = ( aGraphicSize.Height() - aCropRectangle.Top() - aCropRectangle.Bottom() ) / static_cast(nOldHeight); sal_Int32 nDiffLeft = aNewRect.Left() - aOldRect.Left(); sal_Int32 nDiffTop = aNewRect.Top() - aOldRect.Top(); sal_Int32 nDiffRight = aNewRect.Right() - aOldRect.Right(); sal_Int32 nDiffBottom = aNewRect.Bottom() - aOldRect.Bottom(); // Compute new values in 10th of mm sal_Int32 nLeftCrop = static_cast( aCropRectangle.Left() + nDiffLeft * fScaleX ); sal_Int32 nTopCrop = static_cast( aCropRectangle.Top() + nDiffTop * fScaleY ); sal_Int32 nRightCrop = static_cast( aCropRectangle.Right() - nDiffRight * fScaleX ); sal_Int32 nBottomCrop = static_cast( aCropRectangle.Bottom() - nDiffBottom * fScaleY ); // Apply values pSh->StartAllAction(); // pSh->StartUndo(SwUndoId::START); // Set new crop values in twips aCrop.SetLeft (convertMm100ToTwip(nLeftCrop)); aCrop.SetTop (convertMm100ToTwip(nTopCrop)); aCrop.SetRight (convertMm100ToTwip(nRightCrop)); aCrop.SetBottom(convertMm100ToTwip(nBottomCrop)); pSh->SetAttrItem(aCrop); // Set new frame size SwFrameFormat *pFormat = GetFormat(); SwFormatFrameSize aSz( pFormat->GetFrameSize() ); const long aNewWidth(aNewRect.GetWidth() + (aOutRect.GetWidth() - aOldRect.GetWidth())); const long aNewHeight(aNewRect.GetHeight() + (aOutRect.GetHeight() - aOldRect.GetHeight())); aSz.SetWidth(aNewWidth); aSz.SetHeight(aNewHeight); pFormat->GetDoc()->SetAttr( aSz, *pFormat ); // add move - to make result look better. Fill with defaults // for the untransformed case Point aNewTopLeft(aNewRect.TopLeft()); const Point aOldTopLeft(aOldRect.TopLeft()); if(bIsTransformableSwFrame) { // Need to correct the NewTopLeft position in transformed state to make // the interaction look correct. First, extract rotation basegfx::B2DVector aScale, aTranslate; double fRotate, fShearX; GetFlyFrame()->getFrameAreaTransformation().decompose(aScale, aTranslate, fRotate, fShearX); // calc the center of the unchanged object const basegfx::B2DPoint aFormerCenter( GetFlyFrame()->getFrameAreaTransformation() * basegfx::B2DPoint(0.5, 0.5)); // define the existing rotation around that former center const basegfx::B2DHomMatrix aRotFormerCenter( basegfx::utils::createRotateAroundPoint( aFormerCenter.getX(), aFormerCenter.getY(), fRotate)); // use the new center of the unrotated object, rotate it around the // former center const Point aNewCenter(aNewRect.Center()); const basegfx::B2DPoint aRotNewCenter( aRotFormerCenter * basegfx::B2DPoint(aNewCenter.X(), aNewCenter.Y())); // Create the new TopLeft of the unrotated, cropped object by creating // as if re-creating the unrotated geometry aNewTopLeft = Point( basegfx::fround(aRotNewCenter.getX() - (0.5 * aNewRect.getWidth())), basegfx::fround(aRotNewCenter.getY() - (0.5 * aNewRect.getHeight()))); } // check if we have movement and execute if yes const Size aDeltaMove( aNewTopLeft.X() - aOldTopLeft.X(), aNewTopLeft.Y() - aOldTopLeft.Y()); if(0 != aDeltaMove.Width() || 0 != aDeltaMove.Height()) { NbcMove(aDeltaMove); } // pSh->EndUndo(SwUndoId::END); pSh->EndAllAction(); } void SwVirtFlyDrawObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) { const SwFrame* pTmpFrame = GetFlyFrame()->GetAnchorFrame(); if( !pTmpFrame ) { pTmpFrame = GetFlyFrame(); } const bool bVertX(pTmpFrame->IsVertical()); const bool bRTL(pTmpFrame->IsRightToLeft()); const bool bVertL2RX(pTmpFrame->IsVertLR()); const bool bUseRightEdge((bVertX && !bVertL2RX ) || bRTL); const bool bIsTransformableSwFrame( GetFlyFrame()->IsFlyFreeFrame() && static_cast< SwFlyFreeFrame* >(GetFlyFrame())->isTransformableSwFrame()); if(bIsTransformableSwFrame) { // When we have a change in transformed state, we need to fall back to the // state without possible transformations. // In the Resize case to correctly handle the changes, apply to the transformation // and extract the new, untransformed state from that modified transformation basegfx::B2DHomMatrix aNewMat(GetFlyFrame()->getFrameAreaTransformation()); const basegfx::B2DPoint aRef(rRef.X(), rRef.Y()); // apply state to already valid transformation aNewMat.translate(-aRef.getX(), -aRef.getY()); aNewMat.scale(double(xFact), double(yFact)); aNewMat.translate(aRef.getX(), aRef.getY()); // get center of transformed state const basegfx::B2DPoint aCenter(aNewMat * basegfx::B2DPoint(0.5, 0.5)); // decompose to extract scale basegfx::B2DVector aScale, aTranslate; double fRotate, fShearX; aNewMat.decompose(aScale, aTranslate, fRotate, fShearX); const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); // create new modified, but untransformed OutRect aOutRect = tools::Rectangle( basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())), basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())), basegfx::fround(aCenter.getX() + (0.5 * aAbsScale.getX())), basegfx::fround(aCenter.getY() + (0.5 * aAbsScale.getY()))); // restore FrameAreas so that actions below not adapted to new // full transformations take the correct actions TransformableSwFrame* pTransformableSwFrame(static_cast(GetFlyFrame())->getTransformableSwFrame()); pTransformableSwFrame->restoreFrameAreas(); } else { ResizeRect( aOutRect, rRef, xFact, yFact ); } // Position may also change, remember old one. This is now already // the one in the unrotated, old coordinate system Point aOldPos(bUseRightEdge ? GetFlyFrame()->getFrameArea().TopRight() : GetFlyFrame()->getFrameArea().Pos()); // get target size in old coordinate system Size aSz( aOutRect.Right() - aOutRect.Left() + 1, aOutRect.Bottom()- aOutRect.Top() + 1 ); // compare with restored FrameArea if( aSz != GetFlyFrame()->getFrameArea().SSize() ) { //The width of the columns should not be too narrow if ( GetFlyFrame()->Lower() && GetFlyFrame()->Lower()->IsColumnFrame() ) { SwBorderAttrAccess aAccess( SwFrame::GetCache(), GetFlyFrame() ); const SwBorderAttrs &rAttrs = *aAccess.Get(); long nMin = rAttrs.CalcLeftLine()+rAttrs.CalcRightLine(); const SwFormatCol& rCol = rAttrs.GetAttrSet().GetCol(); if ( rCol.GetColumns().size() > 1 ) { for ( const auto &rC : rCol.GetColumns() ) { nMin += rC.GetLeft() + rC.GetRight() + MINFLY; } nMin -= MINFLY; } aSz.setWidth( std::max( aSz.Width(), nMin ) ); } SwFrameFormat *pFormat = GetFormat(); const SwFormatFrameSize aOldFrameSz( pFormat->GetFrameSize() ); GetFlyFrame()->ChgSize( aSz ); SwFormatFrameSize aFrameSz( pFormat->GetFrameSize() ); if ( aFrameSz.GetWidthPercent() || aFrameSz.GetHeightPercent() ) { long nRelWidth, nRelHeight; const SwFrame *pRel = GetFlyFrame()->IsFlyLayFrame() ? GetFlyFrame()->GetAnchorFrame() : GetFlyFrame()->GetAnchorFrame()->GetUpper(); const SwViewShell *pSh = GetFlyFrame()->getRootFrame()->GetCurrShell(); if ( pSh && pRel->IsBodyFrame() && pSh->GetViewOptions()->getBrowseMode() && pSh->VisArea().HasArea() ) { nRelWidth = pSh->GetBrowseWidth(); nRelHeight = pSh->VisArea().Height(); const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); nRelHeight -= 2*aBorder.Height(); } else { nRelWidth = pRel->getFramePrintArea().Width(); nRelHeight = pRel->getFramePrintArea().Height(); } if ( aFrameSz.GetWidthPercent() && aFrameSz.GetWidthPercent() != SwFormatFrameSize::SYNCED && aOldFrameSz.GetWidth() != aFrameSz.GetWidth() ) { aFrameSz.SetWidthPercent( sal_uInt8(aSz.Width() * 100.0 / nRelWidth + 0.5) ); } if ( aFrameSz.GetHeightPercent() && aFrameSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && aOldFrameSz.GetHeight() != aFrameSz.GetHeight() ) { aFrameSz.SetHeightPercent( sal_uInt8(aSz.Height() * 100.0 / nRelHeight + 0.5) ); } pFormat->GetDoc()->SetAttr( aFrameSz, *pFormat ); } } //Position can also be changed, get new one const Point aNewPos(bUseRightEdge ? aOutRect.Right() + 1 : aOutRect.Left(), aOutRect.Top()); if ( aNewPos != aOldPos ) { // Former late change in aOutRect by ChgSize // is now taken into account directly by calculating // aNewPos *after* calling ChgSize (see old code). // Still need to adapt aOutRect since the 'Move' is already applied // here (see ResizeRect) and it's the same SdrObject const Size aDeltaMove( aNewPos.X() - aOldPos.X(), aNewPos.Y() - aOldPos.Y()); aOutRect.Move(-aDeltaMove.Width(), -aDeltaMove.Height()); // Now, move as needed (no empty delta which was a hack anyways) if(bIsTransformableSwFrame) { // need to save aOutRect to FrameArea, will be restored to aOutRect in // SwVirtFlyDrawObj::NbcMove currently for TransformableSwFrames SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*GetFlyFrame()); aFrm.setSwRect(aOutRect); } // keep old hack - not clear what happens here bInResize = true; NbcMove(aDeltaMove); bInResize = false; } } void SwVirtFlyDrawObj::Move(const Size& rSiz) { NbcMove( rSiz ); SetChanged(); GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); } void SwVirtFlyDrawObj::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact, bool /*bUnsetRelative*/) { NbcResize( rRef, xFact, yFact ); SetChanged(); GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); } void SwVirtFlyDrawObj::Crop(const basegfx::B2DPoint& rRef, double fxFact, double fyFact) { NbcCrop( rRef, fxFact, fyFact ); SetChanged(); GetFormat()->GetDoc()->GetIDocumentUndoRedo().DoDrawUndo(false); } // RotGrfFlyFrame: Helper to access possible rotation of Graphic contained in FlyFrame sal_uInt16 SwVirtFlyDrawObj::getPossibleRotationFromFraphicFrame(Size& rSize) const { sal_uInt16 nRetval(0); const SwNoTextFrame* pNoTx = dynamic_cast< const SwNoTextFrame* >(GetFlyFrame()->Lower()); if(pNoTx) { SwNoTextNode& rNoTNd = const_cast< SwNoTextNode& >(*static_cast(pNoTx->GetNode())); SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); if(nullptr != pGrfNd) { const SwAttrSet& rSet = pGrfNd->GetSwAttrSet(); const SwRotationGrf& rRotation = rSet.GetRotationGrf(); rSize = rRotation.GetUnrotatedSize(); nRetval = rRotation.GetValue(); } } return nRetval; } long SwVirtFlyDrawObj::GetRotateAngle() const { if(ContainsSwGrfNode()) { Size aSize; return getPossibleRotationFromFraphicFrame(aSize); } else { return SdrVirtObj::GetRotateAngle(); } } SdrObjectUniquePtr SwVirtFlyDrawObj::getFullDragClone() const { // call parent SdrObjectUniquePtr pRetval = SdrVirtObj::getFullDragClone(); if(pRetval && GetFlyFrame() && ContainsSwGrfNode()) { // RotGrfFlyFrame3: get inner bounds/transformation const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation()); pRetval->TRSetBaseGeometry(aTargetTransform, basegfx::B2DPolyPolygon()); } return pRetval; } void SwVirtFlyDrawObj::addCropHandles(SdrHdlList& rTarget) const { // RotGrfFlyFrame: Adapt to possible rotated Graphic contained in FlyFrame if(GetFlyFrame()->getFrameArea().HasArea()) { // Use InnerBound, OuterBound (same as GetFlyFrame()->getFrameArea().SVRect()) // may have a distance to InnerBound which needs to be taken into account. // The Graphic is mapped to InnerBound, as is the rotated Graphic. const basegfx::B2DRange aTargetRange(getInnerBound()); if(!aTargetRange.isEmpty()) { // RotGrfFlyFrame3: get inner bounds/transformation const basegfx::B2DHomMatrix aTargetTransform(GetFlyFrame()->getFramePrintAreaTransformation()); // break up matrix basegfx::B2DTuple aScale; basegfx::B2DTuple aTranslate; double fRotate(0.0); double fShearX(0.0); aTargetTransform.decompose(aScale, aTranslate, fRotate, fShearX); basegfx::B2DPoint aPos; aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperLeft, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(0.5, 0.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Upper, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::UpperRight, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(0.0, 0.5); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Left , fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(1.0, 0.5); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Right, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(0.0, 1.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerLeft, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(0.5, 1.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::Lower, fShearX, fRotate)); aPos = aTargetTransform * basegfx::B2DPoint(1.0, 1.0); rTarget.AddHdl(std::make_unique(Point(basegfx::fround(aPos.getX()), basegfx::fround(aPos.getY())), SdrHdlKind::LowerRight, fShearX, fRotate)); } } } // Macro PointerStyle SwVirtFlyDrawObj::GetMacroPointer( const SdrObjMacroHitRec& ) const { return PointerStyle::RefHand; } bool SwVirtFlyDrawObj::HasMacro() const { const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL(); return rURL.GetMap() || !rURL.GetURL().isEmpty(); } SdrObject* SwVirtFlyDrawObj::CheckMacroHit( const SdrObjMacroHitRec& rRec ) const { const SwFormatURL &rURL = m_pFlyFrame->GetFormat()->GetURL(); if( rURL.GetMap() || !rURL.GetURL().isEmpty() ) { SwRect aRect; if ( m_pFlyFrame->Lower() && m_pFlyFrame->Lower()->IsNoTextFrame() ) { aRect = m_pFlyFrame->getFramePrintArea(); aRect += m_pFlyFrame->getFrameArea().Pos(); } else aRect = m_pFlyFrame->getFrameArea(); if( aRect.IsInside( rRec.aPos ) ) { aRect.Pos().setX(aRect.Pos().getX() + rRec.nTol); aRect.Pos().setY(aRect.Pos().getY() + rRec.nTol); aRect.AddHeight( -(2 * rRec.nTol) ); aRect.AddWidth( -(2 * rRec.nTol) ); if( aRect.IsInside( rRec.aPos ) ) { if( !rURL.GetMap() || m_pFlyFrame->GetFormat()->GetIMapObject( rRec.aPos, m_pFlyFrame )) return const_cast(this); return nullptr; } } } return SdrObject::CheckMacroHit( rRec ); } bool SwVirtFlyDrawObj::IsTextBox() const { return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */