summaryrefslogtreecommitdiffstats
path: root/sw/source/core/layout/layact.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/layout/layact.cxx')
-rw-r--r--sw/source/core/layout/layact.cxx2419
1 files changed, 2419 insertions, 0 deletions
diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx
new file mode 100644
index 000000000..6cdfcfce0
--- /dev/null
+++ b/sw/source/core/layout/layact.cxx
@@ -0,0 +1,2419 @@
+/* -*- 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 <config_feature_desktop.h>
+#include <config_wasm_strip.h>
+
+#include <ctime>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <viewimp.hxx>
+#include <crsrsh.hxx>
+#include <dflyobj.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <viewopt.hxx>
+#include <dbg_lay.hxx>
+#include <layouter.hxx>
+#include <docstat.hxx>
+#include <swevent.hxx>
+#include <IDocumentDrawModelAccess.hxx>
+#include <IDocumentStatistics.hxx>
+#include <IDocumentLayoutAccess.hxx>
+
+#include <sfx2/event.hxx>
+
+#include <ftnidx.hxx>
+#include <vcl/svapp.hxx>
+#include <editeng/opaqitem.hxx>
+#include <SwSmartTagMgr.hxx>
+#include <sal/log.hxx>
+
+#include <layact.hxx>
+#include <swwait.hxx>
+#include <fmtsrnd.hxx>
+#include <docsh.hxx>
+
+#include <anchoreddrawobject.hxx>
+#include <ndtxt.hxx>
+#include <tabfrm.hxx>
+#include <ftnfrm.hxx>
+#include <txtfrm.hxx>
+#include <notxtfrm.hxx>
+#include <flyfrms.hxx>
+#include <mdiexp.hxx>
+#include <sectfrm.hxx>
+#include <acmplwrd.hxx>
+#include <deletelistener.hxx>
+#include <sortedobjs.hxx>
+#include <objectformatter.hxx>
+#include <fntcache.hxx>
+#include <fmtanchr.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <vector>
+#include <tools/diagnose_ex.h>
+
+void SwLayAction::CheckWaitCursor()
+{
+ if (IsReschedule())
+ {
+ ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell());
+ }
+ if ( !m_pWait && IsWaitAllowed() && IsPaint() &&
+ ((std::clock() - m_nStartTicks) * 1000 / CLOCKS_PER_SEC >= CLOCKS_PER_SEC/2) )
+ {
+ m_pWait.reset( new SwWait( *m_pRoot->GetFormat()->GetDoc()->GetDocShell(), true ) );
+ }
+}
+
+// Time over already?
+inline void SwLayAction::CheckIdleEnd()
+{
+ if (!IsInterrupt())
+ m_bInterrupt = bool(GetInputType()) && Application::AnyInput(GetInputType());
+}
+
+void SwLayAction::SetStatBar( bool bNew )
+{
+ if ( bNew )
+ {
+ m_nEndPage = m_pRoot->GetPageNum();
+ m_nEndPage += m_nEndPage * 10 / 100;
+ }
+ else
+ m_nEndPage = USHRT_MAX;
+}
+
+bool SwLayAction::PaintWithoutFlys( const SwRect &rRect, const SwContentFrame *pCnt,
+ const SwPageFrame *pPage )
+{
+ SwRegionRects aTmp( rRect );
+ const SwSortedObjs &rObjs = *pPage->GetSortedObjs();
+ const SwFlyFrame *pSelfFly = pCnt->FindFlyFrame();
+
+ for ( size_t i = 0; i < rObjs.size() && !aTmp.empty(); ++i )
+ {
+ SwVirtFlyDrawObj *pVirtFly = dynamic_cast<SwVirtFlyDrawObj*>(rObjs[i]->DrawObj());
+ if ( !pVirtFly )
+ continue;
+
+ // do not consider invisible objects
+ const IDocumentDrawModelAccess& rIDDMA = pPage->GetFormat()->getIDocumentDrawModelAccess();
+ if ( !rIDDMA.IsVisibleLayerId( pVirtFly->GetLayer() ) )
+ {
+ continue;
+ }
+
+ SwFlyFrame *pFly = pVirtFly->GetFlyFrame();
+
+ if ( pFly == pSelfFly || !rRect.Overlaps( pFly->getFrameArea() ) )
+ continue;
+
+ 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
+ {
+ const bool 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
+ if ( pFly->Lower() &&
+ pFly->Lower()->IsNoTextFrame() &&
+ ( static_cast<SwNoTextFrame*>(pFly->Lower())->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;
+ }
+
+ aTmp -= pFly->getFrameArea();
+ }
+
+ bool bRetPaint = false;
+ for ( const auto& rRegionRect : aTmp )
+ bRetPaint |= m_pImp->GetShell()->AddPaintRect( rRegionRect );
+ return bRetPaint;
+}
+
+inline bool SwLayAction::PaintContent_( const SwContentFrame *pContent,
+ const SwPageFrame *pPage,
+ const SwRect &rRect )
+{
+ if ( rRect.HasArea() )
+ {
+ if ( pPage->GetSortedObjs() )
+ return PaintWithoutFlys( rRect, pContent, pPage );
+ else
+ return m_pImp->GetShell()->AddPaintRect( rRect );
+ }
+ return false;
+}
+
+/**
+ * Depending of the type, the Content is output according to its changes, or the area
+ * to be outputted is registered with the region, respectively.
+ */
+void SwLayAction::PaintContent( const SwContentFrame *pCnt,
+ const SwPageFrame *pPage,
+ const SwRect &rOldRect,
+ tools::Long nOldBottom )
+{
+ SwRectFnSet aRectFnSet(pCnt);
+
+ if ( pCnt->IsCompletePaint() || !pCnt->IsTextFrame() )
+ {
+ SwRect aPaint( pCnt->GetPaintArea() );
+ if ( !PaintContent_( pCnt, pPage, aPaint ) )
+ pCnt->ResetCompletePaint();
+ }
+ else
+ {
+ // paint the area between printing bottom and frame bottom and
+ // the area left and right beside the frame, if its height changed.
+ tools::Long nOldHeight = aRectFnSet.GetHeight(rOldRect);
+ tools::Long nNewHeight = aRectFnSet.GetHeight(pCnt->getFrameArea());
+ const bool bHeightDiff = nOldHeight != nNewHeight;
+ if( bHeightDiff )
+ {
+ // consider whole potential paint area.
+ SwRect aDrawRect( pCnt->GetPaintArea() );
+ if( nOldHeight > nNewHeight )
+ nOldBottom = aRectFnSet.GetPrtBottom(*pCnt);
+ aRectFnSet.SetTop( aDrawRect, nOldBottom );
+ PaintContent_( pCnt, pPage, aDrawRect );
+ }
+ // paint content area
+ SwRect aPaintRect = static_cast<SwTextFrame*>(const_cast<SwContentFrame*>(pCnt))->GetPaintSwRect();
+ PaintContent_( pCnt, pPage, aPaintRect );
+ }
+
+ if ( !pCnt->IsRetouche() || pCnt->GetNext() )
+ return;
+
+ const SwFrame *pTmp = pCnt;
+ if( pCnt->IsInSct() )
+ {
+ const SwSectionFrame* pSct = pCnt->FindSctFrame();
+ if( pSct->IsRetouche() && !pSct->GetNext() )
+ pTmp = pSct;
+ }
+ SwRect aRect( pTmp->GetUpper()->GetPaintArea() );
+ aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTmp) );
+ if ( !PaintContent_( pCnt, pPage, aRect ) )
+ pCnt->ResetRetouche();
+}
+
+SwLayAction::SwLayAction( SwRootFrame *pRt, SwViewShellImp *pI ) :
+ m_pRoot( pRt ),
+ m_pImp( pI ),
+ m_pOptTab( nullptr ),
+ m_nPreInvaPage( USHRT_MAX ),
+ m_nStartTicks( std::clock() ),
+ m_nInputType( VclInputFlags::NONE ),
+ m_nEndPage( USHRT_MAX ),
+ m_nCheckPageNum( USHRT_MAX )
+{
+ m_bPaintExtraData = ::IsExtraData( m_pImp->GetShell()->GetDoc() );
+ m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true;
+ m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule =
+ m_bUpdateExpFields = m_bBrowseActionStop = m_bActionInProgress = false;
+ // init new flag <mbFormatContentOnInterrupt>.
+ mbFormatContentOnInterrupt = false;
+
+ assert(!m_pImp->m_pLayAction); // there can be only one SwLayAction
+ m_pImp->m_pLayAction = this; // register there
+}
+
+SwLayAction::~SwLayAction()
+{
+ OSL_ENSURE( !m_pWait, "Wait object not destroyed" );
+ m_pImp->m_pLayAction = nullptr; // unregister
+}
+
+void SwLayAction::Reset()
+{
+ SetAgain(false);
+ m_pOptTab = nullptr;
+ m_nStartTicks = std::clock();
+ m_nInputType = VclInputFlags::NONE;
+ m_nEndPage = m_nPreInvaPage = m_nCheckPageNum = USHRT_MAX;
+ m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true;
+ m_bInterrupt = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule =
+ m_bUpdateExpFields = m_bBrowseActionStop = false;
+}
+
+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
+
+ Lock pLow against getting deleted when it will be dereferenced
+ after FormatLayout
+
+ If SetAgain is called to make SwLayAction exit early to avoid that
+ dereference, then it clears these guards
+ */
+ m_aFrameStack.push_back(pLow);
+ m_aFrameDeleteGuards.push_back(std::make_unique<SwFrameDeleteGuard>(pLow));
+}
+
+void SwLayAction::PopFormatLayout()
+{
+ m_aFrameDeleteGuards.pop_back();
+ m_aFrameStack.pop_back();
+}
+
+void SwLayAction::Action(OutputDevice* pRenderContext)
+{
+ m_bActionInProgress = true;
+
+ //TurboMode? Hands-off during idle-format
+ if ( IsPaint() && !IsIdle() && TurboAction() )
+ {
+ m_pWait.reset();
+ m_pRoot->ResetTurboFlag();
+ m_bActionInProgress = false;
+ m_pRoot->DeleteEmptySct();
+ return;
+ }
+ else if ( m_pRoot->GetTurbo() )
+ {
+ m_pRoot->DisallowTurbo();
+ const SwFrame *pFrame = m_pRoot->GetTurbo();
+ m_pRoot->ResetTurbo();
+ pFrame->InvalidatePage();
+ }
+ m_pRoot->DisallowTurbo();
+
+ if ( IsCalcLayout() )
+ SetCheckPages( false );
+
+ InternalAction(pRenderContext);
+ if (RemoveEmptyBrowserPages())
+ SetAgain(true);
+ while ( IsAgain() )
+ {
+ SetAgain(false);
+ m_bNextCycle = false;
+ InternalAction(pRenderContext);
+ if (RemoveEmptyBrowserPages())
+ SetAgain(true);
+ }
+ m_pRoot->DeleteEmptySct();
+
+ m_pWait.reset();
+
+ //Turbo-Action permitted again for all cases.
+ m_pRoot->ResetTurboFlag();
+ m_pRoot->ResetTurbo();
+
+ SetCheckPages( true );
+
+ m_bActionInProgress = false;
+}
+
+SwPageFrame* SwLayAction::CheckFirstVisPage( SwPageFrame *pPage )
+{
+ SwContentFrame *pCnt = pPage->FindFirstBodyContent();
+ SwContentFrame *pChk = pCnt;
+ bool bPageChgd = false;
+ while ( pCnt && pCnt->IsFollow() )
+ pCnt = pCnt->FindMaster();
+ if ( pCnt && pChk != pCnt )
+ { bPageChgd = true;
+ pPage = pCnt->FindPageFrame();
+ }
+
+ 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.
+static void 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();
+
+ while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() )
+ pPage = static_cast<SwPageFrame*>(pPage->GetNext());
+
+ IDocumentLayoutAccess& rLayoutAccess = m_pRoot->GetFormat()->getIDocumentLayoutAccess();
+ const bool bNoLoop = pPage && SwLayouter::StartLoopControl(m_pRoot->GetFormat()->GetDoc(), pPage);
+ sal_uInt16 nPercentPageNum = 0;
+
+ auto lcl_isLayoutLooping = [&]()
+ {
+ const bool bAgain = this->IsAgain();
+ if (bAgain && bNoLoop)
+ rLayoutAccess.GetLayouter()->EndLoopControl();
+ return bAgain;
+ };
+
+ while ( (pPage && !IsInterrupt()) || m_nCheckPageNum != USHRT_MAX )
+ {
+ // 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;
+ }
+
+ SwPageFrame *pTmp = pPage->GetPrev() ?
+ static_cast<SwPageFrame*>(pPage->GetPrev()) : pPage;
+ SetCheckPages( true );
+ SwFrame::CheckPageDescs( pPage, true, &pTmp );
+ SetCheckPages( false );
+ m_nCheckPageNum = USHRT_MAX;
+ pPage = pTmp;
+ continue;
+ }
+
+ if ( m_nEndPage != USHRT_MAX && pPage->GetPhyPageNum() > nPercentPageNum )
+ {
+ nPercentPageNum = pPage->GetPhyPageNum();
+ ::SetProgressState( nPercentPageNum, m_pImp->GetShell()->GetDoc()->GetDocShell());
+ }
+ m_pOptTab = nullptr;
+
+ // No Shortcut for Idle or CalcLayout
+ const bool bTakeShortcut = !IsIdle() && !IsComplete() && IsShortCut(pPage);
+
+ m_pRoot->DeleteEmptySct();
+ 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;
+ const int nLoopControlMax = 20;
+
+ while ( !IsNextCycle() && pPage->IsInvalidLayout() )
+ {
+ pPage->ValidateLayout();
+
+ if ( ++nLoopControlRuns_1 > nLoopControlMax )
+ {
+ OSL_FAIL( "LoopControl_1 in SwLayAction::InternalAction" );
+ break;
+ }
+
+ FormatLayout( pRenderContext, pPage );
+ if (lcl_isLayoutLooping()) return;
+ }
+ // change condition
+ if ( !IsNextCycle() &&
+ ( pPage->IsInvalidContent() ||
+ (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) )
+ {
+ pPage->ValidateFlyInCnt();
+ pPage->ValidateContent();
+ pPage->ValidateFlyLayout();
+ pPage->ValidateFlyContent();
+ if ( !FormatContent( pPage ) )
+ {
+ if (lcl_isLayoutLooping()) return;
+ pPage->InvalidateContent();
+ pPage->InvalidateFlyInCnt();
+ pPage->InvalidateFlyLayout();
+ pPage->InvalidateFlyContent();
+ if ( IsBrowseActionStop() )
+ m_bInterrupt = true;
+ }
+ }
+ if( bNoLoop )
+ rLayoutAccess.GetLayouter()->LoopControl( pPage );
+ }
+
+ unlockPositionOfObjects( pPage );
+ }
+
+ // 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 );
+
+ if ( m_nPreInvaPage != USHRT_MAX )
+ {
+ if( !IsComplete() && m_nPreInvaPage + 2 < nFirstPageNum )
+ {
+ m_pImp->SetFirstVisPageInvalid();
+ SwPageFrame *pTmpPage = m_pImp->GetFirstVisPage(pRenderContext);
+ nFirstPageNum = pTmpPage->GetPhyPageNum();
+ if( m_nPreInvaPage < nFirstPageNum )
+ {
+ m_nPreInvaPage = nFirstPageNum;
+ pPage = pTmpPage;
+ }
+ }
+ while ( pPage->GetPrev() && pPage->GetPhyPageNum() > m_nPreInvaPage )
+ pPage = static_cast<SwPageFrame*>(pPage->GetPrev());
+ m_nPreInvaPage = USHRT_MAX;
+ }
+
+ while ( pPage->GetPrev() &&
+ ( static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalid() ||
+ ( static_cast<SwPageFrame*>(pPage->GetPrev())->GetSortedObjs() &&
+ static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalidFly())) &&
+ (static_cast<SwPageFrame*>(pPage->GetPrev())->GetPhyPageNum() >=
+ nFirstPageNum) )
+ {
+ pPage = static_cast<SwPageFrame*>(pPage->GetPrev());
+ }
+
+ // 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());
+ }
+ else if (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;
+ const SwRect &rVis = m_pImp->GetShell()->VisArea();
+
+ while( pPg && pPg->getFrameArea().Bottom() < rVis.Top() )
+ pPg = static_cast<SwPageFrame*>(pPg->GetNext());
+ if( pPg != pPage )
+ pPg = pPg ? static_cast<SwPageFrame*>(pPg->GetPrev()) : pPage;
+
+ // 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;
+ const int 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 )
+ {
+ OSL_FAIL( "LoopControl_3 in Interrupt formatting in SwLayAction::InternalAction" );
+ break;
+ }
+
+ FormatLayout( pRenderContext, pPg );
+ if (lcl_isLayoutLooping()) return;
+ }
+
+ if ( mbFormatContentOnInterrupt &&
+ ( pPg->IsInvalidContent() ||
+ ( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) )
+ {
+ pPg->ValidateFlyInCnt();
+ pPg->ValidateContent();
+ pPg->ValidateFlyLayout();
+ pPg->ValidateFlyContent();
+
+ if ( ++nLoopControlRuns_2 > nLoopControlMax )
+ {
+ OSL_FAIL( "LoopControl_2 in Interrupt formatting in SwLayAction::InternalAction" );
+ 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());
+ }
+ // reset flag for special interrupt content formatting.
+ mbFormatContentOnInterrupt = false;
+ }
+ m_pOptTab = nullptr;
+ if( bNoLoop )
+ rLayoutAccess.GetLayouter()->EndLoopControl();
+}
+
+bool SwLayAction::TurboAction_( const SwContentFrame *pCnt )
+{
+
+ const SwPageFrame *pPage = nullptr;
+ if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() || pCnt->IsRetouche() )
+ {
+ const SwRect aOldRect( pCnt->UnionFrame( true ) );
+ const tools::Long nOldBottom = pCnt->getFrameArea().Top() + pCnt->getFramePrintArea().Bottom();
+ pCnt->Calc(m_pImp->GetShell()->GetOut());
+ if ( pCnt->getFrameArea().Bottom() < aOldRect.Bottom() )
+ pCnt->SetRetouche();
+
+ pPage = pCnt->FindPageFrame();
+ PaintContent( pCnt, pPage, aOldRect, nOldBottom );
+
+ if ( !pCnt->GetValidLineNumFlag() && pCnt->IsTextFrame() )
+ {
+ const sal_uLong 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();
+ }
+ return false;
+ }
+
+ if ( pPage->IsInvalidLayout() || (pPage->GetSortedObjs() && pPage->IsInvalidFly()) )
+ return false;
+ }
+ if ( !pPage )
+ pPage = pCnt->FindPageFrame();
+
+ // format floating screen objects at content frame.
+ if ( pCnt->IsTextFrame() &&
+ !SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pCnt),
+ *pPage, this ) )
+ {
+ return false;
+ }
+
+ if ( pPage->IsInvalidContent() )
+ return false;
+ return true;
+}
+
+bool SwLayAction::TurboAction()
+{
+ bool bRet = true;
+
+ if ( m_pRoot->GetTurbo() )
+ {
+ if ( !TurboAction_( m_pRoot->GetTurbo() ) )
+ {
+ CheckIdleEnd();
+ bRet = false;
+ }
+ m_pRoot->ResetTurbo();
+ }
+ else
+ bRet = false;
+ return bRet;
+}
+
+static bool lcl_IsInvaLay( const SwFrame *pFrame, tools::Long nBottom )
+{
+ return !pFrame->isFrameAreaDefinitionValid() ||
+ (pFrame->IsCompletePaint() && ( pFrame->getFrameArea().Top() < nBottom ) );
+}
+
+static const SwFrame *lcl_FindFirstInvaLay( const SwFrame *pFrame, tools::Long nBottom )
+{
+ OSL_ENSURE( pFrame->IsLayoutFrame(), "FindFirstInvaLay, no LayFrame" );
+
+ if (lcl_IsInvaLay(pFrame, nBottom))
+ return pFrame;
+ pFrame = static_cast<const SwLayoutFrame*>(pFrame)->Lower();
+ while ( pFrame )
+ {
+ if ( pFrame->IsLayoutFrame() )
+ {
+ if (lcl_IsInvaLay(pFrame, nBottom))
+ return pFrame;
+ const SwFrame *pTmp = lcl_FindFirstInvaLay( pFrame, nBottom );
+ if ( nullptr != pTmp )
+ return pTmp;
+ }
+ pFrame = pFrame->GetNext();
+ }
+ return nullptr;
+}
+
+static const SwFrame *lcl_FindFirstInvaContent( const SwLayoutFrame *pLay, tools::Long nBottom,
+ const SwContentFrame *pFirst )
+{
+ const SwContentFrame *pCnt = pFirst ? pFirst->GetNextContentFrame() :
+ pLay->ContainsContent();
+ while ( pCnt )
+ {
+ if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() )
+ {
+ if ( pCnt->getFrameArea().Top() <= nBottom )
+ return pCnt;
+ }
+
+ if ( pCnt->GetDrawObjs() )
+ {
+ const SwSortedObjs &rObjs = *pCnt->GetDrawObjs();
+ for (SwAnchoredObject* pObj : rObjs)
+ {
+ if ( auto pFly = pObj->DynCastFlyFrame() )
+ {
+ if ( pFly->IsFlyInContentFrame() )
+ {
+ if ( static_cast<const SwFlyInContentFrame*>(pFly)->IsInvalid() ||
+ pFly->IsCompletePaint() )
+ {
+ if ( pFly->getFrameArea().Top() <= nBottom )
+ return pFly;
+ }
+ const SwFrame *pFrame = lcl_FindFirstInvaContent( pFly, nBottom, nullptr );
+ if ( pFrame && pFrame->getFrameArea().Bottom() <= nBottom )
+ return pFrame;
+ }
+ }
+ }
+ }
+ if ( pCnt->getFrameArea().Top() > nBottom && !pCnt->IsInTab() )
+ return nullptr;
+ pCnt = pCnt->GetNextContentFrame();
+ if ( !pLay->IsAnLower( pCnt ) )
+ break;
+ }
+ return nullptr;
+}
+
+// consider drawing objects
+static const SwAnchoredObject* lcl_FindFirstInvaObj( const SwPageFrame* _pPage,
+ tools::Long _nBottom )
+{
+ OSL_ENSURE( _pPage->GetSortedObjs(), "FindFirstInvaObj, no Objs" );
+
+ for (SwAnchoredObject* pObj : *_pPage->GetSortedObjs())
+ {
+ if ( auto pFly = pObj->DynCastFlyFrame() )
+ {
+ if ( pFly->getFrameArea().Top() <= _nBottom )
+ {
+ if ( pFly->IsInvalid() || pFly->IsCompletePaint() )
+ return pFly;
+
+ const SwFrame* pTmp;
+ if ( nullptr != (pTmp = lcl_FindFirstInvaContent( pFly, _nBottom, nullptr )) &&
+ pTmp->getFrameArea().Top() <= _nBottom )
+ return pFly;
+ }
+ }
+ else if ( auto pDrawObject = dynamic_cast< const SwAnchoredDrawObject *>( pObj ) )
+ {
+ if ( !pDrawObject->IsValidPos() )
+ {
+ return pObj;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/* Returns True if the page lies directly below or right of the visible area.
+ *
+ * It's possible for things to change in such a way that the processing
+ * (of the caller!) has to continue with the predecessor of the passed page.
+ * The parameter might therefore get modified!
+ * For BrowseMode, you may even activate the ShortCut if the invalid content
+ * of the page lies below the visible area.
+ */
+bool SwLayAction::IsShortCut( SwPageFrame *&prPage )
+{
+ vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut();
+ bool bRet = false;
+ const SwViewShell *pSh = m_pRoot->GetCurrShell();
+ const bool 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() )
+ return false;
+ }
+
+ const SwRect &rVis = m_pImp->GetShell()->VisArea();
+ if ( (prPage->getFrameArea().Top() >= rVis.Bottom()) ||
+ (prPage->getFrameArea().Left()>= rVis.Right()) )
+ {
+ bRet = true;
+
+ // 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.
+
+ const SwPageFrame *p2ndPage = prPage;
+ const SwContentFrame *pContent;
+ const SwLayoutFrame* pBody = p2ndPage->FindBodyCont();
+ if( p2ndPage->IsFootnotePage() && pBody )
+ pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext());
+ pContent = pBody ? pBody->ContainsContent() : nullptr;
+ while ( p2ndPage && !pContent )
+ {
+ p2ndPage = static_cast<const SwPageFrame*>(p2ndPage->GetNext());
+ if( p2ndPage )
+ {
+ pBody = p2ndPage->FindBodyCont();
+ if( p2ndPage->IsFootnotePage() && pBody )
+ pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext());
+ pContent = pBody ? pBody->ContainsContent() : nullptr;
+ }
+ }
+ if ( pContent )
+ {
+ bool bTstCnt = true;
+ if ( bBrowse )
+ {
+ // Is the Cnt before already invisible?
+ const SwFrame *pLst = pContent;
+ if ( pLst->IsInTab() )
+ pLst = pContent->FindTabFrame();
+ if ( pLst->IsInSct() )
+ pLst = pContent->FindSctFrame();
+ pLst = pLst->FindPrev();
+ if ( pLst &&
+ (pLst->getFrameArea().Top() >= rVis.Bottom() ||
+ pLst->getFrameArea().Left()>= rVis.Right()) )
+ {
+ bTstCnt = false;
+ }
+ }
+
+ 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() )
+ return false;
+
+ bPageChg = pContent->FindPageFrame() != p2ndPage &&
+ prPage->GetPrev();
+ }
+ }
+
+ if ( !bPageChg && !pContent->isFrameAreaDefinitionValid() )
+ {
+ pContent->Calc(pRenderContext);
+ pContent->SetCompletePaint();
+ if ( IsAgain() )
+ return false;
+
+ bPageChg = pContent->FindPageFrame() != p2ndPage &&
+ prPage->GetPrev();
+ }
+
+ if ( !bPageChg && pContent->IsInTab() )
+ {
+ const SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindTabFrame();
+ if ( !pTab->isFrameAreaDefinitionValid() )
+ {
+ pTab->Calc(pRenderContext);
+ pTab->SetCompletePaint();
+ if ( IsAgain() )
+ return false;
+
+ bPageChg = pContent->FindPageFrame() != p2ndPage &&
+ prPage->GetPrev();
+ }
+ }
+
+ if ( !bPageChg && pContent->IsInSct() )
+ {
+ const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame();
+ if ( !pSct->isFrameAreaDefinitionValid() )
+ {
+ pSct->Calc(pRenderContext);
+ pSct->SetCompletePaint();
+ if ( IsAgain() )
+ return false;
+
+ bPageChg = pContent->FindPageFrame() != p2ndPage &&
+ prPage->GetPrev();
+ }
+ }
+
+ if ( bPageChg )
+ {
+ bRet = false;
+ const SwPageFrame* pTmp = pContent->FindPageFrame();
+ if ( pTmp->GetPhyPageNum() < prPage->GetPhyPageNum() &&
+ pTmp->IsInvalid() )
+ {
+ prPage = const_cast<SwPageFrame*>(pTmp);
+ }
+ else
+ {
+ prPage = static_cast<SwPageFrame*>(prPage->GetPrev());
+ }
+ }
+ // no shortcut, if at previous page
+ // an anchored object is registered, whose anchor is <pContent>.
+ else if ( prPage->GetPrev() )
+ {
+ SwSortedObjs* pObjs =
+ static_cast<SwPageFrame*>(prPage->GetPrev())->GetSortedObjs();
+ if ( pObjs )
+ {
+ for (SwAnchoredObject* pObj : *pObjs)
+ {
+ if ( pObj->GetAnchorFrameContainingAnchPos() == pContent )
+ {
+ bRet = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( !bRet && bBrowse )
+ {
+ const tools::Long nBottom = rVis.Bottom();
+ const SwAnchoredObject* pObj( nullptr );
+ if ( prPage->GetSortedObjs() &&
+ (prPage->IsInvalidFlyLayout() || prPage->IsInvalidFlyContent()) &&
+ nullptr != (pObj = lcl_FindFirstInvaObj( prPage, nBottom )) &&
+ pObj->GetObjRect().Top() <= nBottom )
+ {
+ return false;
+ }
+ const SwFrame* pFrame( nullptr );
+ if ( prPage->IsInvalidLayout() &&
+ nullptr != (pFrame = lcl_FindFirstInvaLay( prPage, nBottom )) &&
+ pFrame->getFrameArea().Top() <= nBottom )
+ {
+ return false;
+ }
+ if ( (prPage->IsInvalidContent() || prPage->IsInvalidFlyInCnt()) &&
+ nullptr != (pFrame = lcl_FindFirstInvaContent( prPage, nBottom, nullptr )) &&
+ pFrame->getFrameArea().Top() <= nBottom )
+ {
+ return false;
+ }
+ bRet = true;
+ }
+ return bRet;
+}
+
+// introduce support for vertical layout
+bool SwLayAction::FormatLayout( OutputDevice *pRenderContext, SwLayoutFrame *pLay, bool bAddRect )
+{
+ OSL_ENSURE( !IsAgain(), "Attention to the invalid page." );
+ if ( IsAgain() )
+ return false;
+
+ bool bChanged = false;
+ bool bAlreadyPainted = false;
+ // remember frame at complete paint
+ SwRect aFrameAtCompletePaint;
+
+ if ( !pLay->isFrameAreaDefinitionValid() || pLay->IsCompletePaint() )
+ {
+ if ( pLay->GetPrev() && !pLay->GetPrev()->isFrameAreaDefinitionValid() )
+ pLay->GetPrev()->SetCompletePaint();
+
+ SwRect aOldFrame( pLay->getFrameArea() );
+ SwRect aOldRect( aOldFrame );
+ if( pLay->IsPageFrame() )
+ {
+ aOldRect = static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext);
+ }
+
+ {
+ SwFrameDeleteGuard aDeleteGuard(pLay);
+ pLay->Calc(pRenderContext);
+ }
+
+ if ( aOldFrame != pLay->getFrameArea() )
+ bChanged = true;
+
+ bool bNoPaint = false;
+ if ( pLay->IsPageBodyFrame() &&
+ pLay->getFrameArea().Pos() == aOldRect.Pos() &&
+ pLay->Lower() )
+ {
+ const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell();
+ // Limitations because of headers / footers
+ if( pSh && pSh->GetViewOptions()->getBrowseMode() &&
+ !( pLay->IsCompletePaint() && pLay->FindPageFrame()->FindFootnoteCont() ) )
+ bNoPaint = true;
+ }
+
+ 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);
+ }
+
+ bool bPageInBrowseMode = pLay->IsPageFrame();
+ if( bPageInBrowseMode )
+ {
+ const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell();
+ if( !pSh || !pSh->GetViewOptions()->getBrowseMode() )
+ bPageInBrowseMode = false;
+ }
+ if( bPageInBrowseMode )
+ {
+ // NOTE: no vertical layout in online layout
+ // Is the change even visible?
+ if ( pLay->IsCompletePaint() )
+ {
+ m_pImp->GetShell()->AddPaintRect( aPaint );
+ bAddRect = false;
+ }
+ else
+ {
+ SwRegionRects aRegion( aOldRect );
+ aRegion -= aPaint;
+ for ( size_t i = 0; i < aRegion.size(); ++i )
+ m_pImp->GetShell()->AddPaintRect( aRegion[i] );
+ aRegion.ChangeOrigin( aPaint );
+ aRegion.clear();
+ aRegion.push_back( aPaint );
+ aRegion -= aOldRect;
+ for ( size_t i = 0; i < aRegion.size(); ++i )
+ m_pImp->GetShell()->AddPaintRect( aRegion[i] );
+ }
+ }
+ else
+ {
+ m_pImp->GetShell()->AddPaintRect( aPaint );
+ bAlreadyPainted = true;
+ // remember frame at complete paint
+ aFrameAtCompletePaint = pLay->getFrameArea();
+ }
+
+ // provide paint of spacing
+ // between pages (not only for in online mode).
+ if ( pLay->IsPageFrame() )
+ {
+ const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell();
+ const SwTwips nHalfDocBorder = pSh ? pSh->GetViewOptions()->GetGapBetweenPages()
+ : SwViewOption::defGapBetweenPages;
+ const bool bLeftToRightViewLayout = m_pRoot->IsLeftToRightViewLayout();
+ const bool bPrev = bLeftToRightViewLayout ? pLay->GetPrev() : pLay->GetNext();
+ const bool bNext = bLeftToRightViewLayout ? pLay->GetNext() : pLay->GetPrev();
+ SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay);
+ SwRect aPageRect( pLay->getFrameArea() );
+
+ if(pSh)
+ {
+ SwPageFrame::GetBorderAndShadowBoundRect(aPageRect, pSh,
+ pRenderContext,
+ aPageRect, pPageFrame->IsLeftShadowNeeded(), pPageFrame->IsRightShadowNeeded(),
+ pPageFrame->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT);
+ }
+
+ if ( bPrev )
+ {
+ // top
+ SwRect aSpaceToPrevPage( aPageRect );
+ aSpaceToPrevPage.Top( aSpaceToPrevPage.Top() - nHalfDocBorder );
+ aSpaceToPrevPage.Bottom( pLay->getFrameArea().Top() );
+ if(!aSpaceToPrevPage.IsEmpty())
+ m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage );
+
+ // left
+ aSpaceToPrevPage = aPageRect;
+ aSpaceToPrevPage.Left( aSpaceToPrevPage.Left() - nHalfDocBorder );
+ aSpaceToPrevPage.Right( pLay->getFrameArea().Left() );
+ if(!aSpaceToPrevPage.IsEmpty())
+ m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage );
+ }
+ if ( bNext )
+ {
+ // bottom
+ SwRect aSpaceToNextPage( aPageRect );
+ aSpaceToNextPage.Bottom( aSpaceToNextPage.Bottom() + nHalfDocBorder );
+ aSpaceToNextPage.Top( pLay->getFrameArea().Bottom() );
+ if(!aSpaceToNextPage.IsEmpty())
+ m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage );
+
+ // right
+ aSpaceToNextPage = aPageRect;
+ aSpaceToNextPage.Right( aSpaceToNextPage.Right() + nHalfDocBorder );
+ aSpaceToNextPage.Left( pLay->getFrameArea().Right() );
+ if(!aSpaceToNextPage.IsEmpty())
+ m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage );
+ }
+ }
+ }
+ pLay->ResetCompletePaint();
+ }
+
+ if ( IsPaint() && bAddRect &&
+ !pLay->GetNext() && pLay->IsRetoucheFrame() && pLay->IsRetouche() )
+ {
+ // vertical layout support
+ SwRectFnSet aRectFnSet(pLay);
+ SwRect aRect( pLay->GetUpper()->GetPaintArea() );
+ aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pLay) );
+ if ( !m_pImp->GetShell()->AddPaintRect( aRect ) )
+ pLay->ResetRetouche();
+ }
+
+ if( bAlreadyPainted )
+ bAddRect = false;
+
+ CheckWaitCursor();
+
+ if ( IsAgain() )
+ return false;
+
+ // 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
+ else if( !pLow->IsSctFrame() || static_cast<SwSectionFrame*>(pLow)->GetSection() )
+ {
+ PushFormatLayout(pLow);
+ bChanged |= FormatLayout( pRenderContext, static_cast<SwLayoutFrame*>(pLow), bAddRect );
+ PopFormatLayout();
+ }
+ }
+ else if ( m_pImp->GetShell()->IsPaintLocked() )
+ // Shortcut to minimize the cycles. With Lock, the
+ // paint is coming either way (primarily for browse)
+ pLow->OptCalc();
+
+ if ( IsAgain() )
+ return false;
+ 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 ( bAlreadyPainted &&
+ ( aBoundRect.Width() > aFrameAtCompletePaint.Width() ||
+ aBoundRect.Height() > aFrameAtCompletePaint.Height() )
+ )
+ {
+ m_pImp->GetShell()->AddPaintRect( aBoundRect );
+ }
+ return bChanged || bTabChanged;
+}
+
+void SwLayAction::FormatLayoutFly( SwFlyFrame* pFly )
+{
+ vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut();
+ OSL_ENSURE( !IsAgain(), "Attention to the invalid page." );
+ if ( IsAgain() )
+ return;
+
+ bool bChanged = false;
+ bool bAddRect = true;
+
+ if ( !pFly->isFrameAreaDefinitionValid() || pFly->IsCompletePaint() || pFly->IsInvalid() )
+ {
+ // The Frame has changed, now it's getting formatted.
+ const SwRect aOldRect( pFly->getFrameArea() );
+ pFly->Calc(pRenderContext);
+ bChanged = aOldRect != pFly->getFrameArea();
+
+ if ( IsPaint() && (pFly->IsCompletePaint() || bChanged) &&
+ pFly->getFrameArea().Top() > 0 && pFly->getFrameArea().Left() > 0 )
+ m_pImp->GetShell()->AddPaintRect( pFly->getFrameArea() );
+
+ if ( bChanged )
+ pFly->Invalidate();
+ else
+ pFly->Validate();
+
+ bAddRect = false;
+ pFly->ResetCompletePaint();
+ }
+
+ if ( IsAgain() )
+ return;
+
+ // Now, deal with the lowers that are LayoutFrames
+ SwFrame *pLow = pFly->Lower();
+ while ( pLow )
+ {
+ if ( pLow->IsLayoutFrame() )
+ {
+ if ( pLow->IsTabFrame() )
+ FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect );
+ else
+ FormatLayout( m_pImp->GetShell()->GetOut(), static_cast<SwLayoutFrame*>(pLow), bAddRect );
+ }
+ pLow = pLow->GetNext();
+ }
+}
+
+// Implement vertical layout support
+bool SwLayAction::FormatLayoutTab( SwTabFrame *pTab, bool bAddRect )
+{
+ OSL_ENSURE( !IsAgain(), "8-) Attention to the invalid page." );
+ if ( IsAgain() || !pTab->Lower() )
+ return false;
+
+ vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut();
+ IDocumentTimerAccess& rTimerAccess = m_pRoot->GetFormat()->getIDocumentTimerAccess();
+ rTimerAccess.BlockIdling();
+
+ bool bChanged = false;
+ bool bPainted = false;
+
+ const SwPageFrame *pOldPage = pTab->FindPageFrame();
+
+ // vertical layout support
+ SwRectFnSet aRectFnSet(pTab);
+
+ if ( !pTab->isFrameAreaDefinitionValid() || pTab->IsCompletePaint() || pTab->IsComplete() )
+ {
+ if ( pTab->GetPrev() && !pTab->GetPrev()->isFrameAreaDefinitionValid() )
+ {
+ pTab->GetPrev()->SetCompletePaint();
+ }
+
+ const SwRect aOldRect( pTab->getFrameArea() );
+ pTab->SetLowersFormatted( false );
+ pTab->Calc(pRenderContext);
+ if ( aOldRect != pTab->getFrameArea() )
+ {
+ bChanged = true;
+ }
+ const SwRect aPaintFrame = pTab->GetPaintArea();
+
+ if ( IsPaint() && bAddRect )
+ {
+ // add condition <pTab->getFrameArea().HasArea()>
+ if ( !pTab->IsCompletePaint() &&
+ pTab->IsComplete() &&
+ ( pTab->getFrameArea().SSize() != pTab->getFramePrintArea().SSize() ||
+ // vertical layout support
+ aRectFnSet.GetLeftMargin(*pTab) ) &&
+ pTab->getFrameArea().HasArea()
+ )
+ {
+ // re-implement calculation of margin rectangles.
+ SwRect aMarginRect;
+
+ SwTwips nLeftMargin = aRectFnSet.GetLeftMargin(*pTab);
+ if ( nLeftMargin > 0)
+ {
+ aMarginRect = pTab->getFrameArea();
+ aRectFnSet.SetWidth( aMarginRect, nLeftMargin );
+ m_pImp->GetShell()->AddPaintRect( aMarginRect );
+ }
+
+ if ( aRectFnSet.GetRightMargin(*pTab) > 0)
+ {
+ aMarginRect = pTab->getFrameArea();
+ aRectFnSet.SetLeft( aMarginRect, aRectFnSet.GetPrtRight(*pTab) );
+ m_pImp->GetShell()->AddPaintRect( aMarginRect );
+ }
+
+ SwTwips nTopMargin = aRectFnSet.GetTopMargin(*pTab);
+ if ( nTopMargin > 0)
+ {
+ aMarginRect = pTab->getFrameArea();
+ aRectFnSet.SetHeight( aMarginRect, nTopMargin );
+ m_pImp->GetShell()->AddPaintRect( aMarginRect );
+ }
+
+ if ( aRectFnSet.GetBottomMargin(*pTab) > 0)
+ {
+ aMarginRect = pTab->getFrameArea();
+ aRectFnSet.SetTop( aMarginRect, aRectFnSet.GetPrtBottom(*pTab) );
+ m_pImp->GetShell()->AddPaintRect( aMarginRect );
+ }
+ }
+ else if ( pTab->IsCompletePaint() )
+ {
+ m_pImp->GetShell()->AddPaintRect( aPaintFrame );
+ bAddRect = false;
+ bPainted = true;
+ }
+
+ if ( pTab->IsRetouche() && !pTab->GetNext() )
+ {
+ SwRect aRect( pTab->GetUpper()->GetPaintArea() );
+ // vertical layout support
+ aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) );
+ if ( !m_pImp->GetShell()->AddPaintRect( aRect ) )
+ pTab->ResetRetouche();
+ }
+ }
+ else
+ bAddRect = false;
+
+ 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();
+ }
+
+ CheckWaitCursor();
+
+ rTimerAccess.UnblockIdling();
+
+ // Ugly shortcut!
+ if ( pTab->IsLowersFormatted() &&
+ (bPainted || !m_pImp->GetShell()->VisArea().Overlaps( pTab->getFrameArea())) )
+ return false;
+
+ // Now, deal with the lowers
+ if ( IsAgain() )
+ return false;
+
+ // 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() )
+ {
+ FlowFrameJoinLockGuard tabG(pTab); // tdf#124675 prevent Join() if pTab becomes empty
+ 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() )
+ return false;
+ 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 (auto const* pObjs = pPage->GetSortedObjs())
+ {
+ std::vector<std::pair<SwAnchoredObject*, SwPageFrame*>> moved;
+ for (auto const 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 (auto const& [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);
+ }
+ if (!moved.empty())
+ {
+ pPage->InvalidateFlyLayout();
+ if (auto *const pContent = pPage->FindLastBodyContent())
+ {
+ pContent->InvalidateSize();
+ }
+ }
+ }
+ });
+
+ const SwContentFrame *pContent = pPage->ContainsContent();
+ const SwViewShell *pSh = m_pRoot->GetCurrShell();
+ const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode();
+
+ while ( pContent && pPage->IsAnLower( pContent ) )
+ {
+ // If the content didn't change, we can use a few shortcuts.
+ const bool bFull = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint() ||
+ pContent->IsRetouche() || pContent->GetDrawObjs();
+ if ( bFull )
+ {
+ // We do this so we don't have to search later on.
+ const bool 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);
+
+ const SwLayoutFrame*pOldUpper = pContent->GetUpper();
+ const SwTabFrame *pTab = pContent->FindTabFrame();
+ const bool bInValid = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint();
+ const bool bOldPaint = IsPaint();
+ m_bPaint = bOldPaint && !(pTab && pTab == m_pOptTab);
+ FormatContent_( pContent, pPage );
+ // reset <bPaint> before format objects
+ m_bPaint = bOldPaint;
+
+ // 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 ) )
+ {
+ return false;
+ }
+
+ if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() )
+ {
+ const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines();
+ const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines();
+ if ( IsPaintExtraData() && IsPaint() &&
+ nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() )
+ m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() );
+ }
+
+ if ( IsAgain() )
+ return false;
+
+ // 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 )
+ )
+ return false;
+ }
+ 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 )
+ {
+ return false;
+ }
+ }
+ }
+ // 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");
+ return false;
+ }
+
+ if ( !pContentPrev->isFrameAreaDefinitionValid() && pPage->IsAnLower( pContentPrev ) )
+ {
+ pPage->InvalidateContent();
+ }
+
+ if ( pOldUpper != pContent->GetUpper() &&
+ pPage->GetPhyPageNum() < pContent->FindPageFrame()->GetPhyPageNum() )
+ {
+ pContent = pContentPrev;
+ bSetContent = false;
+ }
+ }
+ if ( bSetContent )
+ {
+ if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() &&
+ pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom())
+ {
+ const tools::Long nBottom = m_pImp->GetShell()->VisArea().Bottom();
+ const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage,
+ nBottom, pContent );
+ if ( !pTmp )
+ {
+ if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) ||
+ !lcl_FindFirstInvaObj( pPage, nBottom )) &&
+ (!pPage->IsInvalidLayout() ||
+ !lcl_FindFirstInvaLay( pPage, nBottom )))
+ SetBrowseActionStop( true );
+ // consider interrupt formatting.
+ if ( !mbFormatContentOnInterrupt )
+ {
+ return false;
+ }
+ }
+ }
+ pContent = bNxtCnt ? pContentNext : pContent->GetNextContentFrame();
+ }
+
+ if (IsReschedule())
+ {
+ ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell());
+ }
+ }
+ else
+ {
+ if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() )
+ {
+ const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines();
+ const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines();
+ if ( IsPaintExtraData() && IsPaint() &&
+ nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() )
+ m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() );
+ }
+
+ // Do this if the frame has been formatted before.
+ if ( pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() &&
+ IsPaint() )
+ PaintContent( pContent, pPage, pContent->getFrameArea(), pContent->getFrameArea().Bottom());
+ if ( IsIdle() )
+ {
+ CheckIdleEnd();
+ // consider interrupt formatting.
+ if ( IsInterrupt() && !mbFormatContentOnInterrupt )
+ return false;
+ }
+ if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() &&
+ pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom())
+ {
+ const tools::Long nBottom = m_pImp->GetShell()->VisArea().Bottom();
+ const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage,
+ nBottom, pContent );
+ if ( !pTmp )
+ {
+ if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) ||
+ !lcl_FindFirstInvaObj( pPage, nBottom )) &&
+ (!pPage->IsInvalidLayout() ||
+ !lcl_FindFirstInvaLay( pPage, nBottom )))
+ SetBrowseActionStop( true );
+ // consider interrupt formatting.
+ if ( !mbFormatContentOnInterrupt )
+ {
+ return false;
+ }
+ }
+ }
+ pContent = pContent->GetNextContentFrame();
+ }
+ }
+ CheckWaitCursor();
+ // consider interrupt formatting.
+ return !IsInterrupt() || mbFormatContentOnInterrupt;
+}
+
+void SwLayAction::FormatContent_( const SwContentFrame *pContent, const SwPageFrame *pPage )
+{
+ // We probably only ended up here because the Content holds DrawObjects.
+ const bool bDrawObjsOnly = pContent->isFrameAreaDefinitionValid() && !pContent->IsCompletePaint() && !pContent->IsRetouche();
+ SwRectFnSet aRectFnSet(pContent);
+ if ( !bDrawObjsOnly && IsPaint() )
+ {
+ const SwRect aOldRect( pContent->UnionFrame() );
+ const tools::Long nOldBottom = aRectFnSet.GetPrtBottom(*pContent);
+ pContent->OptCalc();
+ if( IsAgain() )
+ return;
+ if( aRectFnSet.YDiff( aRectFnSet.GetBottom(pContent->getFrameArea()),
+ aRectFnSet.GetBottom(aOldRect) ) < 0 )
+ {
+ pContent->SetRetouche();
+ }
+ PaintContent( pContent, pContent->FindPageFrame(), aOldRect, nOldBottom);
+ }
+ else
+ {
+ if ( IsPaint() && pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() )
+ PaintContent( pContent, pPage, pContent->getFrameArea(),
+ aRectFnSet.GetBottom(pContent->getFrameArea()) );
+ pContent->OptCalc();
+ }
+}
+
+void SwLayAction::FormatFlyContent( const SwFlyFrame *pFly )
+{
+ const SwContentFrame *pContent = pFly->ContainsContent();
+
+ 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;
+ }
+
+ if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() )
+ {
+ const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines();
+ const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines();
+ if ( IsPaintExtraData() && IsPaint() &&
+ nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() )
+ m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() );
+ }
+
+ if ( IsAgain() )
+ return;
+
+ // If there's input, we interrupt processing.
+ if ( !pFly->IsFlyInContentFrame() )
+ {
+ CheckIdleEnd();
+ // consider interrupt formatting.
+ if ( IsInterrupt() && !mbFormatContentOnInterrupt )
+ return;
+ }
+ pContent = pContent->GetNextContentFrame();
+ }
+ CheckWaitCursor();
+}
+
+bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
+{
+ OSL_ENSURE( pCnt->IsTextFrame(), "NoText neighbour of Text" );
+ // robust against misuse by e.g. #i52542#
+ if( !pCnt->IsTextFrame() )
+ return false;
+
+ SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pCnt));
+ // sw_redlinehide: spell check only the nodes with visible content?
+ SwTextNode* pTextNode = const_cast<SwTextNode*>(pTextFrame->GetTextNodeForParaProps());
+
+ bool bProcess = false;
+ for (size_t i = 0; pTextNode; )
+ {
+ switch ( eJob )
+ {
+ case ONLINE_SPELLING :
+ bProcess = pTextNode->IsWrongDirty(); break;
+ case AUTOCOMPLETE_WORDS :
+ bProcess = pTextNode->IsAutoCompleteWordDirty(); break;
+ case WORD_COUNT :
+ bProcess = pTextNode->IsWordCountDirty(); break;
+ case SMART_TAGS :
+ bProcess = pTextNode->IsSmartTagDirty(); break;
+ }
+ if (bProcess)
+ {
+ break;
+ }
+ if (sw::MergedPara const* pMerged = pTextFrame->GetMergedPara())
+ {
+ while (true)
+ {
+ ++i;
+ if (i < pMerged->extents.size())
+ {
+ if (pMerged->extents[i].pNode != pTextNode)
+ {
+ pTextNode = pMerged->extents[i].pNode;
+ break;
+ }
+ }
+ else
+ {
+ pTextNode = nullptr;
+ break;
+ }
+ }
+ }
+ else
+ pTextNode = nullptr;
+ }
+
+ if( bProcess )
+ {
+ assert(pTextNode);
+ SwViewShell *pSh = m_pImp->GetShell();
+ if( COMPLETE_STRING == m_nTextPos )
+ {
+ --m_nTextPos;
+ if( auto pCursorShell = dynamic_cast<SwCursorShell *>( pSh ) )
+ if( !pCursorShell->IsTableMode() )
+ {
+ SwPaM *pCursor = pCursorShell->GetCursor();
+ if( !pCursor->HasMark() && !pCursor->IsMultiSelection() )
+ {
+ m_pContentNode = pCursor->GetContentNode();
+ m_nTextPos = pCursor->GetPoint()->nContent.GetIndex();
+ }
+ }
+ }
+ sal_Int32 const nPos((m_pContentNode && pTextNode == m_pContentNode)
+ ? m_nTextPos
+ : COMPLETE_STRING);
+
+ switch ( eJob )
+ {
+ case ONLINE_SPELLING :
+ {
+ SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->AutoSpell_(*pTextNode, nPos) );
+ // PENDING should stop idle spell checking
+ m_bPageValid = m_bPageValid && (SwTextNode::WrongState::TODO != pTextNode->GetWrongDirty());
+ if ( aRepaint.HasArea() )
+ m_pImp->GetShell()->InvalidateWindows( aRepaint );
+ if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
+ return true;
+ break;
+ }
+ case 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)))
+ return true;
+ break;
+ case WORD_COUNT :
+ {
+ const sal_Int32 nEnd = pTextNode->GetText().getLength();
+ SwDocStat aStat;
+ pTextNode->CountWords( aStat, 0, nEnd );
+ if ( Application::AnyInput() )
+ return true;
+ break;
+ }
+ case 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)))
+ return true;
+ 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 ) )
+ return true;
+ }
+ pC = pC->GetNextContentFrame();
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool SwLayIdle::DoIdleJob( IdleJobType eJob, bool bVisAreaOnly )
+{
+ // Spellcheck all contents of the pages. Either only the
+ // visible ones or all of them.
+ const SwViewShell* pViewShell = m_pImp->GetShell();
+ const SwViewOption* pViewOptions = pViewShell->GetViewOptions();
+ const SwDoc* pDoc = pViewShell->GetDoc();
+
+ switch ( eJob )
+ {
+ case ONLINE_SPELLING :
+ if( !pViewOptions->IsOnlineSpell() )
+ return false;
+ break;
+ case AUTOCOMPLETE_WORDS :
+ if( !SwViewOption::IsAutoCompleteWords() ||
+ SwDoc::GetAutoCompleteWords().IsLockWordLstLocked())
+ return false;
+ break;
+ case WORD_COUNT :
+ if ( !pViewShell->getIDocumentStatistics().GetDocStat().bModified )
+ return false;
+ break;
+ case SMART_TAGS :
+ if ( pDoc->GetDocShell()->IsHelpDocument() ||
+ pDoc->isXForms() ||
+ !SwSmartTagMgr::Get().IsSmartTagsEnabled() )
+ return false;
+ break;
+ default: OSL_FAIL( "Unknown idle job type" );
+ }
+
+ SwPageFrame *pPage;
+ if ( bVisAreaOnly )
+ pPage = m_pImp->GetFirstVisPage(pViewShell->GetOut());
+ else
+ pPage = static_cast<SwPageFrame*>(m_pRoot->Lower());
+
+ m_pContentNode = nullptr;
+ m_nTextPos = COMPLETE_STRING;
+
+ while ( pPage )
+ {
+ m_bPageValid = true;
+ const SwContentFrame *pCnt = pPage->ContainsContent();
+ while( pCnt && pPage->IsAnLower( pCnt ) )
+ {
+ if ( DoIdleJob_( pCnt, eJob ) )
+ {
+ SAL_INFO("sw.idle", "DoIdleJob " << eJob << " interrupted on page " << pPage->GetPhyPageNum());
+ return true;
+ }
+ pCnt = pCnt->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 " << eJob << " interrupted on page " << pPage->GetPhyPageNum());
+ return true;
+ }
+ }
+ pC = pC->GetNextContentFrame();
+ }
+ }
+ }
+ }
+
+ if( m_bPageValid )
+ {
+ switch ( eJob )
+ {
+ case ONLINE_SPELLING : pPage->ValidateSpelling(); break;
+ case AUTOCOMPLETE_WORDS : pPage->ValidateAutoCompleteWords(); break;
+ case WORD_COUNT : pPage->ValidateWordCount(); break;
+ case SMART_TAGS : pPage->ValidateSmartTags(); break;
+ }
+ }
+
+ pPage = static_cast<SwPageFrame*>(pPage->GetNext());
+ if ( pPage && bVisAreaOnly &&
+ !pPage->getFrameArea().Overlaps( m_pImp->GetShell()->VisArea()))
+ break;
+ }
+ return false;
+}
+
+#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
+
+SwLayIdle::SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pI ) :
+ m_pRoot( pRt ),
+ m_pImp( pI )
+#ifdef DBG_UTIL
+ , m_bIndicator( false )
+#endif
+{
+ SAL_INFO("sw.idle", "SwLayIdle() entry");
+
+ m_pImp->m_pIdleAct = this;
+
+ SHOW_IDLE( COL_LIGHTRED );
+
+ m_pImp->GetShell()->EnableSmooth( false );
+
+ // First, spellcheck the visible area. Only if there's nothing
+ // to do there, we trigger the IdleFormat.
+ if ( !DoIdleJob( SMART_TAGS, true ) &&
+ !DoIdleJob( ONLINE_SPELLING, true ) &&
+ !DoIdleJob( AUTOCOMPLETE_WORDS, true ) )
+ {
+ // 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 );
+ }
+
+ bool bInterrupt(false);
+ {
+ SwLayAction aAction( m_pRoot, m_pImp );
+ aAction.SetInputType( VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER) );
+ aAction.SetIdle( true );
+ aAction.SetWaitAllowed( false );
+ aAction.Action(m_pImp->GetShell()->GetOut());
+ 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;
+
+ if ( rSh.Imp()->HasPaintRegion() )
+ bActions = true;
+ else
+ {
+ SwRect aTmp( rSh.VisArea() );
+ rSh.UISizeNotify();
+
+ // 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() )
+ {
+ pViewImp->DeletePaintRegion();
+
+ // Cause a repaint with virtual device.
+ rSh.LockPaint();
+ 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( WORD_COUNT, false ) )
+ if ( !DoIdleJob( SMART_TAGS, false ) )
+ if ( !DoIdleJob( ONLINE_SPELLING, false ) )
+ DoIdleJob( AUTOCOMPLETE_WORDS, false );
+ }
+
+ bool bInValid = false;
+ const SwViewOption& rVOpt = *m_pImp->GetShell()->GetViewOptions();
+ const SwViewShell* pViewShell = m_pImp->GetShell();
+ // See conditions in DoIdleJob()
+ const bool bSpell = rVOpt.IsOnlineSpell();
+ const bool bACmplWrd = SwViewOption::IsAutoCompleteWords();
+ const bool bWordCount = pViewShell->getIDocumentStatistics().GetDocStat().bModified;
+ const bool bSmartTags = !pViewShell->GetDoc()->GetDocShell()->IsHelpDocument() &&
+ !pViewShell->GetDoc()->isXForms() &&
+ SwSmartTagMgr::Get().IsSmartTagsEnabled();
+
+ SwPageFrame *pPg = static_cast<SwPageFrame*>(m_pRoot->Lower());
+ do
+ {
+ bInValid = pPg->IsInvalidContent() || pPg->IsInvalidLayout() ||
+ pPg->IsInvalidFlyContent() || pPg->IsInvalidFlyLayout() ||
+ pPg->IsInvalidFlyInCnt() ||
+ (bSpell && pPg->IsInvalidSpelling()) ||
+ (bACmplWrd && pPg->IsInvalidAutoCompleteWords()) ||
+ (bWordCount && pPg->IsInvalidWordCount()) ||
+ (bSmartTags && pPg->IsInvalidSmartTags());
+
+ pPg = static_cast<SwPageFrame*>(pPg->GetNext());
+
+ } while ( pPg && !bInValid );
+
+ if ( !bInValid )
+ {
+ m_pRoot->ResetIdleFormat();
+ SfxObjectShell* pDocShell = m_pImp->GetShell()->GetDoc()->GetDocShell();
+ pDocShell->Broadcast( SfxEventHint( SfxEventHintId::SwEventLayoutFinished, SwDocShell::GetEventName(STR_SW_EVENT_LAYOUT_FINISHED), pDocShell ) );
+ }
+ }
+
+ m_pImp->GetShell()->EnableSmooth( true );
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if( m_pImp->IsAccessible() )
+ m_pImp->FireAccessibleEvents();
+#endif
+
+ SAL_INFO("sw.idle", "SwLayIdle() return");
+
+#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
+}
+
+SwLayIdle::~SwLayIdle()
+{
+ m_pImp->m_pIdleAct = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */