tdf#43100 tdf#104683 tdf#120715 sw: cursor on spaces over margin
Allow cursor movement on spaces over margin (or clicking it
it to position the cursor), like MSO does instead of stopping
cursor before the stripped (from the typesetting) spaces.
This way it's possible to follow the modification of these
characters, e.g. removing spaces or inserting other characters
at the cursor position.
Follow-up to commit 8741fd0e0ae9e346de2e09887f0668b831c9b48b
"tdf#43244 sw: show stripped line-end spaces on margin".
Details: extend SwHolePortion to calculate its width,
that can be used for cursor movement, even over the right
margin. Removed some code that not allowed cursor to be
positioned over the right margin. Layout calculation uses
m_nWidth variable (that was 0) to make frames. To keep the
layout unchanged, this new width calculated into m_nExtraBlankWidth temporarily, and this extra width is added to m_nWidth only
after the layout calculation is finished. (Ideally this 2 width
values could be stored and used separately, but that would require
a bigger refactor of the cursor calculation.)
Known regression: lost selection at Search & Replace, e.g. searching
double spaces: there was a narrow selection at end of the line
showing the position.
Other issues: when a different character inserted on the margin
resulting new line break, space formatting marks aren't updated
according to the less spaces. Removing the inserted character by
Backspace results losing of the cursor movement on the spaces.
Co-authored-by: Tibor Nagy (NISZ)
Change-Id: I342b3ac68bef508389f1e227e52a03a22919301e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136613
Reviewed-by: László Németh <nemeth@numbertext.org>
Tested-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/core/text/data/tdf43100_tdf120715_cursorOnSpacesOverMargin.docx b/sw/qa/core/text/data/tdf43100_tdf120715_cursorOnSpacesOverMargin.docx
new file mode 100644
index 0000000..474d805
--- /dev/null
+++ b/sw/qa/core/text/data/tdf43100_tdf120715_cursorOnSpacesOverMargin.docx
Binary files differ
diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx
index 2db4d6d..99d80f6 100644
--- a/sw/qa/core/text/text.cxx
+++ b/sw/qa/core/text/text.cxx
@@ -470,6 +470,74 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testRedlineDelete)
pDoc->getIDocumentRedlineAccess().GetRedlineTable().size());
}
CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf120715_CursorMoveWhenTypingSpaceAtCenteredLineEnd)
{
SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf43100_tdf120715_cursorOnSpacesOverMargin.docx");
SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
// Make a paint to force the call of AddExtraBlankWidth, that calculate width for holePortions.
pDoc->GetDocShell()->GetPreviewBitmap();
// Move the cursor to the last character of the document.
pWrtShell->EndOfSection();
//Press space and check if the cursor move right with the additional space.
sal_Int32 nOldCursorPos = pWrtShell->GetCharRect().Left();
pWrtShell->Insert(" ");
sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
CPPUNIT_ASSERT_GREATER(nOldCursorPos, nNewCursorPos);
}
CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf43100_CursorMoveToSpacesOverMargin)
{
// Test the cursor movement over the right margin in several different paragraphs.
// These differences are based on its paragraphs
// - alignment (left, center, right, justified),
// - line count (1 line, 2 lines, blank line containing only spaces)
SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf43100_tdf120715_cursorOnSpacesOverMargin.docx");
SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
// Make a paint to force the call of AddExtraBlankWidth, that calculate width for holePortions.
pDoc->GetDocShell()->GetPreviewBitmap();
// Move the cursor to the 2. line.
pWrtShell->Down(/*bSelect=*/false, 1, /*bBasicCall=*/false);
// Move the cursor to the right margin.
pWrtShell->RightMargin(false, false);
sal_Int32 nMarginPos = pWrtShell->GetCharRect().Left();
sal_Int32 nLastCursorPos = nMarginPos;
// Move the cursor right 5 times, every step should increase the cursor x position.
// Before this fix, the cursor stopped at the margin.
for (int i = 0; i < 5; i++)
{
pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false);
sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
CPPUNIT_ASSERT_GREATER(nLastCursorPos, nNewCursorPos);
nLastCursorPos = nNewCursorPos;
}
// Move down the cursor several lines, and check if it will keep nearly its horizontal position.
// Some of the lines are not reach beyond the margin, there the cursor won't be able to keep its
// original position.
bool aLineReachOverMargin[] = { false, true, true, false, false, true, true, false, true,
true, true, true, false, true, true, false, false };
// Cursor position can be a bit inaccurate, because it can only be positioned on characters,
// that is based on the actual line layout, therefore the actual cursor position
// is checked against a more distinct position instead of the nMarginPos.
sal_Int32 nAvgLeft = (nMarginPos + nLastCursorPos) / 2;
for (int i = 2; i < 17; i++)
{
pWrtShell->Down(/*bSelect=*/false, 1, /*bBasicCall=*/false);
sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
if (aLineReachOverMargin[i])
CPPUNIT_ASSERT_GREATER(nAvgLeft, nNewCursorPos);
else
CPPUNIT_ASSERT_LESS(nAvgLeft, nNewCursorPos);
}
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index d469083..34681c1 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -100,6 +100,9 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
TextFrameIndex nCharsCnt = nMaxLen - nSpaceCnt;
if ( nSpaceCnt && nCharsCnt < rPor.GetLen() )
{
if (nSpaceCnt)
rInf.GetTextSize( &rSI, rInf.GetIdx() + nCharsCnt, nSpaceCnt,
nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff );
nMaxLen = nCharsCnt;
if ( !nMaxLen )
return true;
@@ -611,6 +614,13 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
else
m_nBreakWidth = 0;
if (m_nBreakStart > rInf.GetIdx() + nPorLen + m_nFieldDiff)
{
rInf.GetTextSize(&rSI, rInf.GetIdx() + nPorLen,
m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff, nMaxComp,
m_nExtraBlankWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
}
if( m_pHanging )
{
m_nBreakPos = m_nCutPos;
diff --git a/sw/source/core/text/guess.hxx b/sw/source/core/text/guess.hxx
index 696a09f..f83c7e2 100644
--- a/sw/source/core/text/guess.hxx
+++ b/sw/source/core/text/guess.hxx
@@ -38,9 +38,10 @@ class SwTextGuess
TextFrameIndex m_nFieldDiff; // absolute positions can be wrong if we
// a field in the text has been expanded
sal_uInt16 m_nBreakWidth; // width of the broken portion
sal_uInt16 m_nExtraBlankWidth; // width of spaces after the break
public:
SwTextGuess(): m_nCutPos(0), m_nBreakStart(0),
m_nBreakPos(0), m_nFieldDiff(0), m_nBreakWidth(0)
m_nBreakPos(0), m_nFieldDiff(0), m_nBreakWidth(0), m_nExtraBlankWidth(0)
{ }
// true, if current portion still fits to current line
@@ -51,6 +52,7 @@ public:
SwHangingPortion* GetHangingPortion() const { return m_pHanging.get(); }
SwHangingPortion* ReleaseHangingPortion() { return m_pHanging.release(); }
sal_uInt16 BreakWidth() const { return m_nBreakWidth; }
sal_uInt16 ExtraBlankWidth() const { return m_nExtraBlankWidth; }
TextFrameIndex CutPos() const { return m_nCutPos; }
TextFrameIndex BreakStart() const { return m_nBreakStart; }
TextFrameIndex BreakPos() const {return m_nBreakPos; }
diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx
index 546d91b..b2a0b3b 100644
--- a/sw/source/core/text/itrcrsr.cxx
+++ b/sw/source/core/text/itrcrsr.cxx
@@ -401,6 +401,26 @@ void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *p
// GetInfo().SetOut( GetInfo().GetWin() );
}
// tdf#120715 tdf#43100: Make width for some HolePortions, so cursor will be able to move into it.
// It should not change the layout, so this should be called after the layout is calculated.
void SwTextCursor::AddExtraBlankWidth()
{
SwLinePortion* pPos = m_pCurr->GetNextPortion();
SwLinePortion* pNextPos;
while (pPos)
{
pNextPos = pPos->GetNextPortion();
// Do it only if it is the last portion that able to handle the cursor,
// else the next portion would misscalculate the cursor position
if (pPos->ExtraBlankWidth() && (!pNextPos || pNextPos->IsMarginPortion()))
{
pPos->Width(pPos->Width() + pPos->ExtraBlankWidth());
pPos->ExtraBlankWidth(0);
}
pPos = pNextPos;
}
}
// 1170: Ancient bug: Shift-End forgets the last character ...
void SwTextCursor::GetEndCharRect(SwRect* pOrig, const TextFrameIndex nOfst,
SwCursorMoveState* pCMS, const tools::Long nMax )
@@ -1213,10 +1233,6 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst,
GetCharRect_( pOrig, nFindOfst, pCMS );
// This actually would have to be "-1 LogicToPixel", but that seems too
// expensive, so it's a value (-12), that should hopefully be OK.
const SwTwips nTmpRight = Right() - 12;
pOrig->Pos().AdjustX(aCharPos.X() );
pOrig->Pos().AdjustY(aCharPos.Y() );
@@ -1228,13 +1244,6 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst,
pCMS->m_p2Lines->aPortion.Pos().AdjustY(aCharPos.Y() );
}
const IDocumentSettingAccess& rIDSA = GetTextFrame()->GetDoc().getIDocumentSettingAccess();
const bool bTabOverMargin = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN)
|| rIDSA.get(DocumentSettingId::TAB_OVER_SPACING);
// Make sure the cursor respects the right margin, unless in compat mode, where the tab size has priority over the margin size.
if( pOrig->Left() > nTmpRight && !bTabOverMargin)
pOrig->Pos().setX( nTmpRight );
if( nMax )
{
if( pOrig->Top() + pOrig->Height() > nMax )
@@ -1255,16 +1264,6 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst,
pCMS->m_aRealHeight.setY( nMax - nTmp );
}
}
tools::Long nOut = pOrig->Right() - GetTextFrame()->getFrameArea().Right();
if( nOut > 0 )
{
if( GetTextFrame()->getFrameArea().Width() < GetTextFrame()->getFramePrintArea().Left()
+ GetTextFrame()->getFramePrintArea().Width() )
nOut += GetTextFrame()->getFrameArea().Width() - GetTextFrame()->getFramePrintArea().Left()
- GetTextFrame()->getFramePrintArea().Width();
if( nOut > 0 )
pOrig->Pos().AdjustX( -(nOut + 10) );
}
}
/**
@@ -1320,9 +1319,6 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con
if( bLeftOver )
x = nLeftMargin;
const bool bRightOver = x > nRightMargin;
if( bRightOver )
x = nRightMargin;
const bool bRightAllowed = pCMS && ( pCMS->m_eState == CursorMoveState::NONE );
// Until here everything in document coordinates.
@@ -1647,7 +1643,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con
return GetModelPositionForViewPoint( pPos, Point( GetLineStart() + nX, rPoint.Y() ),
bChgNode, pCMS );
}
if( pPor->InTextGrp() )
if( pPor->InTextGrp() || pPor->IsHolePortion() )
{
sal_uInt8 nOldProp;
if( GetPropFont() )
@@ -1660,7 +1656,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con
{
SwTextSizeInfo aSizeInf( GetInfo(), &rText, nCurrStart );
const_cast<SwTextCursor*>(this)->SeekAndChg( aSizeInf );
SwTextSlot aDiffText( &aSizeInf, static_cast<SwTextPortion*>(pPor), false, false );
SwTextSlot aDiffText( &aSizeInf, pPor, false, false );
SwFontSave aSave( aSizeInf, pPor->IsDropPortion() ?
static_cast<SwDropPortion*>(pPor)->GetFnt() : nullptr );
diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx
index 3935630..a66d358 100644
--- a/sw/source/core/text/itrpaint.cxx
+++ b/sw/source/core/text/itrpaint.cxx
@@ -127,6 +127,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip,
// maybe catch-up adjustment
GetAdjusted();
AddExtraBlankWidth();
GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
GetInfo().ResetSpaceIdx();
GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
diff --git a/sw/source/core/text/itrtxt.hxx b/sw/source/core/text/itrtxt.hxx
index f36932d..0b48e3b 100644
--- a/sw/source/core/text/itrtxt.hxx
+++ b/sw/source/core/text/itrtxt.hxx
@@ -270,6 +270,7 @@ class SwTextCursor : public SwTextAdjuster
protected:
void CtorInitTextCursor( SwTextFrame *pFrame, SwTextSizeInfo *pInf );
explicit SwTextCursor(SwTextNode const * pTextNode) : SwTextAdjuster(pTextNode) { }
void AddExtraBlankWidth();
public:
SwTextCursor( SwTextFrame *pTextFrame, SwTextSizeInfo *pTextSizeInf )
: SwTextAdjuster(pTextFrame->GetTextNodeFirst())
diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx
index 3cd1d9f..5ce25a3 100644
--- a/sw/source/core/text/porlin.hxx
+++ b/sw/source/core/text/porlin.hxx
@@ -63,6 +63,7 @@ private:
PortionType mnWhichPor; // Who's who?
bool m_bJoinBorderWithPrev;
bool m_bJoinBorderWithNext;
SwTwips m_nExtraBlankWidth = 0; // width of spaces after the break
void Truncate_();
@@ -83,6 +84,8 @@ public:
SwTwips PrtWidth() const { return Width(); }
void AddPrtWidth( const SwTwips nNew ) { Width( Width() + nNew ); }
void SubPrtWidth( const SwTwips nNew ) { Width( Width() - nNew ); }
SwTwips ExtraBlankWidth() const { return m_nExtraBlankWidth; }
void ExtraBlankWidth(const SwTwips nNew) { m_nExtraBlankWidth = nNew; }
SwTwips GetHangingBaseline() const { return mnHangingBaseline; }
void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline = nNewBaseline; }
@@ -191,6 +194,7 @@ inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion)
mnWhichPor = rPortion.mnWhichPor;
m_bJoinBorderWithPrev = rPortion.m_bJoinBorderWithPrev;
m_bJoinBorderWithNext = rPortion.m_bJoinBorderWithNext;
m_nExtraBlankWidth = rPortion.m_nExtraBlankWidth;
return *this;
}
@@ -202,7 +206,8 @@ inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) :
mnHangingBaseline( rPortion.mnHangingBaseline ),
mnWhichPor( rPortion.mnWhichPor ),
m_bJoinBorderWithPrev( rPortion.m_bJoinBorderWithPrev ),
m_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext )
m_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext ),
m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth)
{
}
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index a5ae0ea..c0f6649 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -321,6 +321,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
if ( !bFull )
{
Width( aGuess.BreakWidth() );
ExtraBlankWidth(aGuess.ExtraBlankWidth());
// Caution!
if( !InExpGrp() || InFieldGrp() )
SetLen( rInf.GetLen() );
@@ -409,6 +410,8 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
{
SwHolePortion *pNew = new SwHolePortion( *this );
pNew->SetLen( nRealStart - aGuess.BreakPos() );
pNew->Width(0);
pNew->ExtraBlankWidth( aGuess.ExtraBlankWidth() );
Insert( pNew );
}
}
@@ -749,12 +752,29 @@ SwHolePortion::SwHolePortion( const SwTextPortion &rPor )
{
SetLen( TextFrameIndex(1) );
Height( rPor.Height() );
Width(0);
SetAscent( rPor.GetAscent() );
SetWhichPor( PortionType::Hole );
}
SwLinePortion *SwHolePortion::Compress() { return this; }
// The GetTextSize() assumes that the own length is correct
SwPosSize SwHolePortion::GetTextSize(const SwTextSizeInfo& rInf) const
{
SwPosSize aSize = rInf.GetTextSize();
if (!GetJoinBorderWithPrev())
aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace());
if (!GetJoinBorderWithNext())
aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace());
aSize.Height(aSize.Height() +
rInf.GetFont()->GetTopBorderSpace() +
rInf.GetFont()->GetBottomBorderSpace());
return aSize;
}
void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const
{
if( !rInf.GetOut() )
diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx
index 77ec0a9..a30f6f0 100644
--- a/sw/source/core/text/portxt.hxx
+++ b/sw/source/core/text/portxt.hxx
@@ -69,6 +69,7 @@ public:
void SetBlankWidth( const sal_uInt16 nNew ) { m_nBlankWidth = nNew; }
virtual SwLinePortion *Compress() override;
virtual bool Format( SwTextFormatInfo &rInf ) override;
virtual SwPosSize GetTextSize(const SwTextSizeInfo& rInfo) const override;
virtual void Paint( const SwTextPaintInfo &rInf ) const override;
// Accessibility: pass information about this portion to the PortionHandler