// Time over already? inlinevoid SwLayAction::CheckIdleEnd()
{ if (!IsInterrupt())
m_bInterrupt = bool(GetInputType()) && Application::AnyInput(GetInputType());
if (comphelper::LibreOfficeKit::isActive() && !IsInterrupt() && bool(GetInputType()))
{ // Also check if the LOK client has any pending input events.
m_bInterrupt = comphelper::LibreOfficeKit::anyInput();
}
}
if ( pSelfFly && pSelfFly->IsLowerOf( pFly ) ) continue;
if ( pFly->GetVirtDrawObj()->GetLayer() == rIDDMA.GetHellId() ) continue;
if ( pSelfFly )
{ const SdrObject *pTmp = pSelfFly->GetVirtDrawObj(); if ( pVirtFly->GetLayer() == pTmp->GetLayer() )
{ if ( pVirtFly->GetOrdNumDirect() < pTmp->GetOrdNumDirect() ) // Only look at things above us, if inside the same layer continue;
} else
{ constbool bLowerOfSelf = pFly->IsLowerOf( pSelfFly ); if ( !bLowerOfSelf && !pFly->GetFormat()->GetOpaque().GetValue() ) // Things from other layers are only interesting to us if // they're not transparent or lie inwards continue;
}
}
// Fly frame without a lower have to be subtracted from paint region. // For checking, if fly frame contains transparent graphic or // has surrounded contour, assure that fly frame has a lower
SwFrame* pLower = pFly->Lower(); if ( pLower && pLower->IsNoTextFrame() &&
( static_cast<SwNoTextFrame*>(pLower)->IsTransparent() ||
pFly->GetFormat()->GetSurround().IsContour() )
)
{ continue;
}
// vcl::Region of a fly frame with transparent background or a transparent // shadow have not to be subtracted from paint region if ( pFly->IsBackgroundTransparent() )
{ continue;
}
bool SwLayAction::RemoveEmptyBrowserPages()
{ // switching from the normal to the browser mode, empty pages may be // retained for an annoyingly long time, so delete them here bool bRet = false; const SwViewShell *pSh = m_pRoot->GetCurrShell(); if( pSh && pSh->GetViewOptions()->getBrowseMode() )
{
SwPageFrame *pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); do
{ if ( (pPage->GetSortedObjs() && pPage->GetSortedObjs()->size()) ||
pPage->ContainsContent() ||
pPage->FindFootnoteCont() )
pPage = static_cast<SwPageFrame*>(pPage->GetNext()); else
{
bRet = true;
SwPageFrame *pDel = pPage;
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
pDel->Cut();
SwFrame::DestroyFrame(pDel);
}
} while ( pPage );
} return bRet;
}
void SwLayAction::SetAgain(bool bAgain)
{ if (bAgain == m_bAgain) return;
m_bAgain = bAgain;
assert(m_aFrameStack.size() == m_aFrameDeleteGuards.size());
size_t nCount = m_aFrameStack.size(); if (m_bAgain)
{ // LayAction::FormatLayout is now flagged to exit early and will avoid // dereferencing any SwFrames in the stack of FormatLayouts so allow // their deletion for (size_t i = 0; i < nCount; ++i)
m_aFrameDeleteGuards[i].reset();
} else
{ // LayAction::FormatLayout is now continue normally and will // dereference the top SwFrame in the stack of m_aFrameStack as each // FormatLevel returns so disallow their deletion for (size_t i = 0; i < nCount; ++i)
m_aFrameDeleteGuards[i] = std::make_unique<SwFrameDeleteGuard>(m_aFrameStack[i]);
}
}
void SwLayAction::PushFormatLayout(SwFrame* pLow)
{ /* Workaround crash seen in crashtesting with fdo53985-1.docx
if ( !pPage->GetFormat()->GetDoc().GetFootnoteIdxs().empty() )
{
SwFootnoteContFrame *pCont = pPage->FindFootnoteCont(); if ( pCont )
{
pCnt = pCont->ContainsContent();
pChk = pCnt; while ( pCnt && pCnt->IsFollow() )
pCnt = static_cast<SwContentFrame*>(pCnt->FindPrev()); if ( pCnt && pCnt != pChk )
{ if ( bPageChgd )
{ // Use the 'topmost' page
SwPageFrame *pTmp = pCnt->FindPageFrame(); if ( pPage->GetPhyPageNum() > pTmp->GetPhyPageNum() )
pPage = pTmp;
} else
pPage = pCnt->FindPageFrame();
}
}
} return pPage;
}
// unlock position on start and end of page // layout process. staticvoid unlockPositionOfObjects( SwPageFrame *pPageFrame )
{
assert( pPageFrame );
SwSortedObjs* pObjs = pPageFrame->GetSortedObjs(); if ( pObjs )
{ for (SwAnchoredObject* pObj : *pObjs)
{
pObj->UnlockPosition();
}
}
}
void SwLayAction::InternalAction(OutputDevice* pRenderContext)
{
OSL_ENSURE( m_pRoot->Lower()->IsPageFrame(), ":-( No page below the root.");
m_pRoot->Calc(pRenderContext);
// Figure out the first invalid page or the first one to be formatted, // respectively. A complete-action means the first invalid page. // However, the first page to be formatted might be the one having the // number 1. If we're doing a fake formatting, the number of the first // page is the number of the first visible page.
SwPageFrame *pPage = IsComplete() ? static_cast<SwPageFrame*>(m_pRoot->Lower()) :
m_pImp->GetFirstVisPage(pRenderContext); if ( !pPage )
pPage = static_cast<SwPageFrame*>(m_pRoot->Lower());
// If there's a first-flow-Content in the first visible page that's also a Follow, // we switch the page back to the original master of that Content. if ( !IsComplete() )
pPage = CheckFirstVisPage( pPage );
sal_uInt16 nFirstPageNum = pPage->GetPhyPageNum();
auto lcl_isLayoutLooping = [&]()
{ constbool bAgain = this->IsAgain(); if (bAgain && bNoLoop)
rLayoutAccess.GetLayouter()->EndLoopControl(); return bAgain;
};
int nOuterLoopControlRuns = 0; constint nOuterLoopControlMax = 10000; while ( (pPage && !IsInterrupt()) || m_nCheckPageNum != USHRT_MAX )
{ // Fix infinite loop in sw_ooxmlexport17 unit test // When running the sw_ooxmlexport17 unit test on slower macOS Intel // machines, This loop will never end even after 1M+ loops so set a // maximum number of loops like is done in the nested while loops. if (++nOuterLoopControlRuns > nOuterLoopControlMax)
{
SAL_WARN("sw.layout", "SwLayAction::InternalAction has run too many loops"); if (::std::getenv("TEST_NO_LOOP_CONTROLS"))
{ throw std::exception{}; // => fail test
}
m_bInterrupt = true;
}
// note: this is the only place that consumes and resets m_nCheckPageNum if ((IsInterrupt() || !pPage) && m_nCheckPageNum != USHRT_MAX)
{ if (!pPage || m_nCheckPageNum < pPage->GetPhyPageNum())
{
SwPageFrame *pPg = static_cast<SwPageFrame*>(m_pRoot->Lower()); while (pPg && pPg->GetPhyPageNum() < m_nCheckPageNum)
pPg = static_cast<SwPageFrame*>(pPg->GetNext()); if (pPg)
pPage = pPg; if (!pPage) break;
}
// No Shortcut for Idle or CalcLayout. The shortcut case means that in case the page is not // inside the visible area, then the synchronous (not idle) layout skips the page. constbool bTakeShortcut = !IsIdle() && !IsComplete() && IsShortCut(pPage);
m_pRoot->DeleteEmptySct();
m_pRoot->DeleteEmptyFlys(); if (lcl_isLayoutLooping()) return;
if (!bTakeShortcut)
{ while ( !IsInterrupt() && !IsNextCycle() &&
((pPage->GetSortedObjs() && pPage->IsInvalidFly()) || pPage->IsInvalid()) )
{
unlockPositionOfObjects( pPage );
SwObjectFormatter::FormatObjsAtFrame( *pPage, *pPage, this ); if ( !pPage->GetSortedObjs() )
{ // If there are no (more) Flys, the flags are superfluous.
pPage->ValidateFlyLayout();
pPage->ValidateFlyContent();
} // change condition while ( !IsInterrupt() && !IsNextCycle() &&
( pPage->IsInvalid() ||
(pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) )
{
PROTOCOL( pPage, PROT::FileInit, DbgAction::NONE, nullptr) if (lcl_isLayoutLooping()) return;
// new loop control int nLoopControlRuns_1 = 0; constint nLoopControlMax = 20;
while ( !IsNextCycle() && pPage->IsInvalidLayout() )
{
pPage->ValidateLayout();
if ( ++nLoopControlRuns_1 > nLoopControlMax )
{
SAL_WARN("sw.layout", "LoopControl_1 in SwLayAction::InternalAction"); if (::std::getenv("TEST_NO_LOOP_CONTROLS"))
{ throw std::exception{}; // => fail test
} break;
}
// A previous page may be invalid again. if (lcl_isLayoutLooping()) return; if ( !pPage->GetSortedObjs() )
{ // If there are no (more) Flys, the flags are superfluous.
pPage->ValidateFlyLayout();
pPage->ValidateFlyContent();
} if ( !IsInterrupt() )
{
SetNextCycle( false );
// Continue to the next invalid page while ( pPage && !pPage->IsInvalid() &&
(!pPage->GetSortedObjs() || !pPage->IsInvalidFly()) )
{
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
} if( bNoLoop )
rLayoutAccess.GetLayouter()->LoopControl( pPage );
}
CheckIdleEnd();
}
if ((bTakeShortcut || !pPage) && !IsInterrupt() &&
(m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) )
{ // tdf#139426 allow suppression of AssertFlyPages if ( m_pRoot->IsAssertFlyPages() && !m_pRoot->IsTableUpdateInProgress())
{
m_pRoot->AssertFlyPages();
} if ( m_pRoot->IsSuperfluous() )
{ bool bOld = IsAgain();
m_pRoot->RemoveSuperfluous();
SetAgain(bOld);
} if (lcl_isLayoutLooping()) return;
pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() )
pPage = static_cast<SwPageFrame*>(pPage->GetNext()); while ( pPage && pPage->GetNext() &&
pPage->GetPhyPageNum() < nFirstPageNum )
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
} elseif (bTakeShortcut) break;
} if ( IsInterrupt() && pPage )
{ // If we have input, we don't want to format content anymore, but // we still should clean the layout. // Otherwise, the following situation might arise: // The user enters some text at the end of the paragraph of the last // page, causing the paragraph to create a Follow for the next page. // Meanwhile the user continues typing, so we have input while // still formatting. // The paragraph on the new page has already been partially formatted, // and the new page has been fully formatted and is set to CompletePaint, // but hasn't added itself to the area to be output. Then we paint, // the CompletePaint of the page is reset because the new paragraph // already added itself, but the borders of the page haven't been painted // yet. // Oh well, with the inevitable following LayAction, the page doesn't // register itself, because it's (LayoutFrame) flags have been reset // already - the border of the page will never be painted.
SwPageFrame *pPg = pPage; if (lcl_isLayoutLooping()) return; // LOK case: VisArea() is the entire document and getLOKVisibleArea() may contain the actual // visible area. const SwRect &rVisArea = m_pImp->GetShell().VisArea();
SwRect aLokVisArea(m_pImp->GetShell().getLOKVisibleArea()); bool bUseLokVisArea = comphelper::LibreOfficeKit::isActive() && !aLokVisArea.IsEmpty(); const SwRect& rVis = bUseLokVisArea ? aLokVisArea : rVisArea;
// set flag for interrupt content formatting
mbFormatContentOnInterrupt = true;
tools::Long nBottom = rVis.Bottom(); // #i42586# - format current page, if idle action is active // This is an optimization for the case that the interrupt is created by // the move of a form control object, which is represented by a window. while ( pPg && ( pPg->getFrameArea().Top() < nBottom ||
( IsIdle() && pPg == pPage ) ) )
{
unlockPositionOfObjects( pPg );
if (lcl_isLayoutLooping()) return;
// new loop control int nLoopControlRuns_2 = 0; constint nLoopControlMax = 20;
// special case: interrupt content formatting // conditions are incorrect and are too strict. // adjust interrupt formatting to normal page formatting - see above. while ( ( mbFormatContentOnInterrupt &&
( pPg->IsInvalid() ||
( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) ) ||
( !mbFormatContentOnInterrupt && pPg->IsInvalidLayout() ) )
{ if (lcl_isLayoutLooping()) return; // format also at-page anchored objects
SwObjectFormatter::FormatObjsAtFrame( *pPg, *pPg, this ); if ( !pPg->GetSortedObjs() )
{
pPg->ValidateFlyLayout();
pPg->ValidateFlyContent();
}
// new loop control int nLoopControlRuns_3 = 0;
while ( pPg->IsInvalidLayout() )
{
pPg->ValidateLayout();
if ( ++nLoopControlRuns_3 > nLoopControlMax )
{
SAL_WARN("sw.layout", "LoopControl_3 in Interrupt formatting in SwLayAction::InternalAction"); if (::std::getenv("TEST_NO_LOOP_CONTROLS"))
{ throw std::exception{}; // => fail test
} break;
}
FormatLayout( pRenderContext, pPg ); if (lcl_isLayoutLooping()) return;
}
if ( ++nLoopControlRuns_2 > nLoopControlMax )
{
SAL_WARN("sw.layout", "LoopControl_2 in Interrupt formatting in SwLayAction::InternalAction"); if (::std::getenv("TEST_NO_LOOP_CONTROLS"))
{ throw std::exception{}; // => fail test
} break;
}
if ( !FormatContent( pPg ) )
{ if (lcl_isLayoutLooping()) return;
pPg->InvalidateContent();
pPg->InvalidateFlyInCnt();
pPg->InvalidateFlyLayout();
pPg->InvalidateFlyContent();
} // we are satisfied if the content is formatted once complete. else
{ break;
}
}
}
unlockPositionOfObjects( pPg );
pPg = static_cast<SwPageFrame*>(pPg->GetNext());
} if (m_pRoot->IsSuperfluous()) // could be newly set now!
{ bool bOld = IsAgain();
m_pRoot->RemoveSuperfluous();
SetAgain(bOld);
} // reset flag for special interrupt content formatting.
mbFormatContentOnInterrupt = false;
}
m_pOptTab = nullptr; if( bNoLoop )
rLayoutAccess.GetLayouter()->EndLoopControl();
}
if ( !pCnt->GetValidLineNumFlag() && pCnt->IsTextFrame() )
{ const sal_Int32 nAllLines = static_cast<const SwTextFrame*>(pCnt)->GetAllLines(); const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCnt))->RecalcAllLines(); if ( nAllLines != static_cast<const SwTextFrame*>(pCnt)->GetAllLines() )
{ if ( IsPaintExtraData() )
m_pImp->GetShell().AddPaintRect( pCnt->getFrameArea() ); // This is to calculate the remaining LineNums on the page, // and we don't stop processing here. To perform this inside RecalcAllLines // would be expensive, because we would have to notify the page even // in unnecessary cases (normal actions). const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); while ( pNxt &&
(pNxt->IsInTab() || pNxt->IsInDocBody() != pCnt->IsInDocBody()) )
pNxt = pNxt->GetNextContentFrame(); if ( pNxt )
pNxt->InvalidatePage();
} returnfalse;
}
if ( pPage->IsInvalidLayout() || (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) returnfalse;
} if ( !pPage )
pPage = pCnt->FindPageFrame();
// format floating screen objects at content frame. if ( pCnt->IsTextFrame() &&
!SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pCnt),
*pPage, this ) )
{ returnfalse;
}
if ( pPage->IsInvalidContent() ) returnfalse; returntrue;
}
/* Returns True if the page lies directly below or right of the visible area. * *It'spossibleforthingstochangeinsuchawaythattheprocessing *(ofthecaller!)hastocontinuewiththepredecessorofthepassedpage. *Theparametermightthereforegetmodified! *ForBrowseMode,youmayevenactivatetheShortCutiftheinvalidcontent *ofthepageliesbelowthevisiblearea.
*/ bool SwLayAction::IsShortCut( SwPageFrame *&prPage )
{
vcl::RenderContext* pRenderContext = m_pImp->GetShell().GetOut(); bool bRet = false; const SwViewShell *pSh = m_pRoot->GetCurrShell(); constbool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode();
// If the page is not valid, we quickly format it, otherwise // there's gonna be no end of trouble if ( !prPage->isFrameAreaDefinitionValid() )
{ if ( bBrowse )
{ // format complete page // Thus, loop on all lowers of the page <prPage>, instead of only // format its first lower. // NOTE: In online layout (bBrowse == true) a page can contain // a header frame and/or a footer frame beside the body frame.
prPage->Calc(pRenderContext);
SwFrame* pPageLowerFrame = prPage->Lower(); while ( pPageLowerFrame )
{
pPageLowerFrame->Calc(pRenderContext);
pPageLowerFrame = pPageLowerFrame->GetNext();
}
} else
FormatLayout( pSh ? pSh->GetOut() : nullptr, prPage ); if ( IsAgain() ) returnfalse;
}
// Decide if prPage is visible, i.e. part of the visible area. const SwRect &rVisArea = m_pImp->GetShell().VisArea(); // LOK case: VisArea() is the entire document and getLOKVisibleArea() may contain the actual // visible area.
SwRect aLokVisArea(m_pImp->GetShell().getLOKVisibleArea()); bool bUseLokVisArea = comphelper::LibreOfficeKit::isActive() && !aLokVisArea.IsEmpty(); const SwRect& rVis = bUseLokVisArea ? aLokVisArea : rVisArea;
// This is going to be a bit nasty: The first ContentFrame of this // page in the Body text needs formatting; if it changes the page during // that process, I need to start over a page further back, because we // have been processing a PageBreak. // Even more uncomfortable: The next ContentFrame must be formatted, // because it's possible for empty pages to exist temporarily (for example // a paragraph across multiple pages gets deleted or reduced in size).
// This is irrelevant for the browser, if the last Cnt above it // isn't visible anymore.
if ( bTstCnt )
{ // check after each frame calculation, // if the content frame has changed the page. If yes, no other // frame calculation is performed bool bPageChg = false;
if ( pContent->IsInSct() )
{ const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame(); if ( !pSct->isFrameAreaDefinitionValid() )
{
pSct->Calc(pRenderContext);
pSct->SetCompletePaint(); if ( IsAgain() ) returnfalse;
if ( !bNoPaint && IsPaint() && bAddRect && (pLay->IsCompletePaint() || bChanged) )
{
SwRect aPaint( pLay->getFrameArea() ); // consider border and shadow for // page frames -> enlarge paint rectangle correspondingly. if ( pLay->IsPageFrame() )
{
SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay);
aPaint = pPageFrame->GetBoundRect(pRenderContext);
} elseif (pLay->IsRowFrame())
{ // tdf#167419 Changing the borders of a table doesn't refresh // the bottom border. That border is outside the FrameArea // of the table when the default CollapsingBorders is enabled. // Add it in here for the last row when determining the area // to refresh. const SwRowFrame* pRowFrame = static_cast<SwRowFrame*>(pLay); constbool bLastRow = !pRowFrame->GetNext(); const SwTwips nBorderThicknessUnderArea = bLastRow ? pRowFrame->GetBottomLineSize() : 0; if (nBorderThicknessUnderArea)
{ const SwTabFrame* pTabFrame = pRowFrame->FindTabFrame(); if (pTabFrame && pTabFrame->IsCollapsingBorders())
aPaint.AddBottom(nBorderThicknessUnderArea);
}
}
// Now, deal with the lowers that are LayoutFrames
if ( pLay->IsFootnoteFrame() ) // no LayFrames as Lower return bChanged;
SwFrame *pLow = pLay->Lower(); bool bTabChanged = false; while ( pLow && pLow->GetUpper() == pLay )
{
SwFrame* pNext = nullptr; if ( pLow->IsLayoutFrame() )
{ if ( pLow->IsTabFrame() )
{ // Remember what was the next of the lower. Formatting may move it to the previous // page, in which case it looses its next.
pNext = pLow->GetNext();
if (pNext && pNext->IsTabFrame())
{ auto pTab = static_cast<SwTabFrame*>(pNext); if (pTab->IsFollow())
{ // The next frame is a follow of the previous frame, SwTabFrame::Join() will // delete this one as part of formatting, so forget about it.
pNext = nullptr;
}
}
bTabChanged |= FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect );
} // Skip the ones already registered for deletion elseif( !pLow->IsSctFrame() || static_cast<SwSectionFrame*>(pLow)->GetSection() )
{
PushFormatLayout(pLow);
bChanged |= FormatLayout( pRenderContext, static_cast<SwLayoutFrame*>(pLow), bAddRect );
PopFormatLayout();
}
} elseif (pLay->IsSctFrame() && pLay->GetNext() && pLay->GetNext()->IsSctFrame() && pLow->IsTextFrame() && pLow == pLay->GetLastLower())
{ // else: only calc the last text lower of sections, followed by sections
pLow->OptCalc();
}
if ( IsAgain() ) returnfalse; if (!pNext)
{
pNext = pLow->GetNext();
}
pLow = pNext;
} // add complete frame area as paint area, if frame // area has been already added and after formatting its lowers the frame area // is enlarged.
SwRect aBoundRect(pLay->IsPageFrame() ? static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext) : pLay->getFrameArea() );
if ( pTab->IsCompletePaint() && !m_pOptTab )
m_pOptTab = pTab;
pTab->ResetCompletePaint();
} if ( IsPaint() && bAddRect && pTab->IsRetouche() && !pTab->GetNext() )
{ // set correct rectangle for retouche: area between bottom of table frame // and bottom of paint area of the upper frame.
SwRect aRect( pTab->GetUpper()->GetPaintArea() ); // vertical layout support
aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) ); if ( !m_pImp->GetShell().AddPaintRect( aRect ) )
pTab->ResetRetouche();
}
// Now, deal with the lowers if ( IsAgain() ) returnfalse;
// for safety reasons: // check page number before formatting lowers. if ( pOldPage->GetPhyPageNum() > (pTab->FindPageFrame()->GetPhyPageNum() + 1) )
SetNextCycle( true );
// format lowers, only if table frame is valid if ( pTab->isFrameAreaDefinitionValid() )
{ // tdf#128437 FlowFrameJoinLockGuard on pTab caused a problem here
SwLayoutFrame *pLow = static_cast<SwLayoutFrame*>(pTab->Lower()); while ( pLow )
{
SwFrameDeleteGuard rowG(pLow); // tdf#124675 prevent RemoveFollowFlowLine()
bChanged |= FormatLayout( m_pImp->GetShell().GetOut(), pLow, bAddRect ); if ( IsAgain() ) returnfalse;
pLow = static_cast<SwLayoutFrame*>(pLow->GetNext());
}
}
return bChanged;
}
bool SwLayAction::FormatContent(SwPageFrame *const pPage)
{
::comphelper::ScopeGuard g([this, pPage]() { if (IsAgain())
{ return; // pPage probably deleted
} if (autoconst* pObjs = pPage->GetSortedObjs())
{
std::vector<std::pair<SwAnchoredObject*, SwPageFrame*>> moved; for (autoconst pObj : *pObjs)
{
assert(!pObj->AnchorFrame()->IsTextFrame()
|| !static_cast<SwTextFrame const*>(pObj->AnchorFrame())->IsFollow());
SwPageFrame *const pAnchorPage(pObj->AnchorFrame()->FindPageFrame());
assert(pAnchorPage); if (pAnchorPage != pPage
&& pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum()
&& pObj->GetFrameFormat()->GetAnchor().GetAnchorId()
!= RndStdIds::FLY_AS_CHAR)
{
moved.emplace_back(pObj, pAnchorPage);
}
} for (autoconst& [pObj, pAnchorPage] : moved)
{
SAL_INFO("sw.layout", "SwLayAction::FormatContent: move anchored " << pObj << " from " << pPage->GetPhyPageNum() << " to " << pAnchorPage->GetPhyPageNum());
pObj->RegisterAtPage(*pAnchorPage); // tdf#143239 if the position remains valid, it may not be // positioned again so would remain on the wrong page!
pObj->InvalidateObjPos();
::Notify_Background(pObj->GetDrawObj(), pPage,
pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false); // tdf#148897 in case the fly moves back to this page before // being positioned again, the SwFlyNotify / ::Notify() could // conclude that it didn't move at all and not call // NotifyBackground(); note: pObj->SetObjTop(FAR_AWAY) results // in wrong positions, use different approach!
pObj->SetForceNotifyNewBackground(true);
} if (!moved.empty())
{
pPage->InvalidateFlyLayout(); if (auto *const pContent = pPage->FindLastBodyContent())
{
pContent->InvalidateSize();
}
}
}
});
while ( pContent && pPage->IsAnLower( pContent ) )
{ // If the content didn't change, we can use a few shortcuts. bool bFull = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint() ||
pContent->IsRetouche() || pContent->GetDrawObjs();
auto pText = pContent->DynCastTextFrame(); if (!bFull && !pContent->GetDrawObjs() && pContent->IsFollow() && pText)
{ // This content frame doesn't have to-para anchored objects, but it's a follow, check // the master. const SwTextFrame* pMaster = pText; while (pMaster->IsFollow())
{
pMaster = pMaster->FindMaster();
} if (pMaster && pMaster->GetDrawObjs())
{ for (SwAnchoredObject* pDrawObj : *pMaster->GetDrawObjs())
{ auto pFly = pDrawObj->DynCastFlyFrame(); if (!pFly)
{ continue;
}
if (!pFly->IsFlySplitAllowed())
{ continue;
}
if (pFly->GetAnchorFrameContainingAnchPos() != pContent)
{ continue;
}
// This fly is effectively anchored to pContent, still format pContent.
bFull = true; break;
}
}
}
if ( bFull )
{ // We do this so we don't have to search later on. constbool bNxtCnt = IsCalcLayout() && !pContent->GetFollow(); const SwContentFrame *pContentNext = bNxtCnt ? pContent->GetNextContentFrame() : nullptr;
SwContentFrame* const pContentPrev = pContent->GetPrev() ? pContent->GetPrevContentFrame() : nullptr;
std::optional<SfxDeleteListener> oPrevDeleteListener; if (pContentPrev)
oPrevDeleteListener.emplace(*pContentPrev);
// format floating screen object at content frame. // No format, if action flag <bAgain> is set or action is interrupted. // allow format on interruption of action, if // it's the format for this interrupt // pass correct page frame // to the object formatter. if ( !IsAgain() &&
( !IsInterrupt() || mbFormatContentOnInterrupt ) &&
pContent->IsTextFrame() &&
!SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pContent),
*(pContent->FindPageFrame()), this ) )
{ returnfalse;
}
// Temporarily interrupt processing if layout or Flys become invalid again. // However not for the BrowseView: The layout is getting invalid // all the time because the page height gets adjusted. // The same applies if the user wants to continue working and at least one // paragraph has been processed. if (!pTab || !bInValid)
{
CheckIdleEnd(); // consider interrupt formatting. if ( ( IsInterrupt() && !mbFormatContentOnInterrupt ) ||
( !bBrowse && pPage->IsInvalidLayout() ) || // consider interrupt formatting
( pPage->GetSortedObjs() && pPage->IsInvalidFly() && !mbFormatContentOnInterrupt )
) returnfalse;
} if ( pOldUpper != pContent->GetUpper() )
{ const sal_uInt16 nCurNum = pContent->FindPageFrame()->GetPhyPageNum(); if ( nCurNum < pPage->GetPhyPageNum() )
m_nPreInvaPage = nCurNum;
// If the frame flowed backwards more than one page, we need to // start over again from the beginning, so nothing gets left out. if ( !IsCalcLayout() && pPage->GetPhyPageNum() > nCurNum+1 )
{
SetNextCycle( true ); // consider interrupt formatting if ( !mbFormatContentOnInterrupt )
{ returnfalse;
}
}
} // If the frame moved forwards to the next page, we re-run through // the predecessor. // This way, we catch predecessors which are now responsible for // retouching, but the footers will be touched also. bool bSetContent = true; if ( pContentPrev )
{ if (oPrevDeleteListener->WasDeleted())
{
SAL_WARN("sw", "ContentPrev was deleted"); returnfalse;
}
while ( pContent )
{
FormatContent_( pContent, pContent->FindPageFrame() );
// format floating screen objects at content text frame // pass correct page frame to the object formatter. if ( pContent->IsTextFrame() &&
!SwObjectFormatter::FormatObjsAtFrame(
*const_cast<SwContentFrame*>(pContent),
*(pContent->FindPageFrame()), this ) )
{ // restart format with first content
pContent = pFly->ContainsContent(); continue;
}
switch ( eJob )
{ case IdleJobType::ONLINE_SPELLING:
{
SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->AutoSpell_(*pTextNode, nPos) ); // PENDING should stop idle spell checking
m_bPageValid = m_bPageValid && (sw::WrongState::TODO != pTextNode->GetWrongDirty()); if ( aRepaint.HasArea() )
m_pImp->GetShell().InvalidateWindows( aRepaint ); if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) returntrue; break;
} case IdleJobType::AUTOCOMPLETE_WORDS: const_cast<SwTextFrame*>(pTextFrame)->CollectAutoCmplWrds(*pTextNode, nPos); // note: bPageValid remains true here even if the cursor // position is skipped, so no PENDING state needed currently if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) returntrue; break; case IdleJobType::WORD_COUNT:
{ const sal_Int32 nEnd = pTextNode->GetText().getLength();
SwDocStat aStat;
pTextNode->CountWords( aStat, 0, nEnd ); if ( Application::AnyInput() ) returntrue; break;
} case IdleJobType::SMART_TAGS:
{ try { const SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->SmartTagScan(*pTextNode) );
m_bPageValid = m_bPageValid && !pTextNode->IsSmartTagDirty(); if ( aRepaint.HasArea() )
m_pImp->GetShell().InvalidateWindows( aRepaint );
} catch( const css::uno::RuntimeException&) { // handle smarttag problems gracefully and provide diagnostics
TOOLS_WARN_EXCEPTION( "sw.core", "SMART_TAGS");
} if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) returntrue; break;
}
}
}
// The Flys that are anchored to the paragraph need to be considered too. if ( pCnt->GetDrawObjs() )
{ const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); for (SwAnchoredObject* pObj : rObjs)
{ if ( auto pFly = pObj->DynCastFlyFrame() )
{ if ( pFly->IsFlyInContentFrame() )
{ const SwContentFrame *pC = pFly->ContainsContent(); while( pC )
{ if ( pC->IsTextFrame() )
{ if ( DoIdleJob_( pC, eJob ) ) returntrue;
}
pC = pC->GetNextContentFrame();
}
}
}
}
} returnfalse;
}
case IdleJobType::AUTOCOMPLETE_WORDS:
{ if (!SwViewOption::IsAutoCompleteWords() || SwDoc::GetAutoCompleteWords().IsLockWordLstLocked()) returnfalse; returntrue;
}
case IdleJobType::WORD_COUNT:
{ return pViewShell->getIDocumentStatistics().GetDocStat().bModified;
}
case IdleJobType::SMART_TAGS:
{ const SwDoc* pDoc = pViewShell->GetDoc(); const SwDocShell* pShell = pDoc->GetDocShell(); if (!pShell) returnfalse;
if (pShell->IsHelpDocument() || pDoc->isXForms() || !SwSmartTagMgr::Get().IsSmartTagsEnabled()) returnfalse; returntrue;
}
}
returnfalse;
}
bool SwLayIdle::DoIdleJob(IdleJobType eJob, IdleJobArea eJobArea)
{ // Spellcheck all contents of the pages. Either only the // visible ones or all of them. const SwViewShell& rViewShell = m_pImp->GetShell();
// Check if job ius enabled and can run if (!isJobEnabled(eJob, &rViewShell)) returnfalse;
while ( pPage )
{
m_bPageValid = true; const SwContentFrame* pContentFrame = pPage->ContainsContent(); while (pContentFrame && pPage->IsAnLower(pContentFrame))
{ if (DoIdleJob_(pContentFrame, eJob))
{
SAL_INFO("sw.idle", "DoIdleJob " << sal_Int32(eJob) << " interrupted on page " << pPage->GetPhyPageNum()); returntrue;
}
pContentFrame = pContentFrame->GetNextContentFrame();
} if ( pPage->GetSortedObjs() )
{ for ( size_t i = 0; pPage->GetSortedObjs() &&
i < pPage->GetSortedObjs()->size(); ++i )
{ const SwAnchoredObject* pObj = (*pPage->GetSortedObjs())[i]; if ( auto pFly = pObj->DynCastFlyFrame() )
{ const SwContentFrame *pC = pFly->ContainsContent(); while( pC )
{ if ( pC->IsTextFrame() )
{ if ( DoIdleJob_( pC, eJob ) )
{
SAL_INFO("sw.idle", "DoIdleJob " << sal_Int32(eJob) << " interrupted on page " << pPage->GetPhyPageNum()); returntrue;
}
}
pC = pC->GetNextContentFrame();
}
}
}
}
if( m_bPageValid )
{ switch (eJob)
{ case IdleJobType::ONLINE_SPELLING:
pPage->ValidateSpelling(); break; case IdleJobType::AUTOCOMPLETE_WORDS:
pPage->ValidateAutoCompleteWords(); break; case IdleJobType::WORD_COUNT:
pPage->ValidateWordCount(); break; case IdleJobType::SMART_TAGS:
pPage->ValidateSmartTags(); break;
}
}
pPage = static_cast<SwPageFrame*>(pPage->GetNext()); // LOK case: VisArea() is the entire document and getLOKVisibleArea() may contain the actual // visible area. const SwRect &rVisArea = m_pImp->GetShell().VisArea();
SwRect aLokVisArea(m_pImp->GetShell().getLOKVisibleArea()); bool bUseLokVisArea = comphelper::LibreOfficeKit::isActive() && !aLokVisArea.IsEmpty(); const SwRect& rVis = bUseLokVisArea ? aLokVisArea : rVisArea; if (pPage && eJobArea == IdleJobArea::VISIBLE &&
!pPage->getFrameArea().Overlaps(rVis))
{ break;
}
} returnfalse;
}
#if HAVE_FEATURE_DESKTOP && defined DBG_UTIL void SwLayIdle::ShowIdle( Color eColor )
{ if ( m_bIndicator ) return;
m_bIndicator = true;
vcl::Window *pWin = m_pImp->GetShell().GetWin(); if (pWin && !pWin->SupportsDoubleBuffering()) // FIXME make this work with double-buffering
{
tools::Rectangle aRect( 0, 0, 5, 5 );
aRect = pWin->PixelToLogic( aRect ); // Depending on if idle layout is in progress or not, draw a "red square" or a "green square".
pWin->GetOutDev()->Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR );
pWin->GetOutDev()->SetFillColor( eColor );
pWin->GetOutDev()->SetLineColor();
pWin->GetOutDev()->DrawRect( aRect );
pWin->GetOutDev()->Pop();
}
} #define SHOW_IDLE( Color ) ShowIdle( Color ) #else #define SHOW_IDLE( Color ) #endif// DBG_UTIL
// First, spellcheck the visible area. Only if there's nothing // to do there, we trigger the IdleFormat. if ( !DoIdleJob(IdleJobType::SMART_TAGS, IdleJobArea::VISIBLE) &&
!DoIdleJob(IdleJobType::ONLINE_SPELLING, IdleJobArea::VISIBLE) &&
!DoIdleJob(IdleJobType::AUTOCOMPLETE_WORDS, IdleJobArea::VISIBLE) )
{ // Format, then register repaint rectangles with the SwViewShell if necessary. // This requires running artificial actions, so we don't get undesired // effects when for instance the page count gets changed. // We remember the shells where the cursor is visible, so we can make // it visible again if needed after a document change.
std::vector<bool> aBools; for(SwViewShell& rSh : m_pImp->GetShell().GetRingContainer())
{
++rSh.mnStartAction; bool bVis = false; if ( auto pCursorShell = dynamic_cast<SwCursorShell*>( &rSh) )
{
bVis = pCursorShell->GetCharRect().Overlaps(rSh.VisArea());
}
aBools.push_back( bVis );
}
SdrModel* pSdrModel = m_pImp->GetShell().getIDocumentDrawModelAccess().GetDrawModel(); bool bSdrModelIdle{}; if (pSdrModel)
{ // Let the draw views know that we're inside the idle layout.
bSdrModelIdle = pSdrModel->IsWriterIdle();
pSdrModel->SetWriterIdle(true);
}
aAction.Action(m_pImp->GetShell().GetOut());
if (pSdrModel)
{
pSdrModel->SetWriterIdle(bSdrModelIdle);
}
bInterrupt = aAction.IsInterrupt();
}
// Further start/end actions only happen if there were paints started // somewhere or if the visibility of the CharRects has changed. bool bActions = false;
size_t nBoolIdx = 0; for(SwViewShell& rSh : m_pImp->GetShell().GetRingContainer())
{
--rSh.mnStartAction;
// Are we supposed to crash if rSh isn't a cursor shell?! // bActions |= aTmp != rSh.VisArea() || // aBools[nBoolIdx] != ((SwCursorShell*)&rSh)->GetCharRect().IsOver( rSh.VisArea() );
// aBools[ i ] is true, if the i-th shell is a cursor shell (!!!) // and the cursor is visible.
bActions |= aTmp != rSh.VisArea(); if ( aTmp == rSh.VisArea() ) if ( auto pCursorShell = dynamic_cast< SwCursorShell*>( &rSh) )
bActions |= aBools[nBoolIdx] != pCursorShell->GetCharRect().Overlaps( rSh.VisArea() );
}
++nBoolIdx;
}
if ( bActions )
{ // Prepare start/end actions via CursorShell, so the cursor, selection // and VisArea can be set correctly.
nBoolIdx = 0; for(SwViewShell& rSh : m_pImp->GetShell().GetRingContainer())
{
SwCursorShell* pCursorShell = dynamic_cast<SwCursorShell*>( &rSh);
if ( pCursorShell )
pCursorShell->SttCursorMove();
// If there are accrued paints, it's best to simply invalidate // the whole window. Otherwise there would arise paint problems whose // solution would be disproportionally expensive.
SwViewShellImp *pViewImp = rSh.Imp(); bool bUnlock = false; if ( pViewImp->HasPaintRegion() )
{
SAL_INFO("sw.idle", "Disappointing full document invalidation");
pViewImp->DeletePaintRegion();
// Cause a repaint with virtual device.
rSh.LockPaint(LockPaintReason::SwLayIdle);
bUnlock = true;
}
if ( pCursorShell ) // If the Cursor was visible, we need to make it visible again. // Otherwise, EndCursorMove with true for IdleEnd
pCursorShell->EndCursorMove( !aBools[nBoolIdx] ); if( bUnlock )
{ if( pCursorShell )
{ // UnlockPaint overwrite the selection from the // CursorShell and calls the virtual method paint // to fill the virtual device. This fill don't have // paint the selection! -> Set the focus flag at // CursorShell and it doesn't paint the selection.
pCursorShell->ShellLoseFocus();
pCursorShell->UnlockPaint( true );
pCursorShell->ShellGetFocus();
} else
rSh.UnlockPaint( true );
}
++nBoolIdx;
}
}
if (!bInterrupt)
{ if (!DoIdleJob(IdleJobType::WORD_COUNT, IdleJobArea::ALL)) if (!DoIdleJob(IdleJobType::SMART_TAGS, IdleJobArea::ALL)) if (!DoIdleJob(IdleJobType::ONLINE_SPELLING, IdleJobArea::ALL))
DoIdleJob(IdleJobType::AUTOCOMPLETE_WORDS, IdleJobArea::ALL);
}
#ifdef DBG_UTIL if ( m_bIndicator && m_pImp->GetShell().GetWin() )
{ // Do not invalidate indicator, this may cause an endless loop. Instead, just repaint it // This should be replaced by an overlay object in the future, anyways. Since it's only for debug // purposes, it is not urgent.
m_bIndicator = false; SHOW_IDLE( COL_LIGHTGREEN );
} #endif
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.