/* -*- 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.IsInside( 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.IsInside( 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().IsInside( 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.IsOver(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.IsOver(GetPaintArea()) ) { bRet = SwLayoutFrame::FillSelection( rList, rRect ); if( GetSortedObjs() ) { const SwSortedObjs &rObjs = *GetSortedObjs(); for (SwAnchoredObject* pAnchoredObj : rObjs) { const SwFlyFrame* pFly = dynamic_cast(pAnchoredObj); 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().IsInside( 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().IsInside( 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().IsInside( rPoint ) && getFrameArea().IsInside( 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().IsInside( 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().IsInside( aInsideCell ) ) { bEnd = true; //Get the right Content out of the cell. if ( !pCnt->getFrameArea().IsInside( aInsideCnt ) ) { pCnt = pCell->ContainsContent(); if ( fnNxtPrv == lcl_GetPrvCnt ) while ( pCell->IsAnLower(pCnt->GetNextContentFrame()) ) pCnt = pCnt->GetNextContentFrame(); } } else if ( pCnt->getFrameArea().IsInside( 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().IsInside( 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.IsInside( 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()); pContent = pStart->IsInDocBody() ? pStart->ContainsContent() : pStart->FindPageFrame()->FindFirstBodyContent(); } if ( !pContent ) // Somewhere down the road we have to start with one! { pContent = pStart->FindPageFrame()->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! pContent = pStart->FindPageFrame()->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.IsInside( 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.IsInside( 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().IsOver( 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.IsOver( 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.GetPoint() == pStartPos ? rCursor.GetMark() : rCursor.GetPoint(); 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::optional pSt2Pos = std::move(aTmpState.m_x2Lines); aTmpState.m_nCursorBidiLevel = pEndFrame->IsRightToLeft() ? 1 : 0; pEndFrame->GetCharRect( aEndRect, *pEndPos, &aTmpState ); std::optional pEnd2Pos = std::move(aTmpState.m_x2Lines); 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.IsOver( 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 = dynamic_cast(pAnchoredObj); 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 ) { OSL_ENSURE( dynamic_cast< const SwFlyFrame *>( aSortObjs[k] ) != nullptr, " - 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: */