/* -*- 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 #include #include #include #include #include #include #include #include using namespace ::com::sun::star; size_t SwDoc::GetFlyCount( FlyCntType eType, bool bIgnoreTextBoxes ) const { const SwFrameFormats& rFormats = *GetSpzFrameFormats(); const size_t nSize = rFormats.size(); size_t nCount = 0; const SwNodeIndex* pIdx; for ( size_t i = 0; i < nSize; ++i) { const SwFrameFormat* pFlyFormat = rFormats[ i ]; if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) continue; if( RES_FLYFRMFMT != pFlyFormat->Which() ) continue; pIdx = pFlyFormat->GetContent().GetContentIdx(); if( pIdx && pIdx->GetNodes().IsDocNodes() ) { const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; switch( eType ) { case FLYCNTTYPE_FRM: if(!pNd->IsNoTextNode()) nCount++; break; case FLYCNTTYPE_GRF: if( pNd->IsGrfNode() ) nCount++; break; case FLYCNTTYPE_OLE: if(pNd->IsOLENode()) nCount++; break; default: nCount++; } } } return nCount; } /// @attention If you change this, also update SwXFrameEnumeration in unocoll. SwFrameFormat* SwDoc::GetFlyNum( size_t nIdx, FlyCntType eType, bool bIgnoreTextBoxes ) { SwFrameFormats& rFormats = *GetSpzFrameFormats(); SwFrameFormat* pRetFormat = nullptr; const size_t nSize = rFormats.size(); const SwNodeIndex* pIdx; size_t nCount = 0; for( size_t i = 0; !pRetFormat && i < nSize; ++i ) { SwFrameFormat* pFlyFormat = rFormats[ i ]; if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) continue; if( RES_FLYFRMFMT != pFlyFormat->Which() ) continue; pIdx = pFlyFormat->GetContent().GetContentIdx(); if( pIdx && pIdx->GetNodes().IsDocNodes() ) { const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; switch( eType ) { case FLYCNTTYPE_FRM: if( !pNd->IsNoTextNode() && nIdx == nCount++) pRetFormat = pFlyFormat; break; case FLYCNTTYPE_GRF: if(pNd->IsGrfNode() && nIdx == nCount++ ) pRetFormat = pFlyFormat; break; case FLYCNTTYPE_OLE: if(pNd->IsOLENode() && nIdx == nCount++) pRetFormat = pFlyFormat; break; default: if(nIdx == nCount++) pRetFormat = pFlyFormat; } } } return pRetFormat; } std::vector SwDoc::GetFlyFrameFormats( FlyCntType const eType, bool const bIgnoreTextBoxes) { SwFrameFormats& rFormats = *GetSpzFrameFormats(); const size_t nSize = rFormats.size(); std::vector ret; ret.reserve(nSize); for (size_t i = 0; i < nSize; ++i) { SwFrameFormat const*const pFlyFormat = rFormats[ i ]; if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) { continue; } if (RES_FLYFRMFMT != pFlyFormat->Which()) { continue; } SwNodeIndex const*const pIdx(pFlyFormat->GetContent().GetContentIdx()); if (pIdx && pIdx->GetNodes().IsDocNodes()) { SwNode const*const pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; switch (eType) { case FLYCNTTYPE_FRM: if (!pNd->IsNoTextNode()) ret.push_back(pFlyFormat); break; case FLYCNTTYPE_GRF: if (pNd->IsGrfNode()) ret.push_back(pFlyFormat); break; case FLYCNTTYPE_OLE: if (pNd->IsOLENode()) ret.push_back(pFlyFormat); break; default: ret.push_back(pFlyFormat); } } } return ret; } static Point lcl_FindAnchorLayPos( SwDoc& rDoc, const SwFormatAnchor& rAnch, const SwFrameFormat* pFlyFormat ) { Point aRet; if( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) switch( rAnch.GetAnchorId() ) { case RndStdIds::FLY_AS_CHAR: if( pFlyFormat && rAnch.GetContentAnchor() ) { const SwFrame* pOld = static_cast(pFlyFormat)->GetFrame( &aRet ); if( pOld ) aRet = pOld->getFrameArea().Pos(); } break; case RndStdIds::FLY_AT_PARA: case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL if( rAnch.GetContentAnchor() ) { const SwPosition *pPos = rAnch.GetContentAnchor(); const SwContentNode* pNd = pPos->nNode.GetNode().GetContentNode(); std::pair const tmp(aRet, false); const SwFrame* pOld = pNd ? pNd->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; if( pOld ) aRet = pOld->getFrameArea().Pos(); } break; case RndStdIds::FLY_AT_FLY: // LAYER_IMPL if( rAnch.GetContentAnchor() ) { const SwFlyFrameFormat* pFormat = static_cast(rAnch.GetContentAnchor()-> nNode.GetNode().GetFlyFormat()); const SwFrame* pOld = pFormat ? pFormat->GetFrame( &aRet ) : nullptr; if( pOld ) aRet = pOld->getFrameArea().Pos(); } break; case RndStdIds::FLY_AT_PAGE: { sal_uInt16 nPgNum = rAnch.GetPageNum(); const SwPageFrame *pPage = static_cast(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower()); for( sal_uInt16 i = 1; (i <= nPgNum) && pPage; ++i, pPage =static_cast(pPage->GetNext()) ) if( i == nPgNum ) { aRet = pPage->getFrameArea().Pos(); break; } } break; default: break; } return aRet; } #define MAKEFRMS 0 #define IGNOREANCHOR 1 #define DONTMAKEFRMS 2 sal_Int8 SwDoc::SetFlyFrameAnchor( SwFrameFormat& rFormat, SfxItemSet& rSet, bool bNewFrames ) { // Changing anchors is almost always allowed. // Exception: Paragraph and character bound frames must not become // page bound, if they are located in the header or footer. const SwFormatAnchor &rOldAnch = rFormat.GetAnchor(); const RndStdIds nOld = rOldAnch.GetAnchorId(); SwFormatAnchor aNewAnch( rSet.Get( RES_ANCHOR ) ); RndStdIds nNew = aNewAnch.GetAnchorId(); // Is the new anchor valid? if( !aNewAnch.GetContentAnchor() && (RndStdIds::FLY_AT_FLY == nNew || (RndStdIds::FLY_AT_PARA == nNew) || (RndStdIds::FLY_AS_CHAR == nNew) || (RndStdIds::FLY_AT_CHAR == nNew) )) { return IGNOREANCHOR; } if( nOld == nNew ) return DONTMAKEFRMS; Point aOldAnchorPos( ::lcl_FindAnchorLayPos( *this, rOldAnch, &rFormat )); Point aNewAnchorPos( ::lcl_FindAnchorLayPos( *this, aNewAnch, nullptr )); // Destroy the old Frames. // The Views are hidden implicitly, so hiding them another time would be // kind of a show! rFormat.DelFrames(); if ( RndStdIds::FLY_AS_CHAR == nOld ) { // We need to handle InContents in a special way: // The TextAttribut needs to be destroyed which, unfortunately, also // destroys the format. To avoid that, we disconnect the format from // the attribute. const SwPosition *pPos = rOldAnch.GetContentAnchor(); SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); const sal_Int32 nIdx = pPos->nContent.GetIndex(); SwTextAttr * const pHint = pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); OSL_ENSURE( pHint && pHint->Which() == RES_TXTATR_FLYCNT, "Missing FlyInCnt-Hint." ); OSL_ENSURE( pHint && pHint->GetFlyCnt().GetFrameFormat() == &rFormat, "Wrong TextFlyCnt-Hint." ); if (pHint) const_cast(pHint->GetFlyCnt()).SetFlyFormat(); // They are disconnected. We now have to destroy the attribute. pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); } // We can finally set the attribute. It needs to be the first one! // Undo depends on it! rFormat.SetFormatAttr( aNewAnch ); // Correct the position const SfxPoolItem* pItem; switch( nNew ) { case RndStdIds::FLY_AS_CHAR: // If no position attributes are received, we have to make sure // that no forbidden automatic alignment is left. { const SwPosition *pPos = aNewAnch.GetContentAnchor(); SwTextNode *pNd = pPos->nNode.GetNode().GetTextNode(); OSL_ENSURE( pNd, "Cursor does not point to TextNode." ); SwFormatFlyCnt aFormat( static_cast(&rFormat) ); pNd->InsertItem( aFormat, pPos->nContent.GetIndex(), 0 ); } if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) { SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); bool bSet = true; switch( aOldV.GetVertOrient() ) { case text::VertOrientation::LINE_TOP: aOldV.SetVertOrient( text::VertOrientation::TOP ); break; case text::VertOrientation::LINE_CENTER: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; case text::VertOrientation::LINE_BOTTOM: aOldV.SetVertOrient( text::VertOrientation::BOTTOM); break; case text::VertOrientation::NONE: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; default: bSet = false; } if( bSet ) rSet.Put( aOldV ); } break; case RndStdIds::FLY_AT_PARA: case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL case RndStdIds::FLY_AT_FLY: // LAYER_IMPL case RndStdIds::FLY_AT_PAGE: { // If no position attributes are coming in, we correct the position in a way // such that the fly's document coordinates are preserved. // If only the alignment changes in the position attributes (text::RelOrientation::FRAME // vs. text::RelOrientation::PRTAREA), we also correct the position. if( SfxItemState::SET != rSet.GetItemState( RES_HORI_ORIENT, false, &pItem )) pItem = nullptr; SwFormatHoriOrient aOldH( rFormat.GetHoriOrient() ); bool bPutOldH(false); if( text::HoriOrientation::NONE == aOldH.GetHoriOrient() && ( !pItem || aOldH.GetPos() == static_cast(pItem)->GetPos() )) { SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldH.GetPos(); nPos += aOldAnchorPos.getX() - aNewAnchorPos.getX(); if( pItem ) { SwFormatHoriOrient* pH = const_cast(static_cast(pItem)); aOldH.SetHoriOrient( pH->GetHoriOrient() ); aOldH.SetRelationOrient( pH->GetRelationOrient() ); } aOldH.SetPos( nPos ); bPutOldH = true; } if (nNew == RndStdIds::FLY_AT_PAGE) { sal_Int16 nRelOrient(pItem ? static_cast(pItem)->GetRelationOrient() : aOldH.GetRelationOrient()); if (sw::GetAtPageRelOrientation(nRelOrient, false)) { SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); aOldH.SetRelationOrient(nRelOrient); bPutOldH = true; } } if (bPutOldH) { rSet.Put( aOldH ); } if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) pItem = nullptr; SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); // #i28922# - correction: compare if( text::VertOrientation::NONE == aOldV.GetVertOrient() && (!pItem || aOldV.GetPos() == static_cast(pItem)->GetPos() ) ) { SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldV.GetPos(); nPos += aOldAnchorPos.getY() - aNewAnchorPos.getY(); if( pItem ) { SwFormatVertOrient* pV = const_cast(static_cast(pItem)); aOldV.SetVertOrient( pV->GetVertOrient() ); aOldV.SetRelationOrient( pV->GetRelationOrient() ); } aOldV.SetPos( nPos ); rSet.Put( aOldV ); } } break; default: break; } if( bNewFrames ) rFormat.MakeFrames(); return MAKEFRMS; } static bool lcl_SetFlyFrameAttr(SwDoc & rDoc, sal_Int8 (SwDoc::*pSetFlyFrameAnchor)(SwFrameFormat &, SfxItemSet &, bool), SwFrameFormat & rFlyFormat, SfxItemSet & rSet) { // #i32968# Inserting columns in the frame causes MakeFrameFormat to put two // objects of type SwUndoFrameFormat on the undo stack. We don't want them. ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); // Is the anchor attribute included? // If so, we pass it to a special method, which returns true // if the Fly needs to be created anew, because we e.g change the FlyType. sal_Int8 const nMakeFrames = (SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false )) ? (rDoc.*pSetFlyFrameAnchor)( rFlyFormat, rSet, false ) : DONTMAKEFRMS; const SfxPoolItem* pItem; SfxItemIter aIter( rSet ); SfxItemSet aTmpSet( rDoc.GetAttrPool(), aFrameFormatSetRange ); const SfxPoolItem* pItemIter = aIter.GetCurItem(); do { switch(pItemIter->Which()) { case RES_FILL_ORDER: case RES_BREAK: case RES_PAGEDESC: case RES_CNTNT: case RES_FOOTER: OSL_FAIL( "Unknown Fly attribute." ); [[fallthrough]]; case RES_CHAIN: rSet.ClearItem(pItemIter->Which()); break; case RES_ANCHOR: if( DONTMAKEFRMS != nMakeFrames ) break; [[fallthrough]]; default: if( !IsInvalidItem(pItemIter) && ( SfxItemState::SET != rFlyFormat.GetAttrSet().GetItemState(pItemIter->Which(), true, &pItem ) || *pItem != *pItemIter)) aTmpSet.Put(*pItemIter); break; } pItemIter = aIter.NextItem(); } while (pItemIter && (0 != pItemIter->Which())); if( aTmpSet.Count() ) rFlyFormat.SetFormatAttr( aTmpSet ); if( MAKEFRMS == nMakeFrames ) rFlyFormat.MakeFrames(); return aTmpSet.Count() || MAKEFRMS == nMakeFrames; } void SwDoc::CheckForUniqueItemForLineFillNameOrIndex(SfxItemSet& rSet) { SwDrawModel* pDrawModel = getIDocumentDrawModelAccess().GetOrCreateDrawModel(); SfxItemIter aIter(rSet); for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) { if (IsInvalidItem(pItem)) continue; std::unique_ptr pResult; switch(pItem->Which()) { case XATTR_FILLBITMAP: { pResult = static_cast< const XFillBitmapItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_LINEDASH: { pResult = static_cast< const XLineDashItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_LINESTART: { pResult = static_cast< const XLineStartItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_LINEEND: { pResult = static_cast< const XLineEndItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_FILLGRADIENT: { pResult = static_cast< const XFillGradientItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_FILLFLOATTRANSPARENCE: { pResult = static_cast< const XFillFloatTransparenceItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } case XATTR_FILLHATCH: { pResult = static_cast< const XFillHatchItem* >(pItem)->checkForUniqueItem(pDrawModel); break; } } if(pResult) { rSet.Put(*pResult); } } } bool SwDoc::SetFlyFrameAttr( SwFrameFormat& rFlyFormat, SfxItemSet& rSet ) { if( !rSet.Count() ) return false; std::unique_ptr pSaveUndo; if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it pSaveUndo.reset( new SwUndoFormatAttrHelper( rFlyFormat ) ); } bool const bRet = lcl_SetFlyFrameAttr(*this, &SwDoc::SetFlyFrameAnchor, rFlyFormat, rSet); if (pSaveUndo && pSaveUndo->GetUndo() ) { GetIDocumentUndoRedo().AppendUndo( pSaveUndo->ReleaseUndo() ); } getIDocumentState().SetModified(); SwTextBoxHelper::syncFlyFrameAttr(rFlyFormat, rSet); return bRet; } // #i73249# void SwDoc::SetFlyFrameTitle( SwFlyFrameFormat& rFlyFrameFormat, const OUString& sNewTitle ) { if ( rFlyFrameFormat.GetObjTitle() == sNewTitle ) { return; } ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo( std::make_unique( rFlyFrameFormat, SwUndoId::FLYFRMFMT_TITLE, rFlyFrameFormat.GetObjTitle(), sNewTitle ) ); } rFlyFrameFormat.SetObjTitle( sNewTitle, true ); getIDocumentState().SetModified(); } void SwDoc::SetFlyFrameDescription( SwFlyFrameFormat& rFlyFrameFormat, const OUString& sNewDescription ) { if ( rFlyFrameFormat.GetObjDescription() == sNewDescription ) { return; } ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); if (GetIDocumentUndoRedo().DoesUndo()) { GetIDocumentUndoRedo().AppendUndo( std::make_unique( rFlyFrameFormat, SwUndoId::FLYFRMFMT_DESCRIPTION, rFlyFrameFormat.GetObjDescription(), sNewDescription ) ); } rFlyFrameFormat.SetObjDescription( sNewDescription, true ); getIDocumentState().SetModified(); } bool SwDoc::SetFrameFormatToFly( SwFrameFormat& rFormat, SwFrameFormat& rNewFormat, SfxItemSet* pSet, bool bKeepOrient ) { bool bChgAnchor = false, bFrameSz = false; const SwFormatFrameSize aFrameSz( rFormat.GetFrameSize() ); SwUndoSetFlyFormat* pUndo = nullptr; bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); if (bUndo) { pUndo = new SwUndoSetFlyFormat( rFormat, rNewFormat ); GetIDocumentUndoRedo().AppendUndo(std::unique_ptr(pUndo)); } // #i32968# Inserting columns in the section causes MakeFrameFormat to put // 2 objects of type SwUndoFrameFormat on the undo stack. We don't want them. ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); // Set the column first, or we'll have trouble with //Set/Reset/Synch. and so on const SfxPoolItem* pItem; if( SfxItemState::SET != rNewFormat.GetAttrSet().GetItemState( RES_COL )) rFormat.ResetFormatAttr( RES_COL ); if( rFormat.DerivedFrom() != &rNewFormat ) { rFormat.SetDerivedFrom( &rNewFormat ); // 1. If not automatic = ignore; else = dispose // 2. Dispose of it! if( SfxItemState::SET == rNewFormat.GetAttrSet().GetItemState( RES_FRM_SIZE, false )) { rFormat.ResetFormatAttr( RES_FRM_SIZE ); bFrameSz = true; } const SfxItemSet* pAsk = pSet; if( !pAsk ) pAsk = &rNewFormat.GetAttrSet(); if( SfxItemState::SET == pAsk->GetItemState( RES_ANCHOR, false, &pItem ) && static_cast(pItem)->GetAnchorId() != rFormat.GetAnchor().GetAnchorId() ) { if( pSet ) bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, *pSet, false ); else { // Needs to have the FlyFormat range, because we set attributes in it, // in SetFlyFrameAnchor. SfxItemSet aFlySet( *rNewFormat.GetAttrSet().GetPool(), rNewFormat.GetAttrSet().GetRanges() ); aFlySet.Put( *pItem ); bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, aFlySet, false); } } } // Only reset vertical and horizontal orientation, if we have automatic alignment // set in the template. Otherwise use the old value. // If we update the frame template the Fly should NOT lose its orientation (which // is not being updated!). // text::HoriOrientation::NONE and text::VertOrientation::NONE are allowed now if (!bKeepOrient) { rFormat.ResetFormatAttr(RES_VERT_ORIENT); rFormat.ResetFormatAttr(RES_HORI_ORIENT); } rFormat.ResetFormatAttr( RES_PRINT, RES_SURROUND ); rFormat.ResetFormatAttr( RES_LR_SPACE, RES_UL_SPACE ); rFormat.ResetFormatAttr( RES_BACKGROUND, RES_COL ); rFormat.ResetFormatAttr( RES_URL, RES_EDIT_IN_READONLY ); if( !bFrameSz ) rFormat.SetFormatAttr( aFrameSz ); if( bChgAnchor ) rFormat.MakeFrames(); if( pUndo ) pUndo->EndListeningAll(); getIDocumentState().SetModified(); return bChgAnchor; } void SwDoc::GetGrfNms( const SwFlyFrameFormat& rFormat, OUString* pGrfName, OUString* pFltName ) { SwNodeIndex aIdx( *rFormat.GetContent().GetContentIdx(), 1 ); const SwGrfNode* pGrfNd = aIdx.GetNode().GetGrfNode(); if( pGrfNd && pGrfNd->IsLinkedFile() ) pGrfNd->GetFileFilterNms( pGrfName, pFltName ); } bool SwDoc::ChgAnchor( const SdrMarkList& _rMrkList, RndStdIds _eAnchorType, const bool _bSameOnly, const bool _bPosCorr ) { OSL_ENSURE( getIDocumentLayoutAccess().GetCurrentLayout(), "No layout!" ); if ( !_rMrkList.GetMarkCount() || _rMrkList.GetMark( 0 )->GetMarkedSdrObj()->getParentSdrObjectFromSdrObject() ) { return false; } GetIDocumentUndoRedo().StartUndo( SwUndoId::INSATTR, nullptr ); bool bUnmark = false; for ( size_t i = 0; i < _rMrkList.GetMarkCount(); ++i ) { SdrObject* pObj = _rMrkList.GetMark( i )->GetMarkedSdrObj(); if ( dynamic_cast( pObj) == nullptr ) { SwDrawContact* pContact = static_cast(GetUserCall(pObj)); // consider, that drawing object has // no user call. E.g.: a 'virtual' drawing object is disconnected by // the anchor type change of the 'master' drawing object. // Continue with next selected object and assert, if this isn't excepted. if ( !pContact ) { #if OSL_DEBUG_LEVEL > 0 bool bNoUserCallExcepted = dynamic_cast( pObj) != nullptr && !static_cast(pObj)->IsConnected(); OSL_ENSURE( bNoUserCallExcepted, "SwDoc::ChgAnchor(..) - no contact at selected drawing object" ); #endif continue; } // #i26791# const SwFrame* pOldAnchorFrame = pContact->GetAnchorFrame( pObj ); const SwFrame* pNewAnchorFrame = pOldAnchorFrame; // #i54336# // Instead of only keeping the index position for an as-character // anchored object the complete is kept, because the // anchor index position could be moved, if the object again is // anchored as character. std::unique_ptr xOldAsCharAnchorPos; const RndStdIds eOldAnchorType = pContact->GetAnchorId(); if ( !_bSameOnly && eOldAnchorType == RndStdIds::FLY_AS_CHAR ) { xOldAsCharAnchorPos.reset(new SwPosition(pContact->GetContentAnchor())); } if ( _bSameOnly ) _eAnchorType = eOldAnchorType; SwFormatAnchor aNewAnch( _eAnchorType ); SwAnchoredObject *pAnchoredObj = pContact->GetAnchoredObj(pObj); tools::Rectangle aObjRect(pAnchoredObj->GetObjRect().SVRect()); const Point aPt( aObjRect.TopLeft() ); switch ( _eAnchorType ) { case RndStdIds::FLY_AT_PARA: case RndStdIds::FLY_AT_CHAR: { const Point aNewPoint = ( pOldAnchorFrame->IsVertical() || pOldAnchorFrame->IsRightToLeft() ) ? aObjRect.TopRight() : aPt; // allow drawing objects in header/footer pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aNewPoint ); if ( pNewAnchorFrame->IsTextFrame() && static_cast(pNewAnchorFrame)->IsFollow() ) { pNewAnchorFrame = static_cast(pNewAnchorFrame)->FindMaster(); } if ( pNewAnchorFrame->IsProtected() ) { pNewAnchorFrame = nullptr; } else { SwPosition aPos( pNewAnchorFrame->IsTextFrame() ? *static_cast(pNewAnchorFrame)->GetTextNodeForParaProps() : *static_cast(pNewAnchorFrame)->GetNode() ); aNewAnch.SetType( _eAnchorType ); aNewAnch.SetAnchor( &aPos ); } } break; case RndStdIds::FLY_AT_FLY: // LAYER_IMPL { // Search the closest SwFlyFrame starting from the upper left corner. SwFrame *pTextFrame; { SwCursorMoveState aState( CursorMoveState::SetOnlyText ); SwPosition aPos( GetNodes() ); Point aPoint( aPt ); aPoint.setX(aPoint.getX() - 1); getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); // consider that drawing objects can be in // header/footer. Thus, by left-top-corner std::pair const tmp(aPt, false); pTextFrame = aPos.nNode.GetNode(). GetContentNode()->getLayoutFrame( getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); } const SwFrame *pTmp = ::FindAnchor( pTextFrame, aPt ); pNewAnchorFrame = pTmp->FindFlyFrame(); if( pNewAnchorFrame && !pNewAnchorFrame->IsProtected() ) { const SwFrameFormat *pTmpFormat = static_cast(pNewAnchorFrame)->GetFormat(); const SwFormatContent& rContent = pTmpFormat->GetContent(); SwPosition aPos( *rContent.GetContentIdx() ); aNewAnch.SetAnchor( &aPos ); break; } aNewAnch.SetType( RndStdIds::FLY_AT_PAGE ); [[fallthrough]]; } case RndStdIds::FLY_AT_PAGE: { pNewAnchorFrame = getIDocumentLayoutAccess().GetCurrentLayout()->Lower(); while ( pNewAnchorFrame && !pNewAnchorFrame->getFrameArea().IsInside( aPt ) ) pNewAnchorFrame = pNewAnchorFrame->GetNext(); if ( !pNewAnchorFrame ) continue; aNewAnch.SetPageNum( static_cast(pNewAnchorFrame)->GetPhyPageNum()); } break; case RndStdIds::FLY_AS_CHAR: if( _bSameOnly ) // Change of position/size { if( !pOldAnchorFrame ) { pContact->ConnectToLayout(); pOldAnchorFrame = pContact->GetAnchorFrame(); } const_cast(static_cast(pOldAnchorFrame))->Prepare(); } else // Change of anchors { // allow drawing objects in header/footer pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aPt ); if( pNewAnchorFrame->IsProtected() ) { pNewAnchorFrame = nullptr; break; } bUnmark = ( 0 != i ); Point aPoint( aPt ); aPoint.setX(aPoint.getX() - 1); // Do not load in the DrawObj! aNewAnch.SetType( RndStdIds::FLY_AS_CHAR ); assert(pNewAnchorFrame->IsTextFrame()); // because AS_CHAR SwTextFrame const*const pFrame( static_cast(pNewAnchorFrame)); SwPosition aPos( *pFrame->GetTextNodeForParaProps() ); if ( pNewAnchorFrame->getFrameArea().IsInside( aPoint ) ) { // We need to find a TextNode, because only there we can anchor a // content-bound DrawObject. SwCursorMoveState aState( CursorMoveState::SetOnlyText ); getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); } else { if ( pNewAnchorFrame->getFrameArea().Bottom() < aPt.Y() ) { aPos = pFrame->MapViewToModelPos(TextFrameIndex(0)); } else { aPos = pFrame->MapViewToModelPos( TextFrameIndex(pFrame->GetText().getLength())); } } aNewAnch.SetAnchor( &aPos ); SetAttr( aNewAnch, *pContact->GetFormat() ); // #i26791# - adjust vertical positioning to 'center to // baseline' SetAttr( SwFormatVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME ), *pContact->GetFormat() ); SwTextNode *pNd = aPos.nNode.GetNode().GetTextNode(); OSL_ENSURE( pNd, "Cursor not positioned at TextNode." ); SwFormatFlyCnt aFormat( pContact->GetFormat() ); pNd->InsertItem( aFormat, aPos.nContent.GetIndex(), 0 ); } break; default: OSL_ENSURE( false, "unexpected AnchorId." ); } if ( (RndStdIds::FLY_AS_CHAR != _eAnchorType) && pNewAnchorFrame && ( !_bSameOnly || pNewAnchorFrame != pOldAnchorFrame ) ) { // #i26791# - Direct object positioning no longer needed. Apply // of attributes (method call ) takes care of the // invalidation of the object position. if ( _bPosCorr ) { // #i33313# - consider not connected 'virtual' drawing // objects if ( dynamic_cast( pObj) != nullptr && !static_cast(pObj)->IsConnected() ) { SwRect aNewObjRect( aObjRect ); static_cast(pContact->GetAnchoredObj( nullptr )) ->AdjustPositioningAttr( pNewAnchorFrame, &aNewObjRect ); } else { static_cast(pContact->GetAnchoredObj( pObj )) ->AdjustPositioningAttr( pNewAnchorFrame ); } } if (aNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) { SwFormatHoriOrient item(pContact->GetFormat()->GetHoriOrient()); sal_Int16 nRelOrient(item.GetRelationOrient()); if (sw::GetAtPageRelOrientation(nRelOrient, false)) { SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); item.SetRelationOrient(nRelOrient); SetAttr(item, *pContact->GetFormat()); } } // tdf#136385 set the anchor last - otherwise it messes up the // position in SwDrawContact::Changed_() callback SetAttr(aNewAnch, *pContact->GetFormat()); } // we have changed the anchoring attributes, and those are used to // order the object in its sorted list, so update its position pAnchoredObj->UpdateObjInSortedList(); // #i54336# if (xOldAsCharAnchorPos) { if ( pNewAnchorFrame) { // We need to handle InContents in a special way: // The TextAttribut needs to be destroyed which, unfortunately, also // destroys the format. To avoid that, we disconnect the format from // the attribute. const sal_Int32 nIndx( xOldAsCharAnchorPos->nContent.GetIndex() ); SwTextNode* pTextNode( xOldAsCharAnchorPos->nNode.GetNode().GetTextNode() ); assert(pTextNode && " - missing previous anchor text node for as-character anchored object"); SwTextAttr * const pHint = pTextNode->GetTextAttrForCharAt( nIndx, RES_TXTATR_FLYCNT ); assert(pHint && "Missing FlyInCnt-Hint."); const_cast(pHint->GetFlyCnt()).SetFlyFormat(); // They are disconnected. We now have to destroy the attribute. pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIndx, nIndx ); } } } } GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); getIDocumentState().SetModified(); return bUnmark; } SwChainRet SwDoc::Chainable( const SwFrameFormat &rSource, const SwFrameFormat &rDest ) { // The Source must not yet have a Follow. const SwFormatChain &rOldChain = rSource.GetChain(); if ( rOldChain.GetNext() ) return SwChainRet::SOURCE_CHAINED; // Target must not be equal to Source and we also must not have a closed chain. const SwFrameFormat *pFormat = &rDest; do { if( pFormat == &rSource ) return SwChainRet::SELF; pFormat = pFormat->GetChain().GetNext(); } while ( pFormat ); // There must not be a chaining from outside to inside or the other way around. if( rDest.IsLowerOf( rSource ) || rSource .IsLowerOf( rDest ) ) return SwChainRet::SELF; // The Target must not yet have a Master. const SwFormatChain &rChain = rDest.GetChain(); if( rChain.GetPrev() ) return SwChainRet::IS_IN_CHAIN; // Target must be empty. const SwNodeIndex* pCntIdx = rDest.GetContent().GetContentIdx(); if( !pCntIdx ) return SwChainRet::NOT_FOUND; SwNodeIndex aNxtIdx( *pCntIdx, 1 ); const SwTextNode* pTextNd = aNxtIdx.GetNode().GetTextNode(); if( !pTextNd ) return SwChainRet::NOT_FOUND; const sal_uLong nFlySttNd = pCntIdx->GetIndex(); if( 2 != ( pCntIdx->GetNode().EndOfSectionIndex() - nFlySttNd ) || pTextNd->GetText().getLength() ) { return SwChainRet::NOT_EMPTY; } for( auto pSpzFrameFm : *GetSpzFrameFormats() ) { const SwFormatAnchor& rAnchor = pSpzFrameFm->GetAnchor(); // #i20622# - to-frame anchored objects are allowed. if ( (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PARA) && (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_CHAR) ) continue; if ( nullptr == rAnchor.GetContentAnchor() ) continue; sal_uLong nTstSttNd = rAnchor.GetContentAnchor()->nNode.GetIndex(); if( nFlySttNd <= nTstSttNd && nTstSttNd < nFlySttNd + 2 ) { return SwChainRet::NOT_EMPTY; } } // We also need to consider the right area. // Both Flys need to be located in the same area (Body, Header/Footer, Fly). // If the Source is not the selected frame, it's enough to find a suitable // one. e.g. if it's requested by the API. // both in the same fly, header, footer or on the page? const SwFormatAnchor &rSrcAnchor = rSource.GetAnchor(), &rDstAnchor = rDest.GetAnchor(); sal_uLong nEndOfExtras = GetNodes().GetEndOfExtras().GetIndex(); bool bAllowed = false; if ( RndStdIds::FLY_AT_PAGE == rSrcAnchor.GetAnchorId() ) { if ( (RndStdIds::FLY_AT_PAGE == rDstAnchor.GetAnchorId()) || ( rDstAnchor.GetContentAnchor() && rDstAnchor.GetContentAnchor()->nNode.GetIndex() > nEndOfExtras )) bAllowed = true; } else if( rSrcAnchor.GetContentAnchor() && rDstAnchor.GetContentAnchor() ) { const SwNodeIndex &rSrcIdx = rSrcAnchor.GetContentAnchor()->nNode, &rDstIdx = rDstAnchor.GetContentAnchor()->nNode; const SwStartNode* pSttNd = nullptr; if( rSrcIdx == rDstIdx || ( !pSttNd && nullptr != ( pSttNd = rSrcIdx.GetNode().FindFlyStartNode() ) && pSttNd == rDstIdx.GetNode().FindFlyStartNode() ) || ( !pSttNd && nullptr != ( pSttNd = rSrcIdx.GetNode().FindFooterStartNode() ) && pSttNd == rDstIdx.GetNode().FindFooterStartNode() ) || ( !pSttNd && nullptr != ( pSttNd = rSrcIdx.GetNode().FindHeaderStartNode() ) && pSttNd == rDstIdx.GetNode().FindHeaderStartNode() ) || ( !pSttNd && rDstIdx.GetIndex() > nEndOfExtras && rSrcIdx.GetIndex() > nEndOfExtras )) bAllowed = true; } return bAllowed ? SwChainRet::OK : SwChainRet::WRONG_AREA; } SwChainRet SwDoc::Chain( SwFrameFormat &rSource, const SwFrameFormat &rDest ) { SwChainRet nErr = Chainable( rSource, rDest ); if ( nErr == SwChainRet::OK ) { GetIDocumentUndoRedo().StartUndo( SwUndoId::CHAINE, nullptr ); SwFlyFrameFormat& rDestFormat = const_cast(static_cast(rDest)); // Attach Follow to the Master. SwFormatChain aChain = rDestFormat.GetChain(); aChain.SetPrev( &static_cast(rSource) ); SetAttr( aChain, rDestFormat ); SfxItemSet aSet( GetAttrPool(), svl::Items{} ); // Attach Follow to the Master. aChain.SetPrev( &static_cast(rSource) ); SetAttr( aChain, rDestFormat ); // Attach Master to the Follow. // Make sure that the Master has a fixed height. aChain = rSource.GetChain(); aChain.SetNext( &rDestFormat ); aSet.Put( aChain ); SwFormatFrameSize aSize( rSource.GetFrameSize() ); if ( aSize.GetHeightSizeType() != SwFrameSize::Fixed ) { SwFlyFrame *pFly = SwIterator( rSource ).First(); if ( pFly ) aSize.SetHeight( pFly->getFrameArea().Height() ); aSize.SetHeightSizeType( SwFrameSize::Fixed ); aSet.Put( aSize ); } SetAttr( aSet, rSource ); GetIDocumentUndoRedo().EndUndo( SwUndoId::CHAINE, nullptr ); } return nErr; } void SwDoc::Unchain( SwFrameFormat &rFormat ) { SwFormatChain aChain( rFormat.GetChain() ); if ( aChain.GetNext() ) { GetIDocumentUndoRedo().StartUndo( SwUndoId::UNCHAIN, nullptr ); SwFrameFormat *pFollow = aChain.GetNext(); aChain.SetNext( nullptr ); SetAttr( aChain, rFormat ); aChain = pFollow->GetChain(); aChain.SetPrev( nullptr ); SetAttr( aChain, *pFollow ); GetIDocumentUndoRedo().EndUndo( SwUndoId::UNCHAIN, nullptr ); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */