sw/qa/core/text/data/tdf43100_tdf120715_cursorOnSpacesOverMargin.docx |binary
 sw/qa/core/text/text.cxx                                              |   68 
++++++++++
 sw/source/core/text/guess.cxx                                         |   10 +
 sw/source/core/text/guess.hxx                                         |    4 
 sw/source/core/text/itrcrsr.cxx                                       |   48 
+++----
 sw/source/core/text/itrpaint.cxx                                      |    1 
 sw/source/core/text/itrtxt.hxx                                        |    1 
 sw/source/core/text/porlin.hxx                                        |    7 -
 sw/source/core/text/portxt.cxx                                        |   20 ++
 sw/source/core/text/portxt.hxx                                        |    1 
 10 files changed, 132 insertions(+), 28 deletions(-)

New commits:
commit 690d4eb71509649ad147cfe60f5b97e2cfaaa519
Author:     Attila Szűcs <szucs.atti...@nisz.hu>
AuthorDate: Wed Jun 15 09:16:32 2022 +0200
Commit:     László Németh <nem...@numbertext.org>
CommitDate: Mon Jul 4 15:34:57 2022 +0200

    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 <nem...@numbertext.org>
    Tested-by: László Németh <nem...@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 000000000000..474d805d893e
Binary files /dev/null and 
b/sw/qa/core/text/data/tdf43100_tdf120715_cursorOnSpacesOverMargin.docx differ
diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx
index 2db4d6dab348..99d80f649fef 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 d469083f7eb1..34681c158ed4 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 696a09fc8589..f83c7e280ae4 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 546d91b17403..b2a0b3bdb458 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 3935630192dd..a66d358645f0 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 f36932dbd5f9..0b48e3b874fe 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 3cd1d9fff942..5ce25a3dd76b 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 a5ae0ea7e287..c0f66496f134 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 77ec0a9f1363..a30f6f0e53b1 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

Reply via email to