diff options
Diffstat (limited to 'sw/source/core/text/frmcrsr.cxx')
-rw-r--r-- | sw/source/core/text/frmcrsr.cxx | 1689 |
1 files changed, 1689 insertions, 0 deletions
diff --git a/sw/source/core/text/frmcrsr.cxx b/sw/source/core/text/frmcrsr.cxx new file mode 100644 index 000000000..58792ecf8 --- /dev/null +++ b/sw/source/core/text/frmcrsr.cxx @@ -0,0 +1,1689 @@ +/* -*- 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 <ndtxt.hxx> +#include <pam.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <paratr.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <swtypes.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> +#include "pormulti.hxx" +#include <doc.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <sortedobjs.hxx> + +#include <unicode/ubidi.h> + +#include <txtfrm.hxx> +#include "inftxt.hxx" +#include "itrtxt.hxx" +#include <crstate.hxx> +#include <viewsh.hxx> +#include <swfntcch.hxx> +#include <flyfrm.hxx> + +#define MIN_OFFSET_STEP 10 + +using namespace ::com::sun::star; + +/* + * - SurvivalKit: For how long do we get past the last char of the line. + * - RightMargin abstains from adjusting position with -1 + * - GetCharRect returns a GetEndCharRect for CursorMoveState::RightMargin + * - GetEndCharRect sets bRightMargin to true + * - SwTextCursor::bRightMargin is set to false by CharCursorToLine + */ + +namespace +{ + +SwTextFrame *GetAdjFrameAtPos( SwTextFrame *pFrame, const SwPosition &rPos, + const bool bRightMargin, const bool bNoScroll = true ) +{ + // RightMargin in the last master line + TextFrameIndex const nOffset = pFrame->MapModelToViewPos(rPos); + SwTextFrame *pFrameAtPos = pFrame; + if( !bNoScroll || pFrame->GetFollow() ) + { + pFrameAtPos = pFrame->GetFrameAtPos( rPos ); + if (nOffset < pFrameAtPos->GetOffset() && + !pFrameAtPos->IsFollow() ) + { + assert(pFrameAtPos->MapModelToViewPos(rPos) == nOffset); + TextFrameIndex nNew(nOffset); + if (nNew < TextFrameIndex(MIN_OFFSET_STEP)) + nNew = TextFrameIndex(0); + else + nNew -= TextFrameIndex(MIN_OFFSET_STEP); + sw_ChangeOffset( pFrameAtPos, nNew ); + } + } + while( pFrame != pFrameAtPos ) + { + pFrame = pFrameAtPos; + pFrame->GetFormatted(); + pFrameAtPos = pFrame->GetFrameAtPos( rPos ); + } + + if( nOffset && bRightMargin ) + { + while (pFrameAtPos && + pFrameAtPos->MapViewToModelPos(pFrameAtPos->GetOffset()) == rPos && + pFrameAtPos->IsFollow() ) + { + pFrameAtPos->GetFormatted(); + pFrameAtPos = pFrameAtPos->FindMaster(); + } + OSL_ENSURE( pFrameAtPos, "+GetCharRect: no frame with my rightmargin" ); + } + return pFrameAtPos ? pFrameAtPos : pFrame; +} + +} + +bool sw_ChangeOffset(SwTextFrame* pFrame, TextFrameIndex nNew) +{ + // Do not scroll in areas and outside of flies + OSL_ENSURE( !pFrame->IsFollow(), "Illegal Scrolling by Follow!" ); + if( pFrame->GetOffset() != nNew && !pFrame->IsInSct() ) + { + SwFlyFrame *pFly = pFrame->FindFlyFrame(); + // Attention: if e.g. in a column frame the size is still invalid + // we must not scroll around just like that + if ( ( pFly && pFly->isFrameAreaDefinitionValid() && + !pFly->GetNextLink() && !pFly->GetPrevLink() ) || + ( !pFly && pFrame->IsInTab() ) ) + { + SwViewShell* pVsh = pFrame->getRootFrame()->GetCurrShell(); + if( pVsh ) + { + if( pVsh->GetRingContainer().size() > 1 || + ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) ) + { + if( !pFrame->GetOffset() ) + return false; + nNew = TextFrameIndex(0); + } + pFrame->SetOffset( nNew ); + pFrame->SetPara( nullptr ); + pFrame->GetFormatted(); + if( pFrame->getFrameArea().HasArea() ) + pFrame->getRootFrame()->GetCurrShell()->InvalidateWindows( pFrame->getFrameArea() ); + return true; + } + } + } + return false; +} + +SwTextFrame& SwTextFrame::GetFrameAtOfst(TextFrameIndex const nWhere) +{ + SwTextFrame* pRet = this; + while( pRet->HasFollow() && nWhere >= pRet->GetFollow()->GetOffset() ) + pRet = pRet->GetFollow(); + return *pRet; +} + +SwTextFrame *SwTextFrame::GetFrameAtPos( const SwPosition &rPos ) +{ + TextFrameIndex const nPos(MapModelToViewPos(rPos)); + SwTextFrame *pFoll = this; + while( pFoll->GetFollow() ) + { + if (nPos > pFoll->GetFollow()->GetOffset()) + pFoll = pFoll->GetFollow(); + else + { + if (nPos == pFoll->GetFollow()->GetOffset() + && !SwTextCursor::IsRightMargin() ) + pFoll = pFoll->GetFollow(); + else + break; + } + } + return pFoll; +} + +/* + * GetCharRect() returns the char's char line described by aPos. + * GetModelPositionForViewPoint() does the reverse: It goes from a document coordinate to + * a Pam. + * Both are virtual in the frame base class and thus are redefined here. + */ + +bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos, + SwCursorMoveState *pCMS, bool bAllowFarAway ) const +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::GetCharRect with swapped frame" ); + + if( IsLocked() || IsHiddenNow() ) + return false; + + // Find the right frame first. We need to keep in mind that: + // - the cached information could be invalid (GetPara() == 0) + // - we could have a Follow + // - the Follow chain grows dynamically; the one we end up in + // needs to be formatted + + // Optimisation: reading ahead saves us a GetAdjFrameAtPos + const bool bRightMargin = pCMS && ( CursorMoveState::RightMargin == pCMS->m_eState ); + const bool bNoScroll = pCMS && pCMS->m_bNoScroll; + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), rPos, bRightMargin, + bNoScroll ); + pFrame->GetFormatted(); + + const SwFrame* pTmpFrame = pFrame->GetUpper(); + if (pTmpFrame->getFrameArea().Top() == FAR_AWAY && !bAllowFarAway) + return false; + + SwRectFnSet aRectFnSet(pFrame); + const SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame); + const SwTwips nFrameMaxY = aRectFnSet.GetPrtBottom(*pFrame); + + // nMaxY is an absolute value + SwTwips nMaxY = aRectFnSet.IsVert() ? + ( aRectFnSet.IsVertL2R() ? std::min( nFrameMaxY, nUpperMaxY ) : std::max( nFrameMaxY, nUpperMaxY ) ) : + std::min( nFrameMaxY, nUpperMaxY ); + + bool bRet = false; + + if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) ) + { + Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + SwTextNode const*const pTextNd(GetTextNodeForParaProps()); + short nFirstOffset; + pTextNd->GetFirstLineOfsWithNum( nFirstOffset ); + + Point aPnt2; + if ( aRectFnSet.IsVert() ) + { + if( nFirstOffset > 0 ) + aPnt1.AdjustY(nFirstOffset ); + if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() ) + aPnt1.setX( nMaxY ); + aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() ); + aPnt2.setY( aPnt1.Y() ); + if( aPnt2.X() < nMaxY ) + aPnt2.setX( nMaxY ); + } + else + { + if( nFirstOffset > 0 ) + aPnt1.AdjustX(nFirstOffset ); + + if( aPnt1.Y() > nMaxY ) + aPnt1.setY( nMaxY ); + aPnt2.setX( aPnt1.X() ); + aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() ); + if( aPnt2.Y() > nMaxY ) + aPnt2.setY( nMaxY ); + } + + rOrig = SwRect( aPnt1, aPnt2 ); + + if ( pCMS ) + { + pCMS->m_aRealHeight.setX( 0 ); + pCMS->m_aRealHeight.setY( aRectFnSet.IsVert() ? -rOrig.Width() : rOrig.Height() ); + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + bRet = true; + } + else + { + if( !pFrame->HasPara() ) + return false; + + SwFrameSwapper aSwapper( pFrame, true ); + if ( aRectFnSet.IsVert() ) + nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY ); + + bool bGoOn = true; + TextFrameIndex const nOffset = MapModelToViewPos(rPos); + assert(nOffset != TextFrameIndex(COMPLETE_STRING)); // not going to end well + TextFrameIndex nNextOfst; + + do + { + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + nNextOfst = aLine.GetEnd(); + // See comment in AdjustFrame + // Include the line's last char? + if (bRightMargin) + aLine.GetEndCharRect( &rOrig, nOffset, pCMS, nMaxY ); + else + aLine.GetCharRect( &rOrig, nOffset, pCMS, nMaxY ); + bRet = true; + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( rOrig ); + + if( pFrame->IsUndersized() && pCMS && !pFrame->GetNext() && + aRectFnSet.GetBottom(rOrig) == nUpperMaxY && + pFrame->GetOffset() < nOffset && + !pFrame->IsFollow() && !bNoScroll && + TextFrameIndex(pFrame->GetText().getLength()) != nNextOfst) + { + bGoOn = sw_ChangeOffset( pFrame, nNextOfst ); + } + else + bGoOn = false; + } while ( bGoOn ); + + if ( pCMS ) + { + if ( pFrame->IsRightToLeft() ) + { + if( pCMS->m_b2Lines && pCMS->m_p2Lines) + { + pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aLine ); + pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aPortion ); + } + } + + if ( aRectFnSet.IsVert() ) + { + if ( pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() ); + if ( pCMS->m_aRealHeight.Y() < 0 ) + { + // writing direction is from top to bottom + pCMS->m_aRealHeight.setX( rOrig.Width() - + pCMS->m_aRealHeight.X() + + pCMS->m_aRealHeight.Y() ); + } + } + if( pCMS->m_b2Lines && pCMS->m_p2Lines) + { + pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aLine ); + pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aPortion ); + } + } + + } + } + if( bRet ) + { + SwPageFrame *pPage = pFrame->FindPageFrame(); + OSL_ENSURE( pPage, "Text escaped from page?" ); + const SwTwips nOrigTop = aRectFnSet.GetTop(rOrig); + const SwTwips nPageTop = aRectFnSet.GetTop(pPage->getFrameArea()); + const SwTwips nPageBott = aRectFnSet.GetBottom(pPage->getFrameArea()); + + // We have the following situation: if the frame is in an invalid + // sectionframe, it's possible that the frame is outside the page. + // If we restrict the cursor position to the page area, we enforce + // the formatting of the page, of the section frame and the frame itself. + if( aRectFnSet.YDiff( nPageTop, nOrigTop ) > 0 ) + aRectFnSet.SetTop( rOrig, nPageTop ); + + if ( aRectFnSet.YDiff( nOrigTop, nPageBott ) > 0 ) + aRectFnSet.SetTop( rOrig, nPageBott ); + } + + return bRet; +} + +/* + * GetAutoPos() looks up the char's char line which is described by rPos + * and is used by the auto-positioned frame. + */ + +bool SwTextFrame::GetAutoPos( SwRect& rOrig, const SwPosition &rPos ) const +{ + if( IsHiddenNow() ) + return false; + + TextFrameIndex const nOffset = MapModelToViewPos(rPos); + SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset )); + + pFrame->GetFormatted(); + const SwFrame* pTmpFrame = pFrame->GetUpper(); + + SwRectFnSet aRectFnSet(pTmpFrame); + SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame); + + // nMaxY is in absolute value + SwTwips nMaxY; + if ( aRectFnSet.IsVert() ) + { + if ( aRectFnSet.IsVertL2R() ) + nMaxY = std::min( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY ); + else + nMaxY = std::max( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY ); + } + else + nMaxY = std::min( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY ); + if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) ) + { + Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + Point aPnt2; + if ( aRectFnSet.IsVert() ) + { + if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() ) + aPnt1.setX( nMaxY ); + + aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() ); + aPnt2.setY( aPnt1.Y() ); + if( aPnt2.X() < nMaxY ) + aPnt2.setX( nMaxY ); + } + else + { + if( aPnt1.Y() > nMaxY ) + aPnt1.setY( nMaxY ); + aPnt2.setX( aPnt1.X() ); + aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() ); + if( aPnt2.Y() > nMaxY ) + aPnt2.setY( nMaxY ); + } + rOrig = SwRect( aPnt1, aPnt2 ); + return true; + } + else + { + if( !pFrame->HasPara() ) + return false; + + SwFrameSwapper aSwapper( pFrame, true ); + if ( aRectFnSet.IsVert() ) + nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY ); + + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + aTmpState.m_bRealHeight = true; + aLine.GetCharRect( &rOrig, nOffset, &aTmpState, nMaxY ); + if( aTmpState.m_aRealHeight.X() >= 0 ) + { + rOrig.Pos().AdjustY(aTmpState.m_aRealHeight.X() ); + rOrig.Height( aTmpState.m_aRealHeight.Y() ); + } + + if ( pFrame->IsRightToLeft() ) + pFrame->SwitchLTRtoRTL( rOrig ); + + if ( aRectFnSet.IsVert() ) + pFrame->SwitchHorizontalToVertical( rOrig ); + + return true; + } +} + +/** determine top of line for given position in the text frame + + - Top of first paragraph line is the top of the printing area of the text frame + - If a proportional line spacing is applied use top of anchor character as + top of the line. +*/ +bool SwTextFrame::GetTopOfLine( SwTwips& _onTopOfLine, + const SwPosition& _rPos ) const +{ + bool bRet = true; + + // get position offset + TextFrameIndex const nOffset = MapModelToViewPos(_rPos); + + if (TextFrameIndex(GetText().getLength()) < nOffset) + { + bRet = false; + } + else + { + SwRectFnSet aRectFnSet(this); + if ( IsEmpty() || !aRectFnSet.GetHeight(getFramePrintArea()) ) + { + // consider upper space amount considered + // for previous frame and the page grid. + _onTopOfLine = aRectFnSet.GetPrtTop(*this); + } + else + { + // determine formatted text frame that contains the requested position + SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset )); + pFrame->GetFormatted(); + aRectFnSet.Refresh(pFrame); + // If proportional line spacing is applied + // to the text frame, the top of the anchor character is also the + // top of the line. + // Otherwise the line layout determines the top of the line + const SvxLineSpacingItem& rSpace = GetAttrSet()->GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + SwRect aCharRect; + if ( GetAutoPos( aCharRect, _rPos ) ) + { + _onTopOfLine = aRectFnSet.GetTop(aCharRect); + } + else + { + bRet = false; + } + } + else + { + // assure that text frame is in a horizontal layout + SwFrameSwapper aSwapper( pFrame, true ); + // determine text line that contains the requested position + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + aLine.CharCursorToLine( nOffset ); + // determine top of line + _onTopOfLine = aLine.Y(); + if ( aRectFnSet.IsVert() ) + { + _onTopOfLine = pFrame->SwitchHorizontalToVertical( _onTopOfLine ); + } + } + } + } + + return bRet; +} + +// Minimum distance of non-empty lines is a little less than 2 cm +#define FILL_MIN_DIST 1100 + +struct SwFillData +{ + SwRect aFrame; + const SwCursorMoveState *pCMS; + SwPosition* pPos; + const Point& rPoint; + SwTwips nLineWidth; + bool bFirstLine : 1; + bool bInner : 1; + bool bColumn : 1; + bool bEmpty : 1; + SwFillData( const SwCursorMoveState *pC, SwPosition* pP, const SwRect& rR, + const Point& rPt ) : aFrame( rR ), pCMS( pC ), pPos( pP ), rPoint( rPt ), + nLineWidth( 0 ), bFirstLine( true ), bInner( false ), bColumn( false ), + bEmpty( true ){} + SwFillMode Mode() const { return pCMS->m_pFill->eMode; } + tools::Long X() const { return rPoint.X(); } + tools::Long Y() const { return rPoint.Y(); } + tools::Long Left() const { return aFrame.Left(); } + tools::Long Right() const { return aFrame.Right(); } + tools::Long Bottom() const { return aFrame.Bottom(); } + SwFillCursorPos &Fill() const { return *pCMS->m_pFill; } + void SetTab( sal_uInt16 nNew ) { pCMS->m_pFill->nTabCnt = nNew; } + void SetSpace( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceCnt = nNew; } + void SetSpaceOnly( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceOnlyCnt = nNew; } + void SetOrient( const sal_Int16 eNew ){ pCMS->m_pFill->eOrient = eNew; } +}; + +bool SwTextFrame::GetModelPositionForViewPoint_(SwPosition* pPos, const Point& rPoint, + const bool bChgFrame, SwCursorMoveState* pCMS ) const +{ + // GetModelPositionForViewPoint_ is called by GetModelPositionForViewPoint and GetKeyCursorOfst. + // Never just a return false. + + if( IsLocked() || IsHiddenNow() ) + return false; + + const_cast<SwTextFrame*>(this)->GetFormatted(); + + Point aOldPoint( rPoint ); + + if ( IsVertical() ) + { + SwitchVerticalToHorizontal( const_cast<Point&>(rPoint) ); + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + } + + if ( IsRightToLeft() ) + SwitchRTLtoLTR( const_cast<Point&>(rPoint) ); + + std::unique_ptr<SwFillData> pFillData; + if ( pCMS && pCMS->m_pFill ) + pFillData.reset(new SwFillData( pCMS, pPos, getFrameArea(), rPoint )); + + if ( IsEmpty() ) + { + *pPos = MapViewToModelPos(TextFrameIndex(0)); + if( pCMS && pCMS->m_bFieldInfo ) + { + SwTwips nDiff = rPoint.X() - getFrameArea().Left() - getFramePrintArea().Left(); + if( nDiff > 50 || nDiff < 0 ) + pCMS->m_bPosCorr = true; + } + } + else + { + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + + // See comment in AdjustFrame() + SwTwips nMaxY = getFrameArea().Top() + getFramePrintArea().Top() + getFramePrintArea().Height(); + aLine.TwipsToLine( rPoint.Y() ); + while( aLine.Y() + aLine.GetLineHeight() > nMaxY ) + { + if( !aLine.Prev() ) + break; + } + + if( aLine.GetDropLines() >= aLine.GetLineNr() && 1 != aLine.GetLineNr() + && rPoint.X() < aLine.FirstLeft() + aLine.GetDropLeft() ) + while( aLine.GetLineNr() > 1 ) + aLine.Prev(); + + TextFrameIndex nOffset = aLine.GetModelPositionForViewPoint(pPos, rPoint, bChgFrame, pCMS); + + if( pCMS && pCMS->m_eState == CursorMoveState::NONE && aLine.GetEnd() == nOffset ) + pCMS->m_eState = CursorMoveState::RightMargin; + + // pPos is a pure IN parameter and must not be evaluated. + // pIter->GetModelPositionForViewPoint returns from a nesting with COMPLETE_STRING. + // If SwTextIter::GetModelPositionForViewPoint calls GetModelPositionForViewPoint further by itself + // nNode changes the position. + // In such cases, pPos must not be calculated. + if (TextFrameIndex(COMPLETE_STRING) != nOffset) + { + *pPos = MapViewToModelPos(nOffset); + if( pFillData ) + { + if (TextFrameIndex(GetText().getLength()) > nOffset || + rPoint.Y() < getFrameArea().Top() ) + pFillData->bInner = true; + pFillData->bFirstLine = aLine.GetLineNr() < 2; + if (GetText().getLength()) + { + pFillData->bEmpty = false; + pFillData->nLineWidth = aLine.GetCurr()->Width(); + } + } + } + } + bool bChgFillData = false; + if( pFillData && FindPageFrame()->getFrameArea().Contains( aOldPoint ) ) + { + FillCursorPos( *pFillData ); + bChgFillData = true; + } + + if ( IsVertical() ) + { + if ( bChgFillData ) + SwitchHorizontalToVertical( pFillData->Fill().aCursor.Pos() ); + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + } + + if ( IsRightToLeft() && bChgFillData ) + { + SwitchLTRtoRTL( pFillData->Fill().aCursor.Pos() ); + const sal_Int16 eOrient = pFillData->pCMS->m_pFill->eOrient; + + if ( text::HoriOrientation::LEFT == eOrient ) + pFillData->SetOrient( text::HoriOrientation::RIGHT ); + else if ( text::HoriOrientation::RIGHT == eOrient ) + pFillData->SetOrient( text::HoriOrientation::LEFT ); + } + + const_cast<Point&>(rPoint) = aOldPoint; + + return true; +} + +bool SwTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + const bool bChgFrame = !(pCMS && CursorMoveState::UpDown == pCMS->m_eState); + return GetModelPositionForViewPoint_( pPos, rPoint, bChgFrame, pCMS ); +} + +/* + * Layout-oriented cursor movement to the line start. + */ + +bool SwTextFrame::LeftMargin(SwPaM *pPam) const +{ + assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), + SwTextCursor::IsRightMargin() ); + pFrame->GetFormatted(); + TextFrameIndex nIndx; + if ( pFrame->IsEmpty() ) + nIndx = TextFrameIndex(0); + else + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + + aLine.CharCursorToLine(pFrame->MapModelToViewPos(*pPam->GetPoint())); + nIndx = aLine.GetStart(); + if( pFrame->GetOffset() && !pFrame->IsFollow() && !aLine.GetPrev() ) + { + sw_ChangeOffset(pFrame, TextFrameIndex(0)); + nIndx = TextFrameIndex(0); + } + } + *pPam->GetPoint() = pFrame->MapViewToModelPos(nIndx); + SwTextCursor::SetRightMargin( false ); + return true; +} + +/* + * To the line end: That's the position before the last char of the line. + * Exception: In the last line, it should be able to place the cursor after + * the last char in order to append text. + */ + +bool SwTextFrame::RightMargin(SwPaM *pPam, bool bAPI) const +{ + assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + + SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), + SwTextCursor::IsRightMargin() ); + pFrame->GetFormatted(); + TextFrameIndex nRightMargin(0); + if (!IsEmpty()) + { + SwTextSizeInfo aInf( pFrame ); + SwTextCursor aLine( pFrame, &aInf ); + + aLine.CharCursorToLine(MapModelToViewPos(*pPam->GetPoint())); + nRightMargin = aLine.GetStart() + aLine.GetCurr()->GetLen(); + + // We skip hard line breaks + if( aLine.GetCurr()->GetLen() && + CH_BREAK == aInf.GetText()[sal_Int32(nRightMargin) - 1]) + --nRightMargin; + else if( !bAPI && (aLine.GetNext() || pFrame->GetFollow()) ) + { + while( nRightMargin > aLine.GetStart() && + ' ' == aInf.GetText()[sal_Int32(nRightMargin) - 1]) + --nRightMargin; + } + } + *pPam->GetPoint() = pFrame->MapViewToModelPos(nRightMargin); + SwTextCursor::SetRightMargin( !bAPI ); + return true; +} + +// The following two methods try to put the Cursor into the next/successive +// line. If we do not have a preceding/successive line we forward the call +// to the base class. +// The Cursor's horizontal justification is done afterwards by the CursorShell. + +namespace { + +class SwSetToRightMargin +{ + bool m_bRight; + +public: + SwSetToRightMargin() + : m_bRight(false) + { + } + ~SwSetToRightMargin() { SwTextCursor::SetRightMargin(m_bRight); } + void SetRight(const bool bNew) { m_bRight = bNew; } +}; + +} + +bool SwTextFrame::UnitUp_( SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + // Set the RightMargin if needed + SwSetToRightMargin aSet; + + if( IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + // If the PaM is located within different boxes, we have a table selection, + // which is handled by the base class. + return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); + } + + const_cast<SwTextFrame*>(this)->GetFormatted(); + const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint()); + SwRect aCharBox; + + if( !IsEmpty() && !IsHiddenNow() ) + { + TextFrameIndex nFormat(COMPLETE_STRING); + do + { + if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow()) + sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat ); + + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + + // Optimize away flys with no flow and IsDummy() + if( nPos ) + aLine.CharCursorToLine( nPos ); + else + aLine.Top(); + + const SwLineLayout *pPrevLine = aLine.GetPrevLine(); + const TextFrameIndex nStart = aLine.GetStart(); + aLine.GetCharRect( &aCharBox, nPos ); + + bool bSecondOfDouble = ( aInf.IsMulti() && ! aInf.IsFirstMulti() ); + bool bPrevLine = ( pPrevLine && pPrevLine != aLine.GetCurr() ); + + if( !pPrevLine && !bSecondOfDouble && GetOffset() && !IsFollow() ) + { + nFormat = GetOffset(); + TextFrameIndex nDiff = aLine.GetLength(); + if( !nDiff ) + nDiff = TextFrameIndex(MIN_OFFSET_STEP); + if( nFormat > nDiff ) + nFormat = nFormat - nDiff; + else + nFormat = TextFrameIndex(0); + continue; + } + + // We select the target line for the cursor, in case we are in a + // double line portion, prev line = curr line + if( bPrevLine && !bSecondOfDouble ) + { + aLine.PrevLine(); + while ( aLine.GetStart() == nStart && + nullptr != ( pPrevLine = aLine.GetPrevLine() ) && + pPrevLine != aLine.GetCurr() ) + aLine.PrevLine(); + } + + if ( bPrevLine || bSecondOfDouble ) + { + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + aCharBox.Pos().setX( aCharBox.Pos().X() - 150 ); + + // See comment in SwTextFrame::GetModelPositionForViewPoint() +#if OSL_DEBUG_LEVEL > 0 + const SwNodeOffset nOldNode = pPam->GetPoint()->nNode.GetIndex(); +#endif + // The node should not be changed + TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint(pPam->GetPoint(), + aCharBox.Pos(), false ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( nOldNode == pPam->GetPoint()->nNode.GetIndex(), + "SwTextFrame::UnitUp: illegal node change" ); +#endif + + // We make sure that we move up. + if( nTmpOfst >= nStart && nStart && !bSecondOfDouble ) + { + nTmpOfst = nStart; + aSet.SetRight( true ); + } + *pPam->GetPoint() = MapViewToModelPos(nTmpOfst); + return true; + } + + if ( IsFollow() ) + { + aLine.GetCharRect( &aCharBox, nPos ); + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + } + break; + } while ( true ); + } + /* If 'this' is a follow and a prev failed, we need to go to the + * last line of the master, which is us. + * Or: If we are a follow with follow, we need to get the master. + */ + if ( IsFollow() ) + { + const SwTextFrame *pTmpPrev = FindMaster(); + TextFrameIndex nOffs = GetOffset(); + if( pTmpPrev ) + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bProtectedAllowed = pSh && pSh->GetViewOptions()->IsCursorInProtectedArea(); + const SwTextFrame *pPrevPrev = pTmpPrev; + // We skip protected frames and frames without content here + while( pPrevPrev && ( pPrevPrev->GetOffset() == nOffs || + ( !bProtectedAllowed && pPrevPrev->IsProtected() ) ) ) + { + pTmpPrev = pPrevPrev; + nOffs = pTmpPrev->GetOffset(); + if ( pPrevPrev->IsFollow() ) + pPrevPrev = pTmpPrev->FindMaster(); + else + pPrevPrev = nullptr; + } + if ( !pPrevPrev ) + return pTmpPrev->SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); + aCharBox.Pos().setY( pPrevPrev->getFrameArea().Bottom() - 1 ); + return pPrevPrev->GetKeyCursorOfst( pPam->GetPoint(), aCharBox.Pos() ); + } + } + return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly ); +} + +// Used for Bidi. nPos is the logical position in the string, bLeft indicates +// if left arrow or right arrow was pressed. The return values are: +// nPos: the new visual position +// bLeft: whether the break iterator has to add or subtract from the +// current position +static void lcl_VisualMoveRecursion(const SwLineLayout& rCurrLine, TextFrameIndex nIdx, + TextFrameIndex & nPos, bool& bRight, + sal_uInt8& nCursorLevel, sal_uInt8 nDefaultDir ) +{ + const SwLinePortion* pPor = rCurrLine.GetFirstPortion(); + const SwLinePortion* pLast = nullptr; + + // What's the current portion? + while ( pPor && nIdx + pPor->GetLen() <= nPos ) + { + nIdx = nIdx + pPor->GetLen(); + pLast = pPor; + pPor = pPor->GetNextPortion(); + } + + if ( bRight ) + { + bool bRecurse = pPor && pPor->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pPor)->IsBidi(); + + // 1. special case: at beginning of bidi portion + if ( bRecurse && nIdx == nPos ) + { + nPos = nPos + pPor->GetLen(); + + // leave bidi portion + if ( nCursorLevel != nDefaultDir ) + { + bRecurse = false; + } + else + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is between c and X in the buffer and cursor level = 0 + nCursorLevel++; + } + + // 2. special case: at beginning of portion after bidi portion + else if ( pLast && pLast->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos ) + { + // enter bidi portion + if ( nCursorLevel != nDefaultDir ) + { + bRecurse = true; + nIdx = nIdx - pLast->GetLen(); + pPor = pLast; + } + } + + // Recursion + if ( bRecurse ) + { + const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot(); + TextFrameIndex nTmpPos = nPos - nIdx; + bool bTmpForward = ! bRight; + sal_uInt8 nTmpCursorLevel = nCursorLevel; + lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward, + nTmpCursorLevel, nDefaultDir + 1 ); + + nPos = nTmpPos + nIdx; + bRight = bTmpForward; + nCursorLevel = nTmpCursorLevel; + } + + // go forward + else + { + bRight = true; + nCursorLevel = nDefaultDir; + } + + } + else + { + bool bRecurse = pPor && pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsBidi(); + + // 1. special case: at beginning of bidi portion + if ( bRecurse && nIdx == nPos ) + { + // leave bidi portion + if ( nCursorLevel == nDefaultDir ) + { + bRecurse = false; + } + } + + // 2. special case: at beginning of portion after bidi portion + else if ( pLast && pLast->IsMultiPortion() && + static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos ) + { + nPos = nPos - pLast->GetLen(); + + // enter bidi portion + if ( nCursorLevel % 2 == nDefaultDir % 2 ) + { + bRecurse = true; + nIdx = nIdx - pLast->GetLen(); + pPor = pLast; + + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is behind 3 in the buffer and cursor level = 2 + if ( nDefaultDir + 2 == nCursorLevel ) + nPos = nPos + pLast->GetLen(); + } + } + + // go forward + if ( bRecurse ) + { + const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot(); + TextFrameIndex nTmpPos = nPos - nIdx; + bool bTmpForward = ! bRight; + sal_uInt8 nTmpCursorLevel = nCursorLevel; + lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward, + nTmpCursorLevel, nDefaultDir + 1 ); + + // special case: + // buffer: abcXYZ123 in LTR paragraph + // view: abc123ZYX + // cursor is between Z and 1 in the buffer and cursor level = 2 + if ( nTmpPos == pPor->GetLen() && nTmpCursorLevel == nDefaultDir + 1 ) + { + nTmpPos = nTmpPos - pPor->GetLen(); + nTmpCursorLevel = nDefaultDir; + bTmpForward = ! bTmpForward; + } + + nPos = nTmpPos + nIdx; + bRight = bTmpForward; + nCursorLevel = nTmpCursorLevel; + } + + // go backward + else + { + bRight = false; + nCursorLevel = nDefaultDir; + } + } +} + +void SwTextFrame::PrepareVisualMove(TextFrameIndex & nPos, sal_uInt8& nCursorLevel, + bool& bForward, bool bInsertCursor ) +{ + if( IsEmpty() || IsHiddenNow() ) + return; + + GetFormatted(); + + SwTextSizeInfo aInf(this); + SwTextCursor aLine(this, &aInf); + + if( nPos ) + aLine.CharCursorToLine( nPos ); + else + aLine.Top(); + + const SwLineLayout* pLine = aLine.GetCurr(); + const TextFrameIndex nStt = aLine.GetStart(); + const TextFrameIndex nLen = pLine->GetLen(); + + // We have to distinguish between an insert and overwrite cursor: + // The insert cursor position depends on the cursor level: + // buffer: abcXYZdef in LTR paragraph + // display: abcZYXdef + // If cursor is between c and X in the buffer and cursor level is 0, + // the cursor blinks between c and Z and -> sets the cursor between Z and Y. + // If the cursor level is 1, the cursor blinks between X and d and + // -> sets the cursor between d and e. + // The overwrite cursor simply travels to the next visual character. + if ( bInsertCursor ) + { + lcl_VisualMoveRecursion( *pLine, nStt, nPos, bForward, + nCursorLevel, IsRightToLeft() ? 1 : 0 ); + return; + } + + const sal_uInt8 nDefaultDir = static_cast<sal_uInt8>(IsRightToLeft() ? UBIDI_RTL : UBIDI_LTR); + const bool bVisualRight = ( nDefaultDir == UBIDI_LTR && bForward ) || + ( nDefaultDir == UBIDI_RTL && ! bForward ); + + // Bidi functions from icu 2.0 + + const sal_Unicode* pLineString = GetText().getStr(); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( sal_Int32(nLen), 0, &nError ); + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(pLineString), + sal_Int32(nLen), nDefaultDir, nullptr, &nError ); + + TextFrameIndex nTmpPos(0); + bool bOutOfBounds = false; + + if ( nPos < nStt + nLen ) + { + nTmpPos = TextFrameIndex(ubidi_getVisualIndex( pBidi, sal_Int32(nPos), &nError )); + + // visual indices are always LTR aligned + if ( bVisualRight ) + { + if (nTmpPos + TextFrameIndex(1) < nStt + nLen) + ++nTmpPos; + else + { + nPos = nDefaultDir == UBIDI_RTL ? TextFrameIndex(0) : nStt + nLen; + bOutOfBounds = true; + } + } + else + { + if ( nTmpPos ) + --nTmpPos; + else + { + nPos = nDefaultDir == UBIDI_RTL ? nStt + nLen : TextFrameIndex(0); + bOutOfBounds = true; + } + } + } + else + { + nTmpPos = nDefaultDir == UBIDI_LTR ? nPos - TextFrameIndex(1) : TextFrameIndex(0); + } + + if ( ! bOutOfBounds ) + { + nPos = TextFrameIndex(ubidi_getLogicalIndex( pBidi, sal_Int32(nTmpPos), &nError )); + + if ( bForward ) + { + if ( nPos ) + --nPos; + else + { + ++nPos; + bForward = ! bForward; + } + } + else + ++nPos; + } + + ubidi_close( pBidi ); +} + +bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + + if ( IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + // If the PaM is located within different boxes, we have a table selection, + // which is handled by the base class. + return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); + } + const_cast<SwTextFrame*>(this)->GetFormatted(); + const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint()); + SwRect aCharBox; + const SwContentFrame *pTmpFollow = nullptr; + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + if ( !IsEmpty() && !IsHiddenNow() ) + { + TextFrameIndex nFormat(COMPLETE_STRING); + do + { + if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow() && + !sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat ) ) + break; + + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf ); + nFormat = aLine.GetEnd(); + + aLine.CharCursorToLine( nPos ); + + const SwLineLayout* pNextLine = aLine.GetNextLine(); + const TextFrameIndex nStart = aLine.GetStart(); + aLine.GetCharRect( &aCharBox, nPos ); + + bool bFirstOfDouble = ( aInf.IsMulti() && aInf.IsFirstMulti() ); + + if( pNextLine || bFirstOfDouble ) + { + aCharBox.Width( aCharBox.SSize().Width() / 2 ); +#if OSL_DEBUG_LEVEL > 0 + // See comment in SwTextFrame::GetModelPositionForViewPoint() + const SwNodeOffset nOldNode = pPam->GetPoint()->nNode.GetIndex(); +#endif + if ( pNextLine && ! bFirstOfDouble ) + aLine.NextLine(); + + TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint( pPam->GetPoint(), + aCharBox.Pos(), false ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( nOldNode == pPam->GetPoint()->nNode.GetIndex(), + "SwTextFrame::UnitDown: illegal node change" ); +#endif + + // We make sure that we move down. + if( nTmpOfst <= nStart && ! bFirstOfDouble ) + nTmpOfst = nStart + TextFrameIndex(1); + *pPam->GetPoint() = MapViewToModelPos(nTmpOfst); + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + return true; + } + pTmpFollow = GetFollow(); + if( nullptr != pTmpFollow ) + { // Skip protected follows + const SwContentFrame* pTmp = pTmpFollow; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( !pSh || !pSh->GetViewOptions()->IsCursorInProtectedArea() ) + { + while( pTmpFollow && pTmpFollow->IsProtected() ) + { + pTmp = pTmpFollow; + pTmpFollow = pTmpFollow->GetFollow(); + } + } + if( !pTmpFollow ) // Only protected ones left + { + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + return pTmp->SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); + } + + aLine.GetCharRect( &aCharBox, nPos ); + aCharBox.Width( aCharBox.SSize().Width() / 2 ); + } + else if( !IsFollow() ) + { + TextFrameIndex nTmpLen(aInf.GetText().getLength()); + if( aLine.GetEnd() < nTmpLen ) + { + if( nFormat <= GetOffset() ) + { + nFormat = std::min(GetOffset() + TextFrameIndex(MIN_OFFSET_STEP), + nTmpLen ); + if( nFormat <= GetOffset() ) + break; + } + continue; + } + } + break; + } while( true ); + } + else + pTmpFollow = GetFollow(); + + if ( IsVertical() ) + const_cast<SwTextFrame*>(this)->SwapWidthAndHeight(); + + // We take a shortcut for follows + if( pTmpFollow ) + { + aCharBox.Pos().setY( pTmpFollow->getFrameArea().Top() + 1 ); + return static_cast<const SwTextFrame*>(pTmpFollow)->GetKeyCursorOfst( pPam->GetPoint(), + aCharBox.Pos() ); + } + return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly ); +} + +bool SwTextFrame::UnitUp(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + /* We call ContentNode::GertFrame() in CursorSh::Up(). + * This _always returns the master. + * In order to not mess with cursor travelling, we correct here + * in SwTextFrame. + * We calculate UnitUp for pFrame. pFrame is either a master (= this) or a + * follow (!= this). + */ + const SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *(pPam->GetPoint()), + SwTextCursor::IsRightMargin() ); + const bool bRet = pFrame->UnitUp_( pPam, nOffset, bSetInReadOnly ); + + // No SwTextCursor::SetRightMargin( false ); + // Instead we have a SwSetToRightMargin in UnitUp_ + return bRet; +} + +bool SwTextFrame::UnitDown(SwPaM *pPam, const SwTwips nOffset, + bool bSetInReadOnly ) const +{ + const SwTextFrame *pFrame = GetAdjFrameAtPos(const_cast<SwTextFrame*>(this), *(pPam->GetPoint()), + SwTextCursor::IsRightMargin() ); + const bool bRet = pFrame->UnitDown_( pPam, nOffset, bSetInReadOnly ); + SwTextCursor::SetRightMargin( false ); + return bRet; +} + +void SwTextFrame::FillCursorPos( SwFillData& rFill ) const +{ + if( !rFill.bColumn && GetUpper()->IsColBodyFrame() ) // ColumnFrames now with BodyFrame + { + const SwColumnFrame* pTmp = + static_cast<const SwColumnFrame*>(GetUpper()->GetUpper()->GetUpper()->Lower()); // The 1st column + // The first SwFrame in BodyFrame of the first column + const SwFrame* pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower(); + sal_uInt16 nNextCol = 0; + // In which column do we end up in? + while( rFill.X() > pTmp->getFrameArea().Right() && pTmp->GetNext() ) + { + pTmp = static_cast<const SwColumnFrame*>(pTmp->GetNext()); + if( static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower() ) // ColumnFrames now with BodyFrame + { + pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower(); + nNextCol = 0; + } + else + ++nNextCol; // Empty columns require column breaks + } + if( pTmp != GetUpper()->GetUpper() ) // Did we end up in another column? + { + if( !pFrame ) + return; + if( nNextCol ) + { + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + } + else + { + while( pFrame->GetNext() && pFrame->getFrameArea().Bottom() < rFill.Y() ) + pFrame = pFrame->GetNext(); + } + // No filling, if the last frame in the targeted column does + // not contain a paragraph, but e.g. a table + if( pFrame->IsTextFrame() ) + { + rFill.Fill().nColumnCnt = nNextCol; + rFill.bColumn = true; + if( rFill.pPos ) + { + SwTextFrame const*const pTextFrame(static_cast<const SwTextFrame*>(pFrame)); + *rFill.pPos = pTextFrame->MapViewToModelPos( + TextFrameIndex(pTextFrame->GetText().getLength())); + } + if( nNextCol ) + { + rFill.aFrame = pTmp->getFramePrintArea(); + rFill.aFrame += pTmp->getFrameArea().Pos(); + } + else + rFill.aFrame = pFrame->getFrameArea(); + static_cast<const SwTextFrame*>(pFrame)->FillCursorPos( rFill ); + } + return; + } + } + std::unique_ptr<SwFont> pFnt; + SwTextFormatColl* pColl = GetTextNodeForParaProps()->GetTextColl(); + SwTwips nFirst = GetTextNodeForParaProps()->GetSwAttrSet().GetULSpace().GetLower(); + SwTwips nDiff = rFill.Y() - getFrameArea().Bottom(); + if( nDiff < nFirst ) + nDiff = -1; + else + pColl = &pColl->GetNextTextFormatColl(); + SwAttrSet aSet(const_cast<SwDoc&>(GetDoc()).GetAttrPool(), aTextFormatCollSetRange ); + const SwAttrSet* pSet = &pColl->GetAttrSet(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if (GetTextNodeForParaProps()->HasSwAttrSet()) + { + // sw_redlinehide: pSet is mostly used for para props, but there are + // accesses to char props via pFnt - why does it use only the node's + // props for this, and not hints? + aSet.Put( *GetTextNodeForParaProps()->GetpSwAttrSet() ); + aSet.SetParent( pSet ); + pSet = &aSet; + pFnt.reset(new SwFont( pSet, &GetDoc().getIDocumentSettingAccess() )); + } + else + { + SwFontAccess aFontAccess( pColl, pSh ); + pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() )); + pFnt->CheckFontCacheId( pSh, pFnt->GetActual() ); + } + OutputDevice* pOut = pSh->GetOut(); + if( !pSh->GetViewOptions()->getBrowseMode() || pSh->GetViewOptions()->IsPrtFormat() ) + pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true ); + + pFnt->SetFntChg( true ); + pFnt->ChgPhysFnt( pSh, *pOut ); + + SwTwips nLineHeight = pFnt->GetHeight( pSh, *pOut ); + + bool bFill = false; + if( nLineHeight ) + { + bFill = true; + const SvxULSpaceItem &rUL = pSet->GetULSpace(); + SwTwips nDist = std::max( rUL.GetLower(), rUL.GetUpper() ); + if( rFill.Fill().nColumnCnt ) + { + rFill.aFrame.Height( nLineHeight ); + nDiff = rFill.Y() - rFill.Bottom(); + nFirst = 0; + } + else if( nDist < nFirst ) + nFirst = nFirst - nDist; + else + nFirst = 0; + nDist = std::max( nDist, SwTwips(GetLineSpace()) ); + nDist += nLineHeight; + nDiff -= nFirst; + + if( nDiff > 0 ) + { + nDiff /= nDist; + rFill.Fill().nParaCnt = o3tl::narrowing<sal_uInt16>(nDiff + 1); + rFill.nLineWidth = 0; + rFill.bInner = false; + rFill.bEmpty = true; + rFill.SetOrient( text::HoriOrientation::LEFT ); + } + else + nDiff = -1; + if( rFill.bInner ) + bFill = false; + else + { + const SvxTabStopItem &rRuler = pSet->GetTabStops(); + const SvxLRSpaceItem &rLRSpace = pSet->GetLRSpace(); + + SwRect &rRect = rFill.Fill().aCursor; + rRect.Top( rFill.Bottom() + (nDiff+1) * nDist - nLineHeight ); + if( nFirst && nDiff > -1 ) + rRect.Top( rRect.Top() + nFirst ); + rRect.Height( nLineHeight ); + SwTwips nLeft = rFill.Left() + rLRSpace.GetLeft() + + GetTextNodeForParaProps()->GetLeftMarginWithNum(); + SwTwips nRight = rFill.Right() - rLRSpace.GetRight(); + SwTwips nCenter = ( nLeft + nRight ) / 2; + rRect.Left( nLeft ); + if( SwFillMode::Margin == rFill.Mode() ) + { + if( rFill.bEmpty ) + { + rFill.SetOrient( text::HoriOrientation::LEFT ); + if( rFill.X() < nCenter ) + { + if( rFill.X() > ( nLeft + 2 * nCenter ) / 3 ) + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rRect.Left( nCenter ); + } + } + else if( rFill.X() > ( nRight + 2 * nCenter ) / 3 ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + } + else + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rRect.Left( nCenter ); + } + } + else + bFill = false; + } + else + { + SwTwips nSpace = 0; + if( SwFillMode::Tab != rFill.Mode() ) + { + SwDrawTextInfo aDrawInf( pSh, *pOut, " ", 0, 2 ); + nSpace = pFnt->GetTextSize_( aDrawInf ).Width()/2; + } + if( rFill.X() >= nRight ) + { + if( SwFillMode::Indent != rFill.Mode() && ( rFill.bEmpty || + rFill.X() > rFill.nLineWidth + FILL_MIN_DIST ) ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + } + else + bFill = false; + } + else if( SwFillMode::Indent == rFill.Mode() ) + { + SwTwips nIndent = rFill.X(); + if( !rFill.bEmpty || nIndent > nRight ) + bFill = false; + else + { + nIndent -= rFill.Left(); + if( nIndent >= 0 && nSpace ) + { + nIndent /= nSpace; + nIndent *= nSpace; + rFill.SetTab( sal_uInt16( nIndent ) ); + rRect.Left( nIndent + rFill.Left() ); + } + else + bFill = false; + } + } + else if( rFill.X() > nLeft ) + { + SwTwips nTextLeft = rFill.Left() + rLRSpace.GetTextLeft() + + GetTextNodeForParaProps()->GetLeftMarginWithNum(true); + rFill.nLineWidth += rFill.bFirstLine ? nLeft : nTextLeft; + SwTwips nLeftTab; + SwTwips nRightTab = nLeft; + sal_uInt16 nSpaceCnt = 0; + sal_uInt16 nSpaceOnlyCnt = 0; + sal_uInt16 nTabCnt = 0; + sal_uInt16 nIdx = 0; + do + { + nLeftTab = nRightTab; + if( nIdx < rRuler.Count() ) + { + const SvxTabStop &rTabStop = rRuler.operator[](nIdx); + nRightTab = nTextLeft + rTabStop.GetTabPos(); + if( nLeftTab < nTextLeft && nRightTab > nTextLeft ) + nRightTab = nTextLeft; + else + ++nIdx; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + } + else + { + const SvxTabStopItem& rTab = + pSet->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP ); + const SwTwips nDefTabDist = rTab[0].GetTabPos(); + nRightTab = nLeftTab - nTextLeft; + nRightTab /= nDefTabDist; + nRightTab = nRightTab * nDefTabDist + nTextLeft; + while ( nRightTab <= nLeftTab ) + nRightTab += nDefTabDist; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + while ( nRightTab < rFill.X() ) + { + nRightTab += nDefTabDist; + if( nRightTab > rFill.nLineWidth ) + ++nTabCnt; + } + if( nLeftTab < nRightTab - nDefTabDist ) + nLeftTab = nRightTab - nDefTabDist; + } + if( nRightTab > nRight ) + nRightTab = nRight; + } + while( rFill.X() > nRightTab ); + --nTabCnt; + if( SwFillMode::TabSpace == rFill.Mode() ) + { + if( nSpace > 0 ) + { + if( !nTabCnt ) + nLeftTab = rFill.nLineWidth; + while( nLeftTab < rFill.X() ) + { + nLeftTab += nSpace; + ++nSpaceCnt; + } + if( nSpaceCnt ) + { + nLeftTab -= nSpace; + --nSpaceCnt; + } + if( rFill.X() - nLeftTab > nRightTab - rFill.X() ) + { + nSpaceCnt = 0; + ++nTabCnt; + rRect.Left( nRightTab ); + } + else + { + if( rFill.X() - nLeftTab > nSpace/2 ) + { + ++nSpaceCnt; + rRect.Left( nLeftTab + nSpace ); + } + else + rRect.Left( nLeftTab ); + } + } + else if( rFill.X() - nLeftTab < nRightTab - rFill.X() ) + rRect.Left( nLeftTab ); + else + { + if( nRightTab >= nRight ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + nTabCnt = 0; + nSpaceCnt = 0; + } + else + { + rRect.Left( nRightTab ); + ++nTabCnt; + } + } + } + else if( SwFillMode::Space == rFill.Mode() ) + { + SwTwips nLeftSpace = nLeft; + while( nLeftSpace < rFill.X() ) + { + nLeftSpace += nSpace; + ++nSpaceOnlyCnt; + } + rRect.Left( nLeftSpace ); + } + else + { + if( rFill.X() - nLeftTab < nRightTab - rFill.X() ) + rRect.Left( nLeftTab ); + else + { + if( nRightTab >= nRight ) + { + rFill.SetOrient( text::HoriOrientation::RIGHT ); + rRect.Left( nRight ); + nTabCnt = 0; + nSpaceCnt = 0; + } + else + { + rRect.Left( nRightTab ); + ++nTabCnt; + } + } + } + rFill.SetTab( nTabCnt ); + rFill.SetSpace( nSpaceCnt ); + rFill.SetSpaceOnly( nSpaceOnlyCnt ); + if( bFill ) + { + if( std::abs( rFill.X() - nCenter ) <= + std::abs( rFill.X() - rRect.Left() ) ) + { + rFill.SetOrient( text::HoriOrientation::CENTER ); + rFill.SetTab( 0 ); + rFill.SetSpace( 0 ); + rFill.SetSpaceOnly( 0 ); + rRect.Left( nCenter ); + } + if( !rFill.bEmpty ) + rFill.nLineWidth += FILL_MIN_DIST; + if( rRect.Left() < rFill.nLineWidth ) + bFill = false; + } + } + } + // Do we extend over the page's/column's/etc. lower edge? + const SwFrame* pUp = GetUpper(); + if( pUp->IsInSct() ) + { + if( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + else if( pUp->IsColBodyFrame() && + pUp->GetUpper()->GetUpper()->IsSctFrame() ) + pUp = pUp->GetUpper()->GetUpper()->GetUpper(); + } + SwRectFnSet aRectFnSet(this); + SwTwips nLimit = aRectFnSet.GetPrtBottom(*pUp); + SwTwips nRectBottom = rRect.Bottom(); + if ( aRectFnSet.IsVert() ) + nRectBottom = SwitchHorizontalToVertical( nRectBottom ); + + if( aRectFnSet.YDiff( nLimit, nRectBottom ) < 0 ) + bFill = false; + else + rRect.Width( 1 ); + } + } + const_cast<SwCursorMoveState*>(rFill.pCMS)->m_bFillRet = bFill; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |