2f0c7d5691
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 commitb5e275f47a
"tdf#106733 sw: implement CharNoHyphenation" and commit73bd04a71e
"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>
1733 lines
63 KiB
C++
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: */
|