/* -*- 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())
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();
assert(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->GetPointNode() == 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;
}
bool SwTextFrame::IsInHyphenatedWord(SwPaM *pPam, bool bSelection) const
{
assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep()));
SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(),
SwTextCursor::IsRightMargin() );
pFrame->GetFormatted();
if (!IsEmpty())
{
SwTextSizeInfo aInf( pFrame );
SwTextCursor aLine( pFrame, &aInf );
TextFrameIndex const nCursorPos(MapModelToViewPos(*pPam->GetPoint()));
aLine.CharCursorToLine(nCursorPos);
if ( aLine.GetCurr()->IsEndHyph() )
{
TextFrameIndex nPos(aLine.GetStart() + aLine.GetCurr()->GetLen());
while( nPos > nCursorPos && ' ' != aInf.GetText()[sal_Int32(nPos) - 1] )
--nPos;
if ( nPos == nCursorPos && ( bSelection ||
// without selection, the cursor must be inside the word, not before that
// to apply the character formatting, as usual
( nPos > aLine.GetStart() && ' ' != aInf.GetText()[sal_Int32(nPos) - 1] ) ) )
return true;
}
// the hyphenated word starts in the previous line
if ( aLine.GetStart() > TextFrameIndex(0) )
{
TextFrameIndex nPos(aLine.GetStart());
aLine.CharCursorToLine(nPos - TextFrameIndex(1));
if ( aLine.GetCurr()->IsEndHyph() )
{
while( nPos < nCursorPos && ' ' != aInf.GetText()[sal_Int32(nPos)] )
++nPos;
if ( nPos == nCursorPos &&
( bSelection || ' ' != aInf.GetText()[sal_Int32(nPos)] ) )
return true;
}
}
}
return false;
}
/*
* 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->GetPointNode() == 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->GetPointNode().StartOfSectionNode() !=
pPam->GetMarkNode().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()->GetNodeIndex();
#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()->GetNodeIndex(),
"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->GetPointNode().StartOfSectionNode() !=
pPam->GetMarkNode().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()->GetNodeIndex();
#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()->GetNodeIndex(),
"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 SvxFirstLineIndentItem& rFirstLine(pSet->GetFirstLineIndent());
const SvxTextLeftMarginItem& rTextLeftMargin(pSet->GetTextLeftMargin());
const SvxRightMarginItem& rRightMargin(pSet->GetRightMargin());
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() + rTextLeftMargin.GetLeft(rFirstLine) +
GetTextNodeForParaProps()->GetLeftMarginWithNum();
SwTwips nRight = rFill.Right() - rRightMargin.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, u" "_ustr, 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() + rTextLeftMargin.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()->GetUserOrPoolDefaultItem( 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: */
↑ V547 Expression 'bRet' is always true.
↑ V547 Expression 'bFill' is always true.
↑ V614 Potentially null smart pointer 'pFillData' used.