From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sw/source/core/layout/trvlfrm.cxx | 2654 +++++++++++++++++++++++++++++++++++++ 1 file changed, 2654 insertions(+) create mode 100644 sw/source/core/layout/trvlfrm.cxx (limited to 'sw/source/core/layout/trvlfrm.cxx') diff --git a/sw/source/core/layout/trvlfrm.cxx b/sw/source/core/layout/trvlfrm.cxx new file mode 100644 index 000000000..375b9fe34 --- /dev/null +++ b/sw/source/core/layout/trvlfrm.cxx @@ -0,0 +1,2654 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + bool lcl_GetModelPositionForViewPoint_Objects( const SwPageFrame* pPageFrame, bool bSearchBackground, + SwPosition *pPos, Point const & rPoint, SwCursorMoveState* pCMS ) + { + bool bRet = false; + Point aPoint( rPoint ); + SwOrderIter aIter( pPageFrame ); + aIter.Top(); + while ( aIter() ) + { + const SwVirtFlyDrawObj* pObj = + static_cast(aIter()); + const SwAnchoredObject* pAnchoredObj = GetUserCall( aIter() )->GetAnchoredObj( aIter() ); + const SwFormatSurround& rSurround = pAnchoredObj->GetFrameFormat().GetSurround(); + const SvxOpaqueItem& rOpaque = pAnchoredObj->GetFrameFormat().GetOpaque(); + bool bInBackground = ( rSurround.GetSurround() == css::text::WrapTextMode_THROUGH ) && !rOpaque.GetValue(); + + bool bBackgroundMatches = bInBackground == bSearchBackground; + + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && bBackgroundMatches && + ( ( pCMS && pCMS->m_bSetInReadOnly ) || + !pFly->IsProtected() ) && + pFly->GetModelPositionForViewPoint( pPos, aPoint, pCMS ) ) + { + bRet = true; + break; + } + + if ( pCMS && pCMS->m_bStop ) + return false; + aIter.Prev(); + } + return bRet; + } + + double lcl_getDistance( const SwRect& rRect, const Point& rPoint ) + { + double nDist = 0.0; + + // If the point is inside the rectangle, then distance is 0 + // Otherwise, compute the distance to the center of the rectangle. + if ( !rRect.Contains( rPoint ) ) + { + tools::Line aLine( rPoint, rRect.Center( ) ); + nDist = aLine.GetLength( ); + } + + return nDist; + } +} + +namespace { + +//For SwFlyFrame::GetModelPositionForViewPoint +class SwCursorOszControl +{ +public: + // So the compiler can initialize the class already. No DTOR and member + // as public members + const SwFlyFrame* m_pEntry; + const SwFlyFrame* m_pStack1; + const SwFlyFrame* m_pStack2; + + bool ChkOsz( const SwFlyFrame *pFly ) + { + bool bRet = true; + if (pFly != m_pStack1 && pFly != m_pStack2) + { + m_pStack1 = m_pStack2; + m_pStack2 = pFly; + bRet = false; + } + return bRet; + } + + void Entry( const SwFlyFrame *pFly ) + { + if (!m_pEntry) + m_pEntry = m_pStack1 = pFly; + } + + void Exit( const SwFlyFrame *pFly ) + { + if (pFly == m_pEntry) + m_pEntry = m_pStack1 = m_pStack2 = nullptr; + } +}; + +} + +static SwCursorOszControl g_OszCtrl = { nullptr, nullptr, nullptr }; + +/** Searches the ContentFrame owning the PrtArea containing the point. */ +bool SwLayoutFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + bool bRet = false; + const SwFrame *pFrame = Lower(); + while ( !bRet && pFrame ) + { + pFrame->Calc(pRenderContext); + + // #i43742# New function + const bool bContentCheck = pFrame->IsTextFrame() && pCMS && pCMS->m_bContentCheck; + const SwRect aPaintRect( bContentCheck ? + pFrame->UnionFrame() : + pFrame->GetPaintArea() ); + + if ( aPaintRect.Contains( rPoint ) && + ( bContentCheck || pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ) ) ) + bRet = true; + else + pFrame = pFrame->GetNext(); + if ( pCMS && pCMS->m_bStop ) + return false; + } + return bRet; +} + +/** Searches the page containing the searched point. */ + +bool SwPageFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + Point aPoint( rPoint ); + + // check, if we have to adjust the point + if ( !getFrameArea().Contains( aPoint ) ) + { + aPoint.setX( std::max( aPoint.X(), getFrameArea().Left() ) ); + aPoint.setX( std::min( aPoint.X(), getFrameArea().Right() ) ); + aPoint.setY( std::max( aPoint.Y(), getFrameArea().Top() ) ); + aPoint.setY( std::min( aPoint.Y(), getFrameArea().Bottom() ) ); + } + + bool bRet = false; + //Could it be a free flying one? + //If his content should be protected, we can't set the Cursor in it, thus + //all changes should be impossible. + if ( GetSortedObjs() ) + { + bRet = lcl_GetModelPositionForViewPoint_Objects( this, false, pPos, rPoint, pCMS ); + } + + if ( !bRet ) + { + SwPosition aBackPos( *pPos ); + SwPosition aTextPos( *pPos ); + + //We fix the StartPoint if no Content below the page 'answers' and then + //start all over again one page before the current one. + //However we can't use Flys in such a case. + if (!SwLayoutFrame::GetModelPositionForViewPoint(&aTextPos, aPoint, pCMS)) + { + if ( pCMS && (pCMS->m_bStop || pCMS->m_bExactOnly) ) + { + pCMS->m_bStop = true; + return false; + } + + const SwContentFrame *pCnt = GetContentPos( aPoint, false, false, pCMS, false ); + // GetContentPos may have modified pCMS + if ( pCMS && pCMS->m_bStop ) + return false; + + bool bTextRet = false; + + OSL_ENSURE( pCnt, "Cursor is gone to a Black hole" ); + if( pCMS && pCMS->m_pFill && pCnt->IsTextFrame() ) + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, rPoint, pCMS ); + else + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, aPoint, pCMS ); + + if ( !bTextRet ) + { + // Set point to pCnt, delete mark + // this may happen, if pCnt is hidden + if (pCnt->IsTextFrame()) + { + aTextPos = static_cast(pCnt)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + assert(pCnt->IsNoTextFrame()); + aTextPos = SwPosition( *static_cast(pCnt)->GetNode() ); + } + } + } + + SwContentNode* pContentNode = aTextPos.nNode.GetNode().GetContentNode(); + bool bConsiderBackground = true; + // If the text position is a clickable field, then that should have priority. + if (pContentNode && pContentNode->IsTextNode()) + { + SwTextNode* pTextNd = pContentNode->GetTextNode(); + SwTextAttr* pTextAttr = pTextNd->GetTextAttrForCharAt(aTextPos.nContent.GetIndex(), RES_TXTATR_FIELD); + if (pTextAttr) + { + const SwField* pField = pTextAttr->GetFormatField().GetField(); + if (pField->IsClickable()) + bConsiderBackground = false; + } + } + + bool bBackRet = false; + // Check objects in the background if nothing else matched + if ( GetSortedObjs() ) + { + bBackRet = lcl_GetModelPositionForViewPoint_Objects( this, true, &aBackPos, rPoint, pCMS ); + } + + if (bConsiderBackground && bTestBackground && bBackRet) + { + (*pPos) = aBackPos; + } + else if (!bBackRet) + { + (*pPos) = aTextPos; + } + else // bBackRet && !(bConsiderBackground && bTestBackground) + { + /* In order to provide a selection as accurate as possible when we have both + * text and background object, then we compute the distance between both + * would-be positions and the click point. The shortest distance wins. + */ + double nTextDistance = 0; + bool bValidTextDistance = false; + if (pContentNode) + { + SwContentFrame* pTextFrame = pContentNode->getLayoutFrame( getRootFrame( ) ); + + // try this again but prefer the "previous" position + SwCursorMoveState aMoveState; + SwCursorMoveState *const pState(pCMS ? pCMS : &aMoveState); + comphelper::FlagRestorationGuard g( + pState->m_bPosMatchesBounds, true); + SwPosition prevTextPos(*pPos); + if (SwLayoutFrame::GetModelPositionForViewPoint(&prevTextPos, aPoint, pState)) + { + SwRect aTextRect; + pTextFrame->GetCharRect(aTextRect, prevTextPos); + + if (prevTextPos.nContent < pContentNode->Len()) + { + // aRextRect is just a line on the left edge of the + // previous character; to get a better measure from + // lcl_getDistance, extend that to a rectangle over + // the entire character. + SwPosition const nextTextPos(prevTextPos.nNode, + SwIndex(prevTextPos.nContent, +1)); + SwRect nextTextRect; + pTextFrame->GetCharRect(nextTextRect, nextTextPos); + SwRectFnSet aRectFnSet(pTextFrame); + if (aRectFnSet.GetTop(aTextRect) == + aRectFnSet.GetTop(nextTextRect)) // same line? + { + // need to handle mixed RTL/LTR portions somehow + if (aRectFnSet.GetLeft(aTextRect) < + aRectFnSet.GetLeft(nextTextRect)) + { + aRectFnSet.SetRight( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + else // RTL + { + aRectFnSet.SetLeft( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + } + } + + nTextDistance = lcl_getDistance(aTextRect, rPoint); + bValidTextDistance = true; + } + } + + double nBackDistance = 0; + bool bValidBackDistance = false; + SwContentNode* pBackNd = aBackPos.nNode.GetNode( ).GetContentNode( ); + if ( pBackNd && bConsiderBackground) + { + // FIXME There are still cases were we don't have the proper node here. + SwContentFrame* pBackFrame = pBackNd->getLayoutFrame( getRootFrame( ) ); + if (pBackFrame) + { + SwRect rBackRect; + pBackFrame->GetCharRect( rBackRect, aBackPos ); + + nBackDistance = lcl_getDistance( rBackRect, rPoint ); + bValidBackDistance = true; + } + } + + if ( bValidTextDistance && bValidBackDistance && basegfx::fTools::more( nTextDistance, nBackDistance ) ) + { + (*pPos) = aBackPos; + } + else + { + (*pPos) = aTextPos; + } + } + } + + rPoint = aPoint; + return true; +} + +bool SwLayoutFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + if( rRect.Overlaps(GetPaintArea()) ) + { + const SwFrame* pFrame = Lower(); + while( pFrame ) + { + pFrame->FillSelection( rList, rRect ); + pFrame = pFrame->GetNext(); + } + } + return false; +} + +bool SwPageFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + bool bRet = false; + if( rRect.Overlaps(GetPaintArea()) ) + { + bRet = SwLayoutFrame::FillSelection( rList, rRect ); + if( GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame(); + if( !pFly ) + continue; + if( pFly->FillSelection( rList, rRect ) ) + bRet = true; + } + } + } + return bRet; +} + +bool SwRootFrame::FillSelection( SwSelectionList& aSelList, const SwRect& rRect) const +{ + const SwFrame *pPage = Lower(); + const tools::Long nBottom = rRect.Bottom(); + while( pPage ) + { + if( pPage->getFrameArea().Top() < nBottom ) + { + if( pPage->getFrameArea().Bottom() > rRect.Top() ) + pPage->FillSelection( aSelList, rRect ); + pPage = pPage->GetNext(); + } + else + pPage = nullptr; + } + return !aSelList.isEmpty(); +} + +/** Primary passes the call to the first page. + * + * @return false, if the passed Point gets changed + */ +bool SwRootFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + const bool bOldAction = IsCallbackActionEnabled(); + const_cast(this)->SetCallbackActionEnabled( false ); + OSL_ENSURE( (Lower() && Lower()->IsPageFrame()), "No PageFrame found." ); + if( pCMS && pCMS->m_pFill ) + pCMS->m_bFillRet = false; + Point aOldPoint = rPoint; + + // search for page containing rPoint. The borders around the pages are considered + const SwPageFrame* pPage = GetPageAtPos( rPoint, nullptr, true ); + + // #i95626# + // special handling for beyond root frames area + if ( !pPage && + rPoint.X() > getFrameArea().Right() && + rPoint.Y() > getFrameArea().Bottom() ) + { + pPage = dynamic_cast(Lower()); + while ( pPage && pPage->GetNext() ) + { + pPage = dynamic_cast(pPage->GetNext()); + } + } + if ( pPage ) + { + pPage->SwPageFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS, bTestBackground ); + } + + const_cast(this)->SetCallbackActionEnabled( bOldAction ); + if( pCMS ) + { + if( pCMS->m_bStop ) + return false; + if( pCMS->m_pFill ) + return pCMS->m_bFillRet; + } + return aOldPoint == rPoint; +} + +/** + * If this is about a Content-carrying cell the Cursor will be force inserted into one of the ContentFrames + * if there are no other options. + * + * There is no entry for protected cells. + */ +bool SwCellFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + // cell frame does not necessarily have a lower (split table cell) + if ( !Lower() ) + return false; + + if ( !(pCMS && pCMS->m_bSetInReadOnly) && + GetFormat()->GetProtect().IsContentProtected() ) + return false; + + if ( pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *this ) ) + { + pCMS->m_bStop = true; + return false; + } + } + + if ( Lower() ) + { + if ( Lower()->IsLayoutFrame() ) + return SwLayoutFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + else + { + Calc(pRenderContext); + bool bRet = false; + + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().Contains( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true ); + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + } + return true; + } + } + + return false; +} + +//Problem: If two Flys have the same size and share the same position then +//they end inside each other. +//Because we recursively check if a Point doesn't randomly lie inside another +//fly which lies completely inside the current Fly we could trigger an endless +//loop with the mentioned situation above. +//Using the helper class SwCursorOszControl we prevent the recursion. During +//a recursion GetModelPositionForViewPoint picks the one which lies on top. +bool SwFlyFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + g_OszCtrl.Entry( this ); + + //If the Points lies inside the Fly, we try hard to set the Cursor inside it. + //However if the Point sits inside a Fly which is completely located inside + //the current one, we call GetModelPositionForViewPoint for it. + Calc(pRenderContext); + bool bInside = getFrameArea().Contains( rPoint ) && Lower(); + bool bRet = false; + + //If a Frame contains a graphic, but only text was requested, it basically + //won't accept the Cursor. + if ( bInside && pCMS && pCMS->m_eState == CursorMoveState::SetOnlyText && + (!Lower() || Lower()->IsNoTextFrame()) ) + bInside = false; + + const SwPageFrame *pPage = FindPageFrame(); + if ( bInside && pPage && pPage->GetSortedObjs() ) + { + SwOrderIter aIter( pPage ); + aIter.Top(); + while ( aIter() && !bRet ) + { + const SwVirtFlyDrawObj* pObj = static_cast(aIter()); + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && pFly->getFrameArea().Contains( rPoint ) && + getFrameArea().Contains( pFly->getFrameArea() ) ) + { + if (g_OszCtrl.ChkOsz(pFly)) + break; + bRet = pFly->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( bRet ) + break; + if ( pCMS && pCMS->m_bStop ) + return false; + } + aIter.Next(); + } + } + + while ( bInside && !bRet ) + { + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().Contains( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true, false, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + bRet = true; + } + } + g_OszCtrl.Exit( this ); + return bRet; +} + +/** Layout dependent cursor travelling */ +bool SwNoTextFrame::LeftMargin(SwPaM *pPam) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast(GetNode())-> + MakeStartIndex(&pPam->GetPoint()->nContent); + return true; +} + +bool SwNoTextFrame::RightMargin(SwPaM *pPam, bool) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast(GetNode())-> + MakeEndIndex(&pPam->GetPoint()->nContent); + return true; +} + +static const SwContentFrame *lcl_GetNxtCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetNextContentFrame(); +} + +static const SwContentFrame *lcl_GetPrvCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetPrevContentFrame(); +} + +typedef const SwContentFrame *(*GetNxtPrvCnt)( const SwContentFrame* ); + +/// Frame in repeated headline? +static bool lcl_IsInRepeatedHeadline( const SwFrame *pFrame, + const SwTabFrame** ppTFrame = nullptr ) +{ + const SwTabFrame *pTab = pFrame->FindTabFrame(); + if( ppTFrame ) + *ppTFrame = pTab; + return pTab && pTab->IsFollow() && pTab->IsInHeadline( *pFrame ); +} + +/// Skip protected table cells. Optionally also skip repeated headlines. +//MA 1998-01-26: Chg also skip other protected areas +//FME: Skip follow flow cells +static const SwContentFrame * lcl_MissProtectedFrames( const SwContentFrame *pCnt, + GetNxtPrvCnt fnNxtPrv, + bool bMissHeadline, + bool bInReadOnly, + bool bMissFollowFlowLine ) +{ + if ( pCnt && pCnt->IsInTab() ) + { + bool bProtect = true; + while ( pCnt && bProtect ) + { + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + if ( !pCell || + ( ( bInReadOnly || !pCell->GetFormat()->GetProtect().IsContentProtected() ) && + ( !bMissHeadline || !lcl_IsInRepeatedHeadline( pCell ) ) && + ( !bMissFollowFlowLine || !pCell->IsInFollowFlowRow() ) && + !pCell->IsCoveredCell() ) ) + bProtect = false; + else + pCnt = (*fnNxtPrv)( pCnt ); + } + } + else if ( !bInReadOnly ) + while ( pCnt && pCnt->IsProtected() ) + pCnt = (*fnNxtPrv)( pCnt ); + + return pCnt; +} + +static bool lcl_UpDown( SwPaM *pPam, const SwContentFrame *pStart, + GetNxtPrvCnt fnNxtPrv, bool bInReadOnly ) +{ + OSL_ENSURE( FrameContainsNode(*pStart, pPam->GetNode().GetIndex()), + "lcl_UpDown doesn't work for others." ); + + const SwContentFrame *pCnt = nullptr; + + //We have to cheat a little bit during a table selection: Go to the + //beginning of the cell while going up and go to the end of the cell while + //going down. + bool bTableSel = false; + if ( pStart->IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + bTableSel = true; + const SwLayoutFrame *pCell = pStart->GetUpper(); + while ( !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + // Check, if cell has a Prev/Follow cell: + const bool bFwd = ( fnNxtPrv == lcl_GetNxtCnt ); + const SwLayoutFrame* pTmpCell = bFwd ? + static_cast(pCell)->GetFollowCell() : + static_cast(pCell)->GetPreviousCell(); + + const SwContentFrame* pTmpStart = pStart; + while ( pTmpCell && nullptr != ( pTmpStart = pTmpCell->ContainsContent() ) ) + { + pCell = pTmpCell; + pTmpCell = bFwd ? + static_cast(pCell)->GetFollowCell() : + static_cast(pCell)->GetPreviousCell(); + } + const SwContentFrame *pNxt = pCnt = pTmpStart; + + while ( pCell->IsAnLower( pNxt ) ) + { + pCnt = pNxt; + pNxt = (*fnNxtPrv)( pNxt ); + } + } + + pCnt = (*fnNxtPrv)( pCnt ? pCnt : pStart ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + + const SwTabFrame *pStTab = pStart->FindTabFrame(); + const SwTabFrame *pTable = nullptr; + const bool bTab = pStTab || (pCnt && pCnt->IsInTab()); + bool bEnd = !bTab; + + const SwFrame* pVertRefFrame = pStart; + if ( bTableSel && pStTab ) + pVertRefFrame = pStTab; + SwRectFnSet aRectFnSet(pVertRefFrame); + + SwTwips nX = 0; + if ( bTab ) + { + // pStart or pCnt is inside a table. nX will be used for travelling: + SwRect aRect( pStart->getFrameArea() ); + pStart->GetCharRect( aRect, *pPam->GetPoint() ); + Point aCenter = aRect.Center(); + nX = aRectFnSet.IsVert() ? aCenter.Y() : aCenter.X(); + + pTable = pCnt ? pCnt->FindTabFrame() : nullptr; + if ( !pTable ) + pTable = pStTab; + + if ( pStTab && + !pStTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + { + const SwFrame *pCell = pStart->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "could not find the cell" ); + nX = aRectFnSet.XInc(aRectFnSet.GetLeft(pCell->getFrameArea()), + aRectFnSet.GetWidth(pCell->getFrameArea()) / 2); + + //The flow leads from one table to the next. The X-value needs to be + //corrected based on the middle of the starting cell by the amount + //of the offset of the tables. + if ( pStTab != pTable ) + { + nX += aRectFnSet.GetLeft(pTable->getFrameArea()) - + aRectFnSet.GetLeft(pStTab->getFrameArea()); + } + } + + // Restrict nX to the left and right borders of pTab: + // (is this really necessary?) + if (pTable && !pTable->GetUpper()->IsInTab()) + { + const bool bRTL = pTable->IsRightToLeft(); + const tools::Long nPrtLeft = bRTL ? + aRectFnSet.GetPrtRight(*pTable) : + aRectFnSet.GetPrtLeft(*pTable); + if (bRTL != (aRectFnSet.XDiff(nPrtLeft, nX) > 0)) + nX = nPrtLeft; + else + { + const tools::Long nPrtRight = bRTL ? + aRectFnSet.GetPrtLeft(*pTable) : + aRectFnSet.GetPrtRight(*pTable); + if (bRTL != (aRectFnSet.XDiff(nX, nPrtRight) > 0)) + nX = nPrtRight; + } + } + } + + do + { + //If I'm in the DocumentBody, I want to stay there. + if ( pStart->IsInDocBody() ) + { + while ( pCnt && (!pCnt->IsInDocBody() || + (pCnt->IsTextFrame() && static_cast(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //If I'm in the FootNoteArea, I try to reach the next FootNoteArea in + //case of necessity. + else if ( pStart->IsInFootnote() ) + { + while ( pCnt && (!pCnt->IsInFootnote() || + (pCnt->IsTextFrame() && static_cast(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //In Flys we can go ahead blindly as long as we find a Content. + else if ( pStart->IsInFly() ) + { + if ( pCnt && pCnt->IsTextFrame() && static_cast(pCnt)->IsHiddenNow() ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //Otherwise I'll just refuse to leave to current area. + else if ( pCnt ) + { + const SwFrame *pUp = pStart->GetUpper(); + while (pUp && pUp->GetUpper() && !(pUp->GetType() & FRM_HEADFOOT)) + pUp = pUp->GetUpper(); + bool bSame = false; + const SwFrame *pCntUp = pCnt->GetUpper(); + while ( pCntUp && !bSame ) + { + if ( pUp == pCntUp ) + bSame = true; + else + pCntUp = pCntUp->GetUpper(); + } + if ( !bSame ) + pCnt = nullptr; + else if (pCnt->IsTextFrame() && static_cast(pCnt)->IsHiddenNow()) // i73332 + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + if ( bTab ) + { + if ( !pCnt ) + bEnd = true; + else + { + const SwTabFrame *pTab = pCnt->FindTabFrame(); + if( !pTab ) + bEnd = true; + else + { + if ( pTab != pTable ) + { + //The flow leads from one table to the next. The X-value + //needs to be corrected by the amount of the offset of + //the tables + if ( pTable && + !pTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + nX += pTab->getFrameArea().Left() - pTable->getFrameArea().Left(); + pTable = pTab; + } + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + Point aInsideCell; + Point aInsideCnt; + if ( pCell ) + { + tools::Long nTmpTop = aRectFnSet.GetTop(pCell->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCell = Point( nTmpTop, nX ); + } + else + aInsideCell = Point( nX, nTmpTop ); + } + + tools::Long nTmpTop = aRectFnSet.GetTop(pCnt->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCnt = Point( nTmpTop, nX ); + } + else + aInsideCnt = Point( nX, nTmpTop ); + + if ( pCell && pCell->getFrameArea().Contains( aInsideCell ) ) + { + bEnd = true; + //Get the right Content out of the cell. + if ( !pCnt->getFrameArea().Contains( aInsideCnt ) ) + { + pCnt = pCell->ContainsContent(); + if ( fnNxtPrv == lcl_GetPrvCnt ) + while ( pCell->IsAnLower(pCnt->GetNextContentFrame()) ) + pCnt = pCnt->GetNextContentFrame(); + } + } + else if ( pCnt->getFrameArea().Contains( aInsideCnt ) ) + bEnd = true; + } + } + if ( !bEnd ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + } while ( !bEnd || + (pCnt && pCnt->IsTextFrame() && static_cast(pCnt)->IsHiddenNow())); + + if (pCnt == nullptr) + { + return false; + } + if (pCnt->IsTextFrame()) + { + SwTextFrame const*const pFrame(static_cast(pCnt)); + *pPam->GetPoint() = pFrame->MapViewToModelPos(TextFrameIndex( + fnNxtPrv == lcl_GetPrvCnt + ? pFrame->GetText().getLength() + : 0)); + } + else + { // set the Point on the Content-Node + assert(pCnt->IsNoTextFrame()); + SwContentNode *const pCNd = const_cast(static_cast(pCnt)->GetNode()); + pPam->GetPoint()->nNode = *pCNd; + if ( fnNxtPrv == lcl_GetPrvCnt ) + pCNd->MakeEndIndex( &pPam->GetPoint()->nContent ); + else + pCNd->MakeStartIndex( &pPam->GetPoint()->nContent ); + } + return true; +} + +bool SwContentFrame::UnitUp( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetPrvCnt, bInReadOnly ); +} + +bool SwContentFrame::UnitDown( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetNxtCnt, bInReadOnly ); +} + +/** Returns the number of the current page. + * + * If the method gets a PaM then the current page is the one in which the PaM sits. Otherwise the + * current page is the first one inside the VisibleArea. We only work on available pages! + */ +sal_uInt16 SwRootFrame::GetCurrPage( const SwPaM *pActualCursor ) const +{ + OSL_ENSURE( pActualCursor, "got no page cursor" ); + SwFrame const*const pActFrame = pActualCursor->GetPoint()->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, + pActualCursor->GetPoint()); + return pActFrame->FindPageFrame()->GetPhyPageNum(); +} + +/** Returns a PaM which sits at the beginning of the requested page. + * + * Formatting is done as far as necessary. + * The PaM sits on the last page, if the page number was chosen too big. + * + * @return Null, if the operation was not possible. + */ +sal_uInt16 SwRootFrame::SetCurrPage( SwCursor* pToSet, sal_uInt16 nPageNum ) +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + SwPageFrame *pPage = static_cast(Lower()); + bool bEnd =false; + while ( !bEnd && pPage->GetPhyPageNum() != nPageNum ) + { if ( pPage->GetNext() ) + pPage = static_cast(pPage->GetNext()); + else + { //Search the first ContentFrame and format until a new page is started + //or until the ContentFrame are all done. + const SwContentFrame *pContent = pPage->ContainsContent(); + while ( pContent && pPage->IsAnLower( pContent ) ) + { + pContent->Calc(pRenderContext); + pContent = pContent->GetNextContentFrame(); + } + //Either this is a new page or we found the last page. + if ( pPage->GetNext() ) + pPage = static_cast(pPage->GetNext()); + else + bEnd = true; + } + } + //pPage now points to the 'requested' page. Now we have to create the PaM + //on the beginning of the first ContentFrame in the body-text. + //If this is a footnote-page, the PaM will be set in the first footnote. + const SwContentFrame *pContent = pPage->ContainsContent(); + if ( pPage->IsFootnotePage() ) + while ( pContent && !pContent->IsInFootnote() ) + pContent = pContent->GetNextContentFrame(); + else + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( pContent ) + { + assert(pContent->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast(pContent)); + *pToSet->GetPoint() = pFrame->MapViewToModelPos(pFrame->GetOffset()); + + SwShellCursor* pSCursor = dynamic_cast(pToSet); + if( pSCursor ) + { + Point &rPt = pSCursor->GetPtPos(); + rPt = pContent->getFrameArea().Pos(); + rPt += pContent->getFramePrintArea().Pos(); + } + return pPage->GetPhyPageNum(); + } + return 0; +} + +SwContentFrame *GetFirstSub( const SwLayoutFrame *pLayout ) +{ + return const_cast(static_cast(pLayout))->FindFirstBodyContent(); +} + +SwContentFrame *GetLastSub( const SwLayoutFrame *pLayout ) +{ + return const_cast(static_cast(pLayout))->FindLastBodyContent(); +} + +SwLayoutFrame *GetNextFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pNext = + (pFrame->GetNext() && pFrame->GetNext()->IsLayoutFrame()) ? + const_cast(static_cast(pFrame->GetNext())) : nullptr; + // #i39402# in case of an empty page + if(pNext && !pNext->ContainsContent()) + pNext = (pNext->GetNext() && pNext->GetNext()->IsLayoutFrame()) ? + static_cast(pNext->GetNext()) : nullptr; + return pNext; +} + +SwLayoutFrame *GetThisFrame( const SwLayoutFrame *pFrame ) +{ + return const_cast(pFrame); +} + +SwLayoutFrame *GetPrevFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pPrev = + (pFrame->GetPrev() && pFrame->GetPrev()->IsLayoutFrame()) ? + const_cast(static_cast(pFrame->GetPrev())) : nullptr; + // #i39402# in case of an empty page + if(pPrev && !pPrev->ContainsContent()) + pPrev = (pPrev->GetPrev() && pPrev->GetPrev()->IsLayoutFrame()) ? + static_cast(pPrev->GetPrev()) : nullptr; + return pPrev; +} + +/** + * Returns the first/last Contentframe (controlled using the parameter fnPosPage) + * of the current/previous/next page (controlled using the parameter fnWhichPage). + */ +bool GetFrameInPage( const SwContentFrame *pCnt, SwWhichPage fnWhichPage, + SwPosPage fnPosPage, SwPaM *pPam ) +{ + //First find the requested page, at first the current, then the one which + //was requests through fnWichPage. + const SwLayoutFrame *pLayoutFrame = pCnt->FindPageFrame(); + if ( !pLayoutFrame || (nullptr == (pLayoutFrame = (*fnWhichPage)(pLayoutFrame))) ) + return false; + + //Now the desired ContentFrame below the page + pCnt = (*fnPosPage)(pLayoutFrame); + if( nullptr == pCnt ) + return false; + else + { + // repeated headlines in tables + if ( pCnt->IsInTab() && fnPosPage == GetFirstSub ) + { + const SwTabFrame* pTab = pCnt->FindTabFrame(); + if ( pTab->IsFollow() ) + { + if ( pTab->IsInHeadline( *pCnt ) ) + { + SwLayoutFrame* pRow = pTab->GetFirstNonHeadlineRow(); + if ( pRow ) + { + // We are in the first line of a follow table + // with repeated headings. + // To actually make a "real" move we take the first content + // of the next row + pCnt = pRow->ContainsContent(); + if ( ! pCnt ) + return false; + } + } + } + } + + assert(pCnt->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast(pCnt)); + TextFrameIndex const nIdx((fnPosPage == GetFirstSub) + ? pFrame->GetOffset() + : (pFrame->GetFollow()) + ? pFrame->GetFollow()->GetOffset() - TextFrameIndex(1) + : TextFrameIndex(pFrame->GetText().getLength())); + *pPam->GetPoint() = pFrame->MapViewToModelPos(nIdx); + return true; + } +} + +static sal_uInt64 CalcDiff(const Point &rPt1, const Point &rPt2) +{ + //Calculate the distance between the two points. + //'delta' X^2 + 'delta'Y^2 = 'distance'^2 + sal_uInt64 dX = std::max( rPt1.X(), rPt2.X() ) - + std::min( rPt1.X(), rPt2.X() ), + dY = std::max( rPt1.Y(), rPt2.Y() ) - + std::min( rPt1.Y(), rPt2.Y() ); + return (dX * dX) + (dY * dY); +} + +/** Check if the point lies inside the page part in which also the ContentFrame lies. + * + * In this context header, page body, footer and footnote-container count as page part. + * This will suit the purpose that the ContentFrame which lies in the "right" page part will be + * accepted instead of one which doesn't lie there although his distance to the point is shorter. + */ +static const SwLayoutFrame* lcl_Inside( const SwContentFrame *pCnt, Point const & rPt ) +{ + const SwLayoutFrame* pUp = pCnt->GetUpper(); + while( pUp ) + { + if( pUp->IsPageBodyFrame() || pUp->IsFooterFrame() || pUp->IsHeaderFrame() ) + { + if( rPt.Y() >= pUp->getFrameArea().Top() && rPt.Y() <= pUp->getFrameArea().Bottom() ) + return pUp; + return nullptr; + } + if( pUp->IsFootnoteContFrame() ) + return pUp->getFrameArea().Contains( rPt ) ? pUp : nullptr; + pUp = pUp->GetUpper(); + } + return nullptr; +} + +/** Search for the nearest Content to pass. + * + * Considers the previous, the current and the next page. + * If no content is found, the area gets expanded until one is found. + * + * @return The 'semantically correct' position inside the PrtArea of the found ContentFrame. + */ +const SwContentFrame *SwLayoutFrame::GetContentPos( Point& rPoint, + const bool bDontLeave, + const bool bBodyOnly, + SwCursorMoveState *pCMS, + const bool bDefaultExpand ) const +{ + //Determine the first ContentFrame. + const SwLayoutFrame *pStart = (!bDontLeave && bDefaultExpand && GetPrev()) ? + static_cast(GetPrev()) : this; + const SwContentFrame *pContent = pStart->ContainsContent(); + + if ( !pContent && (GetPrev() && !bDontLeave) ) + pContent = ContainsContent(); + + if ( bBodyOnly && pContent && !pContent->IsInDocBody() ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + + const SwContentFrame *pActual= pContent; + const SwLayoutFrame *pInside = nullptr; + sal_uInt16 nMaxPage = GetPhyPageNum() + (bDefaultExpand ? 1 : 0); + Point aPoint = rPoint; + sal_uInt64 nDistance = SAL_MAX_UINT64; + + while ( true ) //A loop to be sure we always find one. + { + while ( pContent && + ((!bDontLeave || IsAnLower( pContent )) && + (pContent->GetPhyPageNum() <= nMaxPage)) ) + { + if ( pContent->getFrameArea().Width() && + ( !bBodyOnly || pContent->IsInDocBody() ) ) + { + //If the Content lies in a protected area (cell, Footnote, section), + //we search the next Content which is not protected. + const SwContentFrame *pComp = pContent; + pContent = ::lcl_MissProtectedFrames( pContent, lcl_GetNxtCnt, false, + pCMS && pCMS->m_bSetInReadOnly, false ); + if ( pComp != pContent ) + continue; + + if ( !pContent->IsTextFrame() || !static_cast(pContent)->IsHiddenNow() ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.Contains( rPoint ) ) + { + pActual = pContent; + aPoint = rPoint; + break; + } + //The distance from rPoint to the nearest Point of pContent + //will now be calculated. + Point aContentPoint( rPoint ); + + //First set the vertical position + if ( aContentFrame.Top() > aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Bottom() ); + + //Now the horizontal position + if ( aContentFrame.Left() > aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Right() ); + + // pInside is a page area in which the point lies. As soon + // as pInside != 0 only frames are accepted which are + // placed inside. + if( !pInside || ( pInside->IsAnLower( pContent ) && + ( !pContent->IsInFootnote() || pInside->IsFootnoteContFrame() ) ) ) + { + const sal_uInt64 nDiff = ::CalcDiff(aContentPoint, rPoint); + bool bBetter = nDiff < nDistance; // This one is nearer + if( !pInside ) + { + pInside = lcl_Inside( pContent, rPoint ); + if( pInside ) // In the "right" page area + bBetter = true; + } + if( bBetter ) + { + aPoint = aContentPoint; + nDistance = nDiff; + pActual = pContent; + } + } + } + } + pContent = pContent->GetNextContentFrame(); + if ( bBodyOnly ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + if ( !pActual ) + { //If we not yet found one we have to expand the searched + //area, sometime we will find one! + //MA 1997-01-09: Opt for many empty pages - if we only search inside + //the body, we can expand the searched area sufficiently in one step. + if ( bBodyOnly ) + { + while ( !pContent && pStart->GetPrev() ) + { + ++nMaxPage; + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast(pStart->GetPrev()); + if( pStart->IsInDocBody() ) + pContent = pStart->ContainsContent(); + else + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->FindFirstBodyContent(); + } + } + if ( !pContent ) // Somewhere down the road we have to start with one! + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->GetUpper()->ContainsContent(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( !pContent ) + return nullptr; // There is no document content yet! + } + } + else + { + ++nMaxPage; + if ( pStart->GetPrev() ) + { + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast(pStart->GetPrev()); + pContent = pStart->ContainsContent(); + } + else // Somewhere down the road we have to start with one! + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->GetUpper()->ContainsContent(); + } + } + pActual = pContent; + } + else + break; + } + + OSL_ENSURE( pActual, "no Content found." ); + OSL_ENSURE( !bBodyOnly || pActual->IsInDocBody(), "Content not in Body." ); + + //Special case for selecting tables not in repeated TableHeadlines. + if ( pActual->IsInTab() && pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = pActual->FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *pActual ) ) + { + pCMS->m_bStop = true; + return nullptr; + } + } + + //A small correction at the first/last + Size aActualSize( pActual->getFramePrintArea().SSize() ); + if ( aActualSize.Height() > pActual->GetUpper()->getFramePrintArea().Height() ) + aActualSize.setHeight( pActual->GetUpper()->getFramePrintArea().Height() ); + + SwRectFnSet aRectFnSet(pActual); + if ( !pActual->GetPrev() && + aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) > 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Top() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Right() : + pActual->getFramePrintArea().Left() ) ); + } + else if ( !pActual->GetNext() && + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) < 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Bottom() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Left() : + pActual->getFramePrintArea().Right() ) ); + } + + //Bring the Point into the PrtArea + const SwRect aRect( pActual->getFrameArea().Pos() + pActual->getFramePrintArea().Pos(), + aActualSize ); + if ( aPoint.Y() < aRect.Top() ) + aPoint.setY( aRect.Top() ); + else if ( aPoint.Y() > aRect.Bottom() ) + aPoint.setY( aRect.Bottom() ); + if ( aPoint.X() < aRect.Left() ) + aPoint.setX( aRect.Left() ); + else if ( aPoint.X() > aRect.Right() ) + aPoint.setX( aRect.Right() ); + rPoint = aPoint; + return pActual; +} + +/** Same as SwLayoutFrame::GetContentPos(). Specialized for fields and border. */ +void SwPageFrame::GetContentPosition( const Point &rPt, SwPosition &rPos ) const +{ + //Determine the first ContentFrame. + const SwContentFrame *pContent = ContainsContent(); + if ( pContent ) + { + //Look back one more (if possible). + const SwContentFrame *pTmp = pContent->GetPrevContentFrame(); + while ( pTmp && !pTmp->IsInDocBody() ) + pTmp = pTmp->GetPrevContentFrame(); + if ( pTmp ) + pContent = pTmp; + } + else + pContent = GetUpper()->ContainsContent(); + + const SwContentFrame *pAct = pContent; + Point aAct = rPt; + sal_uInt64 nDist = SAL_MAX_UINT64; + + while ( pContent ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.Contains( rPt ) ) + { + //This is the nearest one. + pAct = pContent; + break; + } + + //Calculate the distance from rPt to the nearest point of pContent. + Point aPoint( rPt ); + + //Calculate the vertical position first + if ( aContentFrame.Top() > rPt.Y() ) + aPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < rPt.Y() ) + aPoint.setY( aContentFrame.Bottom() ); + + //And now the horizontal position + if ( aContentFrame.Left() > rPt.X() ) + aPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < rPt.X() ) + aPoint.setX( aContentFrame.Right() ); + + const sal_uInt64 nDiff = ::CalcDiff( aPoint, rPt ); + if ( nDiff < nDist ) + { + aAct = aPoint; + nDist = nDiff; + pAct = pContent; + } + else if ( aContentFrame.Top() > getFrameArea().Bottom() ) + //In terms of fields, it's not possible to be closer any more! + break; + + pContent = pContent->GetNextContentFrame(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + + //Bring the point into the PrtArea. + const SwRect aRect( pAct->getFrameArea().Pos() + pAct->getFramePrintArea().Pos(), pAct->getFramePrintArea().SSize() ); + if ( aAct.Y() < aRect.Top() ) + aAct.setY( aRect.Top() ); + else if ( aAct.Y() > aRect.Bottom() ) + aAct.setY( aRect.Bottom() ); + if ( aAct.X() < aRect.Left() ) + aAct.setX( aRect.Left() ); + else if ( aAct.X() > aRect.Right() ) + aAct.setX( aRect.Right() ); + + if (!pAct->isFrameAreaDefinitionValid() || + (pAct->IsTextFrame() && !static_cast(pAct)->HasPara())) + { + // ContentFrame not formatted -> always on node-beginning + // tdf#100635 also if the SwTextFrame would require reformatting, + // which is unwanted in case this is called from text formatting code + rPos = static_cast(pAct)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + pAct->GetModelPositionForViewPoint( &rPos, aAct, &aTmpState ); + } +} + +/** Search the nearest Content to the passed point. + * + * Only search inside the BodyText. + * @note Only the nearest vertically one will be searched. + * @note JP 11.10.2001: only in tables we try to find the right column - Bug 72294 + */ +Point SwRootFrame::GetNextPrevContentPos( const Point& rPoint, bool bNext ) const +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + // #123110# - disable creation of an action by a callback + // event during processing of this method. Needed because formatting is + // triggered by this method. + DisableCallbackAction aDisableCallbackAction(const_cast(*this)); + //Search the first ContentFrame and his successor in the body area. + //To be efficient (and not formatting too much) we'll start at the correct + //page. + const SwLayoutFrame *pPage = static_cast(Lower()); + if( pPage ) + while( pPage->GetNext() && pPage->getFrameArea().Bottom() < rPoint.Y() ) + pPage = static_cast(pPage->GetNext()); + + const SwContentFrame *pCnt = pPage ? pPage->ContainsContent() : ContainsContent(); + while ( pCnt && !pCnt->IsInDocBody() ) + pCnt = pCnt->GetNextContentFrame(); + + if ( !pCnt ) + return Point( 0, 0 ); + + pCnt->Calc(pRenderContext); + if( !bNext ) + { + // As long as the point lies before the first ContentFrame and there are + // still precedent pages I'll go to the next page. + while ( rPoint.Y() < pCnt->getFrameArea().Top() && pPage->GetPrev() ) + { + pPage = static_cast(pPage->GetPrev()); + pCnt = pPage->ContainsContent(); + while ( !pCnt ) + { + pPage = static_cast(pPage->GetPrev()); + if ( pPage ) + pCnt = pPage->ContainsContent(); + else + return ContainsContent()->UnionFrame().Pos(); + } + pCnt->Calc(pRenderContext); + } + } + + //Does the point lie above the first ContentFrame? + if ( rPoint.Y() < pCnt->getFrameArea().Top() && !lcl_IsInRepeatedHeadline( pCnt ) ) + return pCnt->UnionFrame().Pos(); + + Point aRet(0, 0); + do + { + //Does the point lie in the current ContentFrame? + SwRect aContentFrame( pCnt->UnionFrame() ); + if ( aContentFrame.Contains( rPoint ) && !lcl_IsInRepeatedHeadline( pCnt )) + { + aRet = rPoint; + break; + } + + //Is the current one the last ContentFrame? + //If the next ContentFrame lies behind the point, then the current on is the + //one we searched. + const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); + while ( pNxt && !pNxt->IsInDocBody() ) + pNxt = pNxt->GetNextContentFrame(); + + //Does the point lie behind the last ContentFrame? + if ( !pNxt ) + { + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + + //If the next ContentFrame lies behind the point then it is the one we + //searched. + const SwTabFrame* pTFrame; + pNxt->Calc(pRenderContext); + if( pNxt->getFrameArea().Top() > rPoint.Y() && + !lcl_IsInRepeatedHeadline( pCnt, &pTFrame ) && + ( !pTFrame || pNxt->getFrameArea().Left() > rPoint.X() )) + { + if (bNext) + aRet = pNxt->getFrameArea().Pos(); + else + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + pCnt = pNxt; + } + while (pCnt); + return aRet; +} + +/** Returns the absolute document position of the desired page. + * + * Formatting is done only as far as needed and only if bFormat=true. + * Pos is set to the one of the last page, if the page number was chosen too big. + * + * @return Null, if the operation failed. + */ +Point SwRootFrame::GetPagePos( sal_uInt16 nPageNum ) const +{ + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + const SwPageFrame *pPage = static_cast(Lower()); + while ( true ) + { + if ( pPage->GetPhyPageNum() >= nPageNum || !pPage->GetNext() ) + break; + pPage = static_cast(pPage->GetNext()); + } + return pPage->getFrameArea().Pos(); +} + +/** get page frame by physical page number + * + * @return pointer to the page frame with the given physical page number + */ +SwPageFrame* SwRootFrame::GetPageByPageNum( sal_uInt16 _nPageNum ) const +{ + const SwPageFrame* pPageFrame = static_cast( Lower() ); + while ( pPageFrame && pPageFrame->GetPhyPageNum() < _nPageNum ) + { + pPageFrame = static_cast( pPageFrame->GetNext() ); + } + + if ( pPageFrame && pPageFrame->GetPhyPageNum() == _nPageNum ) + { + return const_cast( pPageFrame ); + } + else + { + return nullptr; + } +} + +/** + * @return true, when the given physical pagenumber doesn't exist or this page is an empty page. + */ +bool SwRootFrame::IsDummyPage( sal_uInt16 nPageNum ) const +{ + if( !Lower() || !nPageNum || nPageNum > GetPageNum() ) + return true; + + const SwPageFrame *pPage = static_cast(Lower()); + while( pPage && nPageNum < pPage->GetPhyPageNum() ) + pPage = static_cast(pPage->GetNext()); + return !pPage || pPage->IsEmptyPage(); +} + +/** Is the Frame or rather the Section in which it lies protected? + * + * Also Fly in Fly in ... and Footnotes + */ +bool SwFrame::IsProtected() const +{ + if (IsTextFrame()) + { + const SwDoc *pDoc = &static_cast(this)->GetDoc(); + bool isFormProtected=pDoc->GetDocumentSettingManager().get(DocumentSettingId::PROTECT_FORM ); + if (isFormProtected) + { + return false; // TODO a hack for now, well deal with it later, I we return true here we have a "double" locking + } + } + //The Frame can be protected in borders, cells or sections. + //Also goes up FlyFrames recursive and from footnote to anchor. + const SwFrame *pFrame = this; + do + { + if (pFrame->IsTextFrame()) + { // sw_redlinehide: redlines can't overlap section nodes, so any node will do + if (static_cast(pFrame)->GetTextNodeFirst()->IsInProtectSect()) + { + return true; + } + } + else if ( pFrame->IsContentFrame() ) + { + assert(pFrame->IsNoTextFrame()); + if (static_cast(pFrame)->GetNode() && + static_cast(pFrame)->GetNode()->IsInProtectSect()) + { + return true; + } + } + else + { + if ( static_cast(pFrame)->GetFormat() && + static_cast(pFrame)->GetFormat()-> + GetProtect().IsContentProtected() ) + return true; + if ( pFrame->IsCoveredCell() ) + return true; + } + if ( pFrame->IsFlyFrame() ) + { + //In a chain the protection of the content can be specified by the + //master of the chain. + if ( static_cast(pFrame)->GetPrevLink() ) + { + const SwFlyFrame *pMaster = static_cast(pFrame); + do + { pMaster = pMaster->GetPrevLink(); + } while ( pMaster->GetPrevLink() ); + if ( pMaster->IsProtected() ) + return true; + } + pFrame = static_cast(pFrame)->GetAnchorFrame(); + } + else if ( pFrame->IsFootnoteFrame() ) + pFrame = static_cast(pFrame)->GetRef(); + else + pFrame = pFrame->GetUpper(); + + } while ( pFrame ); + + return false; +} + +/** @return the physical page number */ +sal_uInt16 SwFrame::GetPhyPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + return pPage ? pPage->GetPhyPageNum() : 0; +} + +/** Decides if the page want to be a right page or not. + * + * If the first content of the page has a page descriptor, we take the follow + * of the page descriptor of the last not empty page. If this descriptor allows + * only right(left) pages and the page isn't an empty page then it wants to be + * such right(left) page. If the descriptor allows right and left pages, we + * look for a number offset in the first content. If there is one, odd number + * results right pages (or left pages if document starts with even number), + * even number results left pages (or right pages if document starts with even + * number). + * If there is no number offset, we take the physical page number instead, + * but a previous empty page doesn't count. + */ +bool SwFrame::WannaRightPage() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return true; + + const SwFrame *pFlow = pPage->FindFirstBodyContent(); + const SwPageDesc *pDesc = nullptr; + ::std::optional oPgNum; + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + const SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pFlow ); + if ( !pTmp->IsFollow() ) + { + const SwFormatPageDesc& rPgDesc = pFlow->GetPageDescItem(); + pDesc = rPgDesc.GetPageDesc(); + oPgNum = rPgDesc.GetNumOffset(); + } + } + if ( !pDesc ) + { + SwPageFrame *pPrv = const_cast(static_cast(pPage->GetPrev())); + if( pPrv && pPrv->IsEmptyPage() ) + pPrv = static_cast(pPrv->GetPrev()); + if( pPrv ) + pDesc = pPrv->GetPageDesc()->GetFollow(); + else + { + const SwDoc* pDoc = pPage->GetFormat()->GetDoc(); + pDesc = &pDoc->GetPageDesc( 0 ); + } + } + OSL_ENSURE( pDesc, "No pagedescriptor" ); + bool isRightPage; + if( oPgNum ) + isRightPage = sw::IsRightPageByNumber(*mpRoot, *oPgNum); + else + { + isRightPage = pPage->OnRightPage(); + if( pPage->GetPrev() && static_cast(pPage->GetPrev())->IsEmptyPage() ) + isRightPage = !isRightPage; + } + if( !pPage->IsEmptyPage() ) + { + if( !pDesc->GetRightFormat() ) + isRightPage = false; + else if( !pDesc->GetLeftFormat() ) + isRightPage = true; + } + return isRightPage; +} + +bool SwFrame::OnFirstPage() const +{ + bool bRet = false; + const SwPageFrame *pPage = FindPageFrame(); + + if (pPage) + { + const SwPageFrame* pPrevFrame = dynamic_cast(pPage->GetPrev()); + if (pPrevFrame) + { + // first page of layout may be empty page, but only if it starts with "Left Page" style + const SwPageDesc* pDesc = pPage->GetPageDesc(); + bRet = pPrevFrame->GetPageDesc() != pDesc; + } + else + bRet = true; + } + return bRet; +} + +void SwFrame::Calc(vcl::RenderContext* pRenderContext) const +{ + if ( !isFrameAreaPositionValid() || !isFramePrintAreaValid() || !isFrameAreaSizeValid() ) + { + const_cast(this)->PrepareMake(pRenderContext); + } +} + +Point SwFrame::GetRelPos() const +{ + Point aRet( getFrameArea().Pos() ); + // here we cast since SwLayoutFrame is declared only as forwarded + aRet -= GetUpper()->getFramePrintArea().Pos(); + aRet -= GetUpper()->getFrameArea().Pos(); + return aRet; +} + +/** @return the virtual page number with the offset. */ +sal_uInt16 SwFrame::GetVirtPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return 0; + + sal_uInt16 nPhyPage = pPage->GetPhyPageNum(); + if ( !static_cast(pPage->GetUpper())->IsVirtPageNum() ) + return nPhyPage; + + //Search the nearest section using the virtual page number. + //Because searching backwards needs a lot of time we search specific using + //the dependencies. From the PageDescs we get the attributes and from the + //attributes we get the sections. + const SwPageFrame *pVirtPage = nullptr; + const SwFrame *pFrame = nullptr; + const SfxItemPool &rPool = pPage->GetFormat()->GetDoc()->GetAttrPool(); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(RES_PAGEDESC)) + { + const SwFormatPageDesc *pDesc = dynamic_cast(pItem); + if ( !pDesc ) + continue; + + if ( pDesc->GetNumOffset() && pDesc->GetDefinedIn() ) + { + const sw::BroadcastingModify *pMod = pDesc->GetDefinedIn(); + SwVirtPageNumInfo aInfo( pPage ); + pMod->GetInfo( aInfo ); + if ( aInfo.GetPage() ) + { + if( !pVirtPage || aInfo.GetPage()->GetPhyPageNum() > pVirtPage->GetPhyPageNum() ) + { + pVirtPage = aInfo.GetPage(); + pFrame = aInfo.GetFrame(); + } + } + } + } + if ( pFrame ) + { + ::std::optional oNumOffset = pFrame->GetPageDescItem().GetNumOffset(); + if (oNumOffset) + { + return nPhyPage - pFrame->GetPhyPageNum() + *oNumOffset; + } + else + { + return nPhyPage - pFrame->GetPhyPageNum(); + } + } + return nPhyPage; +} + +/** Determines and sets those cells which are enclosed by the selection. */ +bool SwRootFrame::MakeTableCursors( SwTableCursor& rTableCursor ) +{ + //Find Union-Rects and tables (Follows) of the selection. + OSL_ENSURE( rTableCursor.GetContentNode() && rTableCursor.GetContentNode( false ), + "Tabselection not on Cnt." ); + + bool bRet = false; + + // For new table models there's no need to ask the layout... + if( rTableCursor.NewTableSelection() ) + return true; + + Point aPtPt, aMkPt; + { + SwShellCursor* pShCursor = dynamic_cast(&rTableCursor); + + if( pShCursor ) + { + aPtPt = pShCursor->GetPtPos(); + aMkPt = pShCursor->GetMkPos(); + } + } + + // #151012# Made code robust here + const SwContentNode* pTmpStartNode = rTableCursor.GetContentNode(); + const SwContentNode* pTmpEndNode = rTableCursor.GetContentNode(false); + + std::pair tmp(aPtPt, false); + const SwFrame *const pTmpStartFrame = pTmpStartNode ? pTmpStartNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + tmp.first = aMkPt; + const SwFrame *const pTmpEndFrame = pTmpEndNode ? pTmpEndNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + + const SwLayoutFrame* pStart = pTmpStartFrame ? pTmpStartFrame->GetUpper() : nullptr; + const SwLayoutFrame* pEnd = pTmpEndFrame ? pTmpEndFrame->GetUpper() : nullptr; + + OSL_ENSURE( pStart && pEnd, "MakeTableCursors: Good to have the code robust here!" ); + + /* #109590# Only change table boxes if the frames are + valid. Needed because otherwise the table cursor after moving + table cells by dnd resulted in an empty tables cursor. */ + if ( pStart && pEnd && pStart->isFrameAreaDefinitionValid() && pEnd->isFrameAreaDefinitionValid()) + { + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + SwSelBoxes aNew; + + const bool bReadOnlyAvailable = rTableCursor.IsReadOnlyAvailable(); + + for (SwSelUnion & rUnion : aUnions) + { + const SwTabFrame *pTable = rUnion.GetTable(); + + // Skip any repeated headlines in the follow: + SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + const_cast(static_cast(pTable->Lower())); + + while ( pRow ) + { + if ( pRow->getFrameArea().Overlaps( rUnion.GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while ( pCell && pRow->IsAnLower( pCell ) ) + { + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( IsFrameInTableSel( rUnion.GetUnion(), pCell ) && + (bReadOnlyAvailable || + !pCell->GetFormat()->GetProtect().IsContentProtected())) + { + SwTableBox* pInsBox = const_cast( + static_cast(pCell)->GetTabBox()); + aNew.insert( pInsBox ); + } + if ( pCell->GetNext() ) + { + pCell = static_cast(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + { + const SwLayoutFrame* pLastCell = pCell; + do + { + pCell = pCell->GetNextLayoutLeaf(); + } while ( pCell && pLastCell->IsAnLower( pCell ) ); + // For sections with columns + if( pCell && pCell->IsInTab() ) + { + while( !pCell->IsCellFrame() ) + { + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "Where's my cell?" ); + } + } + } + } + } + pRow = static_cast(pRow->GetNext()); + } + } + + rTableCursor.ActualizeSelection( aNew ); + bRet = true; + } + + return bRet; +} + +static void Sub( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 && + rRect.Overlaps( rRegion.GetOrigin() )) + rRegion -= rRect; +} + +static void Add( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 ) + rRegion += rRect; +} + +/* + * The following situations can happen: + * 1. Start and end lie in one screen-row and in the same node + * -> one rectangle out of start and end; and we're okay + * 2. Start and end lie in one frame (therefore in the same node!) + * -> expand start to the right, end to the left and if more than two + * screen-rows are involved - calculate the in-between + * 3. Start and end lie in different frames + * -> expand start to the right until frame-end, calculate Rect + * expand end to the left until frame-start, calculate Rect + * and if more than two frames are involved add the PrtArea of all + * frames which lie in between + * + * Big reorganization because of the FlyFrame - those need to be locked out. + * Exceptions: - The Fly in which the selection took place (if it took place + * in a Fly) + * - The Flys which are underrun by the text + * - The Flys which are anchored to somewhere inside the selection. + * Functioning: First a SwRegion with a root gets initialized. + * Out of the region the inverted sections are cut out. The + * section gets compressed and finally inverted and thereby the + * inverted rectangles are available. + * In the end the Flys are cut out of the section. + */ +void SwRootFrame::CalcFrameRects(SwShellCursor &rCursor) +{ + SwPosition *pStartPos = rCursor.Start(), + *pEndPos = rCursor.End(); + + SwViewShell *pSh = GetCurrShell(); + + bool bIgnoreVisArea = true; + if (pSh) + bIgnoreVisArea = pSh->GetViewOptions()->IsPDFExport() || comphelper::LibreOfficeKit::isActive(); + + // #i12836# enhanced pdf + SwRegionRects aRegion( !bIgnoreVisArea ? + pSh->VisArea() : + getFrameArea() ); + if( !pStartPos->nNode.GetNode().IsContentNode() || + !pStartPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) || + ( pStartPos->nNode != pEndPos->nNode && + ( !pEndPos->nNode.GetNode().IsContentNode() || + !pEndPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) ) ) ) + { + return; + } + + DisableCallbackAction a(*this); // the GetCharRect below may format + + //First obtain the ContentFrames for the start and the end - those are needed + //anyway. + std::pair tmp(rCursor.GetSttPos(), true); + SwContentFrame* pStartFrame = pStartPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pStartPos, &tmp); + + tmp.first = rCursor.GetEndPos(); + SwContentFrame* pEndFrame = pEndPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pEndPos, &tmp); + + assert(pStartFrame && pEndFrame && "No ContentFrames found."); + //tdf#119224 start and end are expected to exist for the scope of this function + SwFrameDeleteGuard aStartFrameGuard(pStartFrame), aEndFrameGuard(pEndFrame); + + //Do not subtract the FlyFrames in which selected Frames lie. + SwSortedObjs aSortObjs; + if ( pStartFrame->IsInFly() ) + { + const SwAnchoredObject* pObj = pStartFrame->FindFlyFrame(); + OSL_ENSURE( pObj, "No Start Object." ); + if (pObj) aSortObjs.Insert( *const_cast(pObj) ); + const SwAnchoredObject* pObj2 = pEndFrame->FindFlyFrame(); + OSL_ENSURE( pObj2, "SwRootFrame::CalcFrameRects(..) - FlyFrame missing - looks like an invalid selection" ); + if ( pObj2 != nullptr && pObj2 != pObj ) + { + aSortObjs.Insert( *const_cast(pObj2) ); + } + } + + // if a selection which is not allowed exists, we correct what is not + // allowed (header/footer/table-headline) for two pages. + do { // middle check loop + const SwLayoutFrame* pSttLFrame = pStartFrame->GetUpper(); + const SwFrameType cHdFtTableHd = SwFrameType::Header | SwFrameType::Footer | SwFrameType::Tab; + while( pSttLFrame && + ! (cHdFtTableHd & pSttLFrame->GetType() )) + pSttLFrame = pSttLFrame->GetUpper(); + if( !pSttLFrame ) + break; + const SwLayoutFrame* pEndLFrame = pEndFrame->GetUpper(); + while( pEndLFrame && + ! (cHdFtTableHd & pEndLFrame->GetType() )) + pEndLFrame = pEndLFrame->GetUpper(); + if( !pEndLFrame ) + break; + + OSL_ENSURE( pEndLFrame->GetType() == pSttLFrame->GetType(), + "Selection over different content" ); + switch( pSttLFrame->GetType() ) + { + case SwFrameType::Header: + case SwFrameType::Footer: + // On different pages? Then always on the start-page + if( pEndLFrame->FindPageFrame() != pSttLFrame->FindPageFrame() ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + break; + case SwFrameType::Tab: + // On different pages? Then check for table-headline + { + const SwTabFrame* pTabFrame = static_cast(pSttLFrame); + if( ( pTabFrame->GetFollow() || + static_cast(pEndLFrame)->GetFollow() ) && + pTabFrame->GetTable()->GetRowsToRepeat() > 0 && + pTabFrame->GetLower() != static_cast(pEndLFrame)->GetLower() && + ( lcl_IsInRepeatedHeadline( pStartFrame ) || + lcl_IsInRepeatedHeadline( pEndFrame ) ) ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + } + break; + default: break; + } + } while( false ); + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_b2Lines = true; + aTmpState.m_bNoScroll = true; + aTmpState.m_nCursorBidiLevel = pStartFrame->IsRightToLeft() ? 1 : 0; + + //ContentRects to Start- and EndFrames. + SwRect aStRect, aEndRect; + pStartFrame->GetCharRect( aStRect, *pStartPos, &aTmpState ); + std::unique_ptr pSt2Pos = std::move(aTmpState.m_p2Lines); + aTmpState.m_nCursorBidiLevel = pEndFrame->IsRightToLeft() ? 1 : 0; + + pEndFrame->GetCharRect( aEndRect, *pEndPos, &aTmpState ); + std::unique_ptr pEnd2Pos = std::move(aTmpState.m_p2Lines); + + SwRect aStFrame ( pStartFrame->UnionFrame( true ) ); + aStFrame.Intersection( pStartFrame->GetPaintArea() ); + SwRect aEndFrame( pStartFrame == pEndFrame ? aStFrame : pEndFrame->UnionFrame( true ) ); + if( pStartFrame != pEndFrame ) + { + aEndFrame.Intersection( pEndFrame->GetPaintArea() ); + } + SwRectFnSet aRectFnSet(pStartFrame); + const bool bR2L = pStartFrame->IsRightToLeft(); + const bool bEndR2L = pEndFrame->IsRightToLeft(); + const bool bB2T = pStartFrame->IsVertLRBT(); + + // If there's no doubleline portion involved or start and end are both + // in the same doubleline portion, all works fine, but otherwise + // we need the following... + if( pSt2Pos != pEnd2Pos && ( !pSt2Pos || !pEnd2Pos || + pSt2Pos->aPortion != pEnd2Pos->aPortion ) ) + { + // If we have a start(end) position inside a doubleline portion + // the surrounded part of the doubleline portion is subtracted + // from the region and the aStRect(aEndRect) is set to the + // end(start) of the doubleline portion. + if( pSt2Pos ) + { + SwRect aTmp( aStRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pSt2Pos->nMultiType ) ? + ! bR2L : + bR2L; + + if( MultiPortionType::BIDI == pSt2Pos->nMultiType && + aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + // nested bidi portion + tools::Long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + + if ( ! pEnd2Pos || pEnd2Pos->aPortion != pSt2Pos->aPortion ) + { + SwRect aTmp2( pSt2Pos->aPortion ); + aRectFnSet.SetRight( aTmp2, nLeftAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if( bPorR2L ) + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + else + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pSt2Pos->nMultiType || + aRectFnSet.GetTop(pSt2Pos->aPortion) == + aRectFnSet.GetTop(aTmp) ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetTop(pSt2Pos->aLine) ); + } + + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + SwTwips nTmp = aRectFnSet.GetBottom(pSt2Pos->aLine); + if( MultiPortionType::ROT_90 != pSt2Pos->nMultiType && + aRectFnSet.BottomDist( aStRect, nTmp ) > 0 ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetBottom(aTmp) ); + aRectFnSet.SetBottom( aTmp, nTmp ); + if( aRectFnSet.BottomDist( aStRect, aRectFnSet.GetBottom(pSt2Pos->aPortion) ) > 0 ) + { + if( bPorR2L ) + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + else + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? + aRectFnSet.GetLeft(pSt2Pos->aPortion) : + aRectFnSet.GetRight(pSt2Pos->aPortion) ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + if( pEnd2Pos ) + { + SwRectFnSet fnRectX(pEndFrame); + SwRect aTmp( aEndRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pEnd2Pos->nMultiType ) ? + ! bEndR2L : + bEndR2L; + + if( MultiPortionType::BIDI == pEnd2Pos->nMultiType && + fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + // nested bidi portion + tools::Long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs = nRightAbs - fnRectX.GetLeft(pEnd2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + + if ( ! pSt2Pos || pSt2Pos->aPortion != pEnd2Pos->aPortion ) + { + SwRect aTmp2( pEnd2Pos->aPortion ); + fnRectX.SetLeft( aTmp2, nRightAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if ( bPorR2L ) + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + else + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pEnd2Pos->nMultiType || + fnRectX.GetBottom(pEnd2Pos->aPortion) == + fnRectX.GetBottom(aEndRect) ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetBottom(pEnd2Pos->aLine) ); + } + + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + // The next statement means neither ruby nor rotate(90): + if( MultiPortionType::RUBY != pEnd2Pos->nMultiType && MultiPortionType::ROT_90 != pEnd2Pos->nMultiType ) + { + SwTwips nTmp = fnRectX.GetTop(pEnd2Pos->aLine); + if( fnRectX.GetTop(aEndRect) != nTmp ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetTop(aTmp) ); + fnRectX.SetTop( aTmp, nTmp ); + if( fnRectX.GetTop(aEndRect) != + fnRectX.GetTop(pEnd2Pos->aPortion) ) + { + if( bPorR2L ) + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + else + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + } + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + } + } + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? + fnRectX.GetRight(pEnd2Pos->aPortion) : + fnRectX.GetLeft(pEnd2Pos->aPortion) ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + else if( pSt2Pos && pEnd2Pos && + MultiPortionType::BIDI == pSt2Pos->nMultiType && + MultiPortionType::BIDI == pEnd2Pos->nMultiType && + pSt2Pos->aPortion == pEnd2Pos->aPortion && + pSt2Pos->aPortion2 != pEnd2Pos->aPortion2 ) + { + // This is the ugly special case, where the selection starts and + // ends in the same bidi portion but one start or end is inside a + // nested bidi portion. + + if ( aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + SwRect aTmp( aStRect ); + tools::Long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? nRightAbs : nLeftAbs ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + SwRectFnSet fnRectX(pEndFrame); + if ( fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + SwRect aTmp( aEndRect ); + tools::Long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs -= fnRectX.GetLeft(pEnd2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? nLeftAbs : nRightAbs ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + + // The charrect may be outside the paintarea (for cursortravelling) + // but the selection has to be restricted to the paintarea + if( aStRect.Left() < aStFrame.Left() ) + aStRect.Left( aStFrame.Left() ); + else if( aStRect.Left() > aStFrame.Right() ) + aStRect.Left( aStFrame.Right() ); + SwTwips nTmp = aStRect.Right(); + if( nTmp < aStFrame.Left() ) + aStRect.Right( aStFrame.Left() ); + else if( nTmp > aStFrame.Right() ) + aStRect.Right( aStFrame.Right() ); + if( aEndRect.Left() < aEndFrame.Left() ) + aEndRect.Left( aEndFrame.Left() ); + else if( aEndRect.Left() > aEndFrame.Right() ) + aEndRect.Left( aEndFrame.Right() ); + nTmp = aEndRect.Right(); + if( nTmp < aEndFrame.Left() ) + aEndRect.Right( aEndFrame.Left() ); + else if( nTmp > aEndFrame.Right() ) + aEndRect.Right( aEndFrame.Right() ); + + if( pStartFrame == pEndFrame ) + { + bool bSameRotatedOrBidi = pSt2Pos && pEnd2Pos && + ( MultiPortionType::BIDI == pSt2Pos->nMultiType || + MultiPortionType::ROT_270 == pSt2Pos->nMultiType || + MultiPortionType::ROT_90 == pSt2Pos->nMultiType ) && + pSt2Pos->aPortion == pEnd2Pos->aPortion; + //case 1: (Same frame and same row) + if( bSameRotatedOrBidi || + aRectFnSet.GetTop(aStRect) == aRectFnSet.GetTop(aEndRect) ) + { + Point aTmpSt( aStRect.Pos() ); + Point aTmpEnd( aEndRect.Right(), aEndRect.Bottom() ); + if (bSameRotatedOrBidi || bR2L || bB2T) + { + if( aTmpSt.Y() > aTmpEnd.Y() ) + { + tools::Long nTmpY = aTmpEnd.Y(); + aTmpEnd.setY( aTmpSt.Y() ); + aTmpSt.setY( nTmpY ); + } + if( aTmpSt.X() > aTmpEnd.X() ) + { + tools::Long nTmpX = aTmpEnd.X(); + aTmpEnd.setX( aTmpSt.X() ); + aTmpSt.setX( nTmpX ); + } + } + + SwRect aTmp( aTmpSt, aTmpEnd ); + // Bug 34888: If content is selected which doesn't take space + // away (i.e. PostIts, RefMarks, TOXMarks), then at + // least set the width of the Cursor. + if( 1 == aRectFnSet.GetWidth(aTmp) && + pStartPos->nContent.GetIndex() != + pEndPos->nContent.GetIndex() ) + { + OutputDevice* pOut = pSh->GetOut(); + tools::Long nCursorWidth = pOut->GetSettings().GetStyleSettings(). + GetCursorSize(); + aRectFnSet.SetWidth( aTmp, pOut->PixelToLogic( + Size( nCursorWidth, 0 ) ).Width() ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + //case 2: (Same frame, but not the same line) + else + { + SwTwips lLeft, lRight; + if( pSt2Pos && pEnd2Pos && pSt2Pos->aPortion == pEnd2Pos->aPortion ) + { + lLeft = aRectFnSet.GetLeft(pSt2Pos->aPortion); + lRight = aRectFnSet.GetRight(pSt2Pos->aPortion); + } + else + { + lLeft = aRectFnSet.GetLeft(pStartFrame->getFrameArea()) + + aRectFnSet.GetLeft(pStartFrame->getFramePrintArea()); + lRight = aRectFnSet.GetRight(aEndFrame); + } + if( lLeft < aRectFnSet.GetLeft(aStFrame) ) + lLeft = aRectFnSet.GetLeft(aStFrame); + if( lRight > aRectFnSet.GetRight(aStFrame) ) + lRight = aRectFnSet.GetRight(aStFrame); + SwRect aSubRect( aStRect ); + //First line + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, lLeft ); + else + aRectFnSet.SetRight( aSubRect, lRight ); + Sub( aRegion, aSubRect ); + + //If there's at least a twips between start- and endline, + //so the whole area between will be added. + SwTwips aTmpBottom = aRectFnSet.GetBottom(aStRect); + SwTwips aTmpTop = aRectFnSet.GetTop(aEndRect); + if( aTmpBottom != aTmpTop ) + { + aRectFnSet.SetLeft( aSubRect, lLeft ); + aRectFnSet.SetRight( aSubRect, lRight ); + aRectFnSet.SetTop( aSubRect, aTmpBottom ); + aRectFnSet.SetBottom( aSubRect, aTmpTop ); + Sub( aRegion, aSubRect ); + } + //and the last line + aSubRect = aEndRect; + if( bR2L ) + aRectFnSet.SetRight( aSubRect, lRight ); + else + aRectFnSet.SetLeft( aSubRect, lLeft ); + Sub( aRegion, aSubRect ); + } + } + //case 3: (Different frames, maybe with other frames between) + else + { + //The startframe first... + SwRect aSubRect( aStRect ); + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aStFrame)); + else + aRectFnSet.SetRight( aSubRect, aRectFnSet.GetRight(aStFrame)); + Sub( aRegion, aSubRect ); + SwTwips nTmpTwips = aRectFnSet.GetBottom(aStRect); + if( aRectFnSet.GetBottom(aStFrame) != nTmpTwips ) + { + aSubRect = aStFrame; + aRectFnSet.SetTop( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + + //Now the frames between, if there are any + bool const bBody = pStartFrame->IsInDocBody(); + const SwTableBox* pCellBox = pStartFrame->GetUpper()->IsCellFrame() ? + static_cast(pStartFrame->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pCellBox = nullptr; + + const SwContentFrame *pContent = pStartFrame->GetNextContentFrame(); + SwRect aPrvRect; + + OSL_ENSURE( pContent, + " - no content frame. This is a serious defect" ); + while ( pContent && pContent != pEndFrame ) + { + if ( pContent->IsInFly() ) + { + const SwAnchoredObject* pObj = pContent->FindFlyFrame(); + if (!aSortObjs.Contains(*pObj)) + { // is this even possible, assuming valid cursor pos.? + aSortObjs.Insert( *const_cast(pObj) ); + } + } + + // Consider only frames which have the same IsInDocBody value like pStartFrame + // If pStartFrame is inside a SwCellFrame, consider only frames which are inside the + // same cell frame (or its follow cell) + const SwTableBox* pTmpCellBox = pContent->GetUpper()->IsCellFrame() ? + static_cast(pContent->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pTmpCellBox = nullptr; + if ( bBody == pContent->IsInDocBody() && + ( !pCellBox || pCellBox == pTmpCellBox ) ) + { + SwRect aCRect( pContent->UnionFrame( true ) ); + aCRect.Intersection( pContent->GetPaintArea() ); + if( aCRect.Overlaps( aRegion.GetOrigin() )) + { + SwRect aTmp( aPrvRect ); + aTmp.Union( aCRect ); + if ( (aPrvRect.Height() * aPrvRect.Width() + + aCRect.Height() * aCRect.Width()) == + (aTmp.Height() * aTmp.Width()) ) + { + aPrvRect.Union( aCRect ); + } + else + { + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + aPrvRect = aCRect; + } + } + } + pContent = pContent->GetNextContentFrame(); + OSL_ENSURE( pContent, + " - no content frame. This is a serious defect!" ); + } + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + + //At least the endframe... + aRectFnSet.Refresh(pEndFrame); + nTmpTwips = aRectFnSet.GetTop(aEndRect); + if( aRectFnSet.GetTop(aEndFrame) != nTmpTwips ) + { + aSubRect = aEndFrame; + aRectFnSet.SetBottom( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + aSubRect = aEndRect; + if( bEndR2L ) + aRectFnSet.SetRight(aSubRect, aRectFnSet.GetRight(aEndFrame)); + else + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aEndFrame) ); + Sub( aRegion, aSubRect ); + } + + aRegion.Invert(); + pSt2Pos.reset(); + pEnd2Pos.reset(); + + // Cut out Flys during loop. We don't cut out Flys when: + // - the Lower is StartFrame/EndFrame (FlyInCnt and all other Flys which again + // sit in it) + // - if in the Z-order we have Flys above those in which the StartFrame is + // placed + // - if they are anchored to inside the selection and thus part of it + const SwPageFrame *pPage = pStartFrame->FindPageFrame(); + const SwPageFrame *pEndPage = pEndFrame->FindPageFrame(); + + while ( pPage ) + { + if ( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame(); + if ( !pFly ) + continue; + const SwVirtFlyDrawObj* pObj = pFly->GetVirtDrawObj(); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat().GetAnchor()); + const SwPosition* anchoredAt = rAnchor.GetContentAnchor(); + bool inSelection = ( + anchoredAt != nullptr + && ( (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR + && IsDestroyFrameAnchoredAtChar(*anchoredAt, *pStartPos, *pEndPos)) + || (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + && IsSelectFrameAnchoredAtPara(*anchoredAt, *pStartPos, *pEndPos)))); + if( inSelection ) + Add( aRegion, pFly->getFrameArea() ); + else if ( !pFly->IsAnLower( pStartFrame ) && + (rSur.GetSurround() != css::text::WrapTextMode_THROUGH && + !rSur.IsContour()) ) + { + if ( aSortObjs.Contains( *pAnchoredObj ) ) + continue; + + bool bSub = true; + const sal_uInt32 nPos = pObj->GetOrdNum(); + for ( size_t k = 0; bSub && k < aSortObjs.size(); ++k ) + { + assert( dynamic_cast< const SwFlyFrame *>( aSortObjs[k] ) && + " - object in of unexpected type" ); + const SwFlyFrame* pTmp = static_cast(aSortObjs[k]); + do + { + if ( nPos < pTmp->GetVirtDrawObj()->GetOrdNumDirect() ) + { + bSub = false; + } + else + { + pTmp = pTmp->GetAnchorFrame()->FindFlyFrame(); + } + } while ( bSub && pTmp ); + } + if ( bSub ) + Sub( aRegion, pFly->getFrameArea() ); + } + } + } + if ( pPage == pEndPage ) + break; + else + pPage = static_cast(pPage->GetNext()); + } + + //Because it looks better, we close the DropCaps. + SwRect aDropRect; + if ( pStartFrame->IsTextFrame() ) + { + if ( static_cast(pStartFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + if ( pEndFrame != pStartFrame && pEndFrame->IsTextFrame() ) + { + if ( static_cast(pEndFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + + rCursor.assign( aRegion.begin(), aRegion.end() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3