office-gobmx/sw/source/core/text/frmcrsr.cxx
László Németh 2f0c7d5691 tdf#161563 tdf#161565 sw: add No Break to word context menu & visualize
Add No Break option to context menu of words hyphenated automatically,
giving as easy access to fix paragraph layout, as context menu of
misspelled words – like DTP software do. Also add this option to context
menu of words with enabled "No Break" to disable it.

To avoid unwanted paragraph layout during further text editing or
formatting, visualize words excluded from hyphenation with a light
gray dotted underline, when Formatting Marks is enabled.

Follow-up to commit b5e275f47a
"tdf#106733 sw: implement CharNoHyphenation" and
commit 73bd04a71e
"tdf#106733 xmloff: keep fo:hyphenate in character formatting".

Change-Id: I81bb410abcf999c8d9a3dca28acfc5c21aa0f260
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168827
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
2024-06-14 10:10:38 +02:00

1733 lines
63 KiB
C++

/* -*- 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: */