/* -*- 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 <hintids.hxx>
#include <editeng/protitem.hxx>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <unotools/charclass.hxx>
#include <svl/ctloptions.hxx>
#include <svl/srchitem.hxx>
#include <swmodule.hxx>
#include <fmtcntnt.hxx>
#include <swtblfmt.hxx>
#include <swcrsr.hxx>
#include <unocrsr.hxx>
#include <bookmark.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <docary.hxx>
#include <ndtxt.hxx>
#include <section.hxx>
#include <swtable.hxx>
#include <cntfrm.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <notxtfrm.hxx>
#include <scriptinfo.hxx>
#include <crstate.hxx>
#include <docsh.hxx>
#include <viewsh.hxx>
#include <frmatr.hxx>
#include <breakit.hxx>
#include <mdiexp.hxx>
#include <strings.hrc>
#include <redline.hxx>
#include <txatbase.hxx>
#include <IDocumentMarkAccess.hxx>
#include <memory>
#include <comphelper/lok.hxx>
#include <editsh.hxx>

#include <viewopt.hxx>
#include <annotationmark.hxx>

using namespace ::com::sun::star::i18n;

const sal_uInt16 coSrchRplcThreshold = 60000;

namespace {

struct PercentHdl
{
    SwDocShell* pDSh;
    sal_uLong nActPos;
    bool bBack, bNodeIdx;

    PercentHdl( sal_uLong nStt, sal_uLong nEnd, SwDocShell* pSh )
        : pDSh(pSh), nActPos(nStt), bBack(false), bNodeIdx(false)
    {
        bBack = (nStt > nEnd);
        if( bBack )
            std::swap( nStt, nEnd );
        ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd );
    }

    explicit PercentHdl( const SwPaM& rPam )
        : pDSh( rPam.GetDoc().GetDocShell() )
    {
        sal_Int32 nStt, nEnd;
        if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
        {
            bNodeIdx = false;
            nStt = rPam.GetMark()->GetContentIndex();
            nEnd = rPam.GetPoint()->GetContentIndex();
        }
        else
        {
            bNodeIdx = true;
            nStt = sal_Int32(rPam.GetMark()->GetNodeIndex());
            nEnd = sal_Int32(rPam.GetPoint()->GetNodeIndex());
        }
        nActPos = nStt;
        bBack = (nStt > nEnd );
        if( bBack )
            std::swap( nStt, nEnd );
        ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd, pDSh );
    }

    ~PercentHdl()                      { ::EndProgress( pDSh ); }

    void NextPos( sal_uLong nPos ) const
        { ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh ); }

    void NextPos( SwPosition const & rPos ) const
        {
            sal_Int32 nPos;
            if( bNodeIdx )
                nPos = sal_Int32(rPos.GetNodeIndex());
            else
                nPos = rPos.GetContentIndex();
            ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh );
        }
};

}

SwCursor::SwCursor( const SwPosition &rPos, SwPaM* pRing )
    : SwPaM( rPos, pRing )
    , m_nRowSpanOffset(0)
    , m_nCursorBidiLevel(0)
    , m_bColumnSelection(false)
{
}

// @@@ semantic: no copy ctor.
SwCursor::SwCursor(SwCursor const& rCpy, SwPaM *const pRing)
    : SwPaM( rCpy, pRing )
    , m_nRowSpanOffset(rCpy.m_nRowSpanOffset)
    , m_nCursorBidiLevel(rCpy.m_nCursorBidiLevel)
    , m_bColumnSelection(rCpy.m_bColumnSelection)
{
}

SwCursor::~SwCursor()
{
}

SwCursor* SwCursor::Create( SwPaM* pRing ) const
{
    return new SwCursor( *GetPoint(), pRing );
}

bool SwCursor::IsReadOnlyAvailable() const
{
    return false;
}

bool SwCursor::IsSkipOverHiddenSections() const
{
    return true;
}

bool SwCursor::IsSkipOverProtectSections() const
{
    return !IsReadOnlyAvailable();
}

// CreateNewSavePos is virtual so that derived classes of cursor can implement
// own SaveObjects if needed and validate them in the virtual check routines.
void SwCursor::SaveState()
{
    m_vSavePos.emplace_back( *this );
}

void SwCursor::RestoreState()
{
    if (!m_vSavePos.empty()) // Robust
    {
        m_vSavePos.pop_back();
    }
}

/// determine if point is outside of the node-array's content area
bool SwCursor::IsNoContent() const
{
    return GetPoint()->GetNodeIndex() <
            GetDoc().GetNodes().GetEndOfExtras().GetIndex();
}

bool SwCursor::IsSelOvrCheck(SwCursorSelOverFlags)
{
    return false;
}

// extracted from IsSelOvr()
bool SwTableCursor::IsSelOvrCheck(SwCursorSelOverFlags eFlags)
{
    SwNodes& rNds = GetDoc().GetNodes();
    // check sections of nodes array
    if( (SwCursorSelOverFlags::CheckNodeSection & eFlags)
        && HasMark() )
    {
        SwNodeIndex aOldPos( rNds, GetSavePos()->nNode );
        if( !CheckNodesRange( aOldPos.GetNode(), GetPoint()->GetNode(), true ))
        {
            GetPoint()->Assign( aOldPos );
            GetPoint()->SetContent( GetSavePos()->nContent );
            return true;
        }
    }
    return SwCursor::IsSelOvrCheck(eFlags);
}

namespace
{
    const SwTextAttr* InputFieldAtPos(SwPosition const *pPos)
    {
        SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
        if (!pTextNd)
            return nullptr;
        return pTextNd->GetTextAttrAt(pPos->GetContentIndex(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent);
    }
}

bool SwCursor::IsSelOvr(SwCursorSelOverFlags const eFlags)
{
    SwDoc& rDoc = GetDoc();
    SwNodes& rNds = rDoc.GetNodes();

    bool bSkipOverHiddenSections = IsSkipOverHiddenSections();
    bool bSkipOverProtectSections = IsSkipOverProtectSections();

    if ( IsSelOvrCheck( eFlags ) )
    {
        return true;
    }

    if (m_vSavePos.back().nNode != GetPoint()->GetNodeIndex() &&
        // (1997) in UI-ReadOnly everything is allowed
        ( !rDoc.GetDocShell() || !rDoc.GetDocShell()->IsReadOnlyUI() ))
    {
        // check new sections
        SwPosition& rPtPos = *GetPoint();
        const SwSectionNode* pSectNd = rPtPos.GetNode().FindSectionNode();
        if( pSectNd &&
            ((bSkipOverHiddenSections && pSectNd->GetSection().IsHiddenFlag() ) ||
            (bSkipOverProtectSections && pSectNd->GetSection().IsProtectFlag() )))
        {
            if( !( SwCursorSelOverFlags::ChangePos & eFlags ) )
            {
                // then we're already done
                RestoreSavePos();
                return true;
            }

            // set cursor to new position:
            SwNodeIndex aIdx( rPtPos.GetNode() );
            sal_Int32 nContentPos = m_vSavePos.back().nContent;
            bool bGoNxt = m_vSavePos.back().nNode < rPtPos.GetNodeIndex();
            SwContentNode* pCNd = bGoNxt
                ? SwNodes::GoNextSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections)
                : SwNodes::GoPrevSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections);
            if( !pCNd && ( SwCursorSelOverFlags::EnableRevDirection & eFlags ))
            {
                bGoNxt = !bGoNxt;
                pCNd = bGoNxt ? SwNodes::GoNextSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections)
                    : SwNodes::GoPrevSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections);
            }

            bool bIsValidPos = nullptr != pCNd;
            const bool bValidNodesRange = bIsValidPos &&
                ::CheckNodesRange( rPtPos.GetNode(), aIdx.GetNode(), true );
            if( !bValidNodesRange )
            {
                rPtPos.Assign( m_vSavePos.back().nNode );
                pCNd = rPtPos.GetNode().GetContentNode();
                if( !pCNd )
                {
                    bIsValidPos = false;
                    nContentPos = 0;
                    rPtPos.Assign( aIdx );
                    pCNd = rPtPos.GetNode().GetContentNode();
                    if( !pCNd )
                    {
                        // then to the beginning of the document
                        rPtPos.Assign( rNds.GetEndOfExtras() );
                        pCNd = SwNodes::GoNext(&rPtPos);
                    }
                }
            }

            // register ContentIndex:
            const sal_Int32 nTmpPos = bIsValidPos ? (bGoNxt ? 0 : pCNd->Len()) : nContentPos;
            GetPoint()->SetContent( nTmpPos );
            if( !bIsValidPos || !bValidNodesRange ||
                IsInProtectTable( true ) )
                return true;
        }

        // is there a protected section in the section?
        if( HasMark() && bSkipOverProtectSections)
        {
            SwNodeOffset nSttIdx = GetMark()->GetNodeIndex(),
                nEndIdx = GetPoint()->GetNodeIndex();
            if( nEndIdx <= nSttIdx )
                std::swap( nSttIdx, nEndIdx );

            const SwSectionFormats& rFormats = rDoc.GetSections();
            for( SwSectionFormats::size_type n = 0; n < rFormats.size(); ++n )
            {
                const SwSectionFormat* pFormat = rFormats[n];
                const SvxProtectItem& rProtect = pFormat->GetProtect();
                if( rProtect.IsContentProtected() )
                {
                    const SwFormatContent& rContent = pFormat->GetContent(false);
                    OSL_ENSURE( rContent.GetContentIdx(), "No SectionNode?" );
                    SwNodeOffset nIdx = rContent.GetContentIdx()->GetIndex();
                    if( nSttIdx <= nIdx && nEndIdx >= nIdx )
                    {
                        // if it is no linked section then we cannot select it
                        const SwSection& rSect = *pFormat->GetSection();
                        if( SectionType::Content == rSect.GetType() )
                        {
                            RestoreSavePos();
                            return true;
                        }
                    }
                }
            }
        }
    }

    const SwNode* pNd = &GetPoint()->GetNode();
    if( pNd->IsContentNode() && !dynamic_cast<SwUnoCursor*>(this) )
    {
        const SwContentFrame* pFrame = static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() );
        if ( (SwCursorSelOverFlags::ChangePos & eFlags)   //allowed to change position if it's a bad one
            && pFrame && pFrame->isFrameAreaDefinitionValid()
            && !pFrame->getFrameArea().Height()     //a bad zero height position
            && !InputFieldAtPos(GetPoint()) )                       //unless it's a (vertical) input field
        {
            // skip to the next/prev valid paragraph with a layout
            SwPosition& rPtPos = *GetPoint();
            bool bGoNxt = m_vSavePos.back().nNode < rPtPos.GetNodeIndex();
            for (;;)
            {
                pFrame = bGoNxt ? pFrame->FindNextCnt(true) : pFrame->FindPrevCnt();
                if (!pFrame || 0 != pFrame->getFrameArea().Height() )
                    break;
            }

            // #i72394# skip to prev/next valid paragraph with a layout in case
            // the first search did not succeed:
            if( !pFrame )
            {
                bGoNxt = !bGoNxt;
                pFrame = static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() );
                while ( pFrame && 0 == pFrame->getFrameArea().Height() )
                {
                    pFrame = bGoNxt ? pFrame->FindNextCnt(true) : pFrame->FindPrevCnt();
                }
            }

            if (pFrame != nullptr)
            {
                if (pFrame->IsTextFrame())
                {
                    SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pFrame));
                    *GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex(
                            bGoNxt ? 0 : pTextFrame->GetText().getLength()));
                }
                else
                {
                    assert(pFrame->IsNoTextFrame());
                    SwContentNode *const pCNd = const_cast<SwContentNode*>(
                        static_cast<SwNoTextFrame const*>(pFrame)->GetNode());
                    assert(pCNd);

                    // set this ContentNode as new position
                    rPtPos.Assign( *pCNd );
                    // assign corresponding ContentIndex
                    const sal_Int32 nTmpPos = bGoNxt ? 0 : pCNd->Len();
                    GetPoint()->SetContent( nTmpPos );
                }


                if (rPtPos.GetNodeIndex() == m_vSavePos.back().nNode
                    && GetPoint()->GetContentIndex() == m_vSavePos.back().nContent)
                {
                    // new position equals saved one
                    // --> trigger restore of saved pos by setting <pFrame> to NULL - see below
                    pFrame = nullptr;
                }

                if ( IsInProtectTable( true ) )
                {
                    // new position in protected table
                    // --> trigger restore of saved pos by setting <pFrame> to NULL - see below
                    pFrame = nullptr;
                }
            }
        }

        if( !pFrame )
        {
            assert(!m_vSavePos.empty());
            SwContentNode const*const pSaveNode(rNds[m_vSavePos.back().nNode]->GetContentNode());
            // if the old position already didn't have a frame, allow moving
            // anyway, hope the caller can handle that
            if (pSaveNode && pSaveNode->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()))
            {
                DeleteMark();
                RestoreSavePos();
                return true; // we need a frame
            }
        }
    }

    // is the cursor allowed to be in a protected node?
    if( !( SwCursorSelOverFlags::ChangePos & eFlags ) && !IsAtValidPos() )
    {
        DeleteMark();
        RestoreSavePos();
        return true;
    }

    if( !HasMark() )
        return false;

    // check for invalid sections
    if( !::CheckNodesRange( GetMark()->GetNode(), GetPoint()->GetNode(), true ))
    {
        DeleteMark();
        RestoreSavePos();
        return true; // we need a frame
    }

    pNd = &GetMark()->GetNode();
    if( pNd->IsContentNode()
        && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() )
        && !dynamic_cast<SwUnoCursor*>(this) )
    {
        DeleteMark();
        RestoreSavePos();
        return true; // we need a frame
    }

    // ensure that selection is only inside an InputField or contains the InputField completely
    {
        const SwTextAttr* pInputFieldTextAttrAtPoint = InputFieldAtPos(GetPoint());
        const SwTextAttr* pInputFieldTextAttrAtMark = InputFieldAtPos(GetMark());

        if ( pInputFieldTextAttrAtPoint != pInputFieldTextAttrAtMark )
        {
            const SwNodeOffset nRefNodeIdx =
                ( SwCursorSelOverFlags::Toggle & eFlags )
                ? m_vSavePos.back().nNode
                : GetMark()->GetNodeIndex();
            const sal_Int32 nRefContentIdx =
                ( SwCursorSelOverFlags::Toggle & eFlags )
                ? m_vSavePos.back().nContent
                : GetMark()->GetContentIndex();
            const bool bIsForwardSelection =
                nRefNodeIdx < GetPoint()->GetNodeIndex()
                || ( nRefNodeIdx == GetPoint()->GetNodeIndex()
                     && nRefContentIdx < GetPoint()->GetContentIndex() );

            if ( pInputFieldTextAttrAtPoint != nullptr )
            {
                const sal_Int32 nNewPointPos =
                    bIsForwardSelection ? *(pInputFieldTextAttrAtPoint->End()) : pInputFieldTextAttrAtPoint->GetStart();
                GetPoint()->SetContent( nNewPointPos );
            }

            if ( pInputFieldTextAttrAtMark != nullptr )
            {
                const sal_Int32 nNewMarkPos =
                    bIsForwardSelection ? pInputFieldTextAttrAtMark->GetStart() : *(pInputFieldTextAttrAtMark->End());
                GetMark()->SetContent( nNewMarkPos );
            }
        }
    }

    const SwTableNode* pPtNd = GetPoint()->GetNode().FindTableNode();
    const SwTableNode* pMrkNd = GetMark()->GetNode().FindTableNode();
    // both in no or in same table node
    if( ( !pMrkNd && !pPtNd ) || pPtNd == pMrkNd )
        return false;

    // in different tables or only mark in table
    if( pMrkNd )
    {
        // not allowed, so go back to old position
        RestoreSavePos();
        // Cursor stays at old position
        return true;
    }

    // Note: this cannot happen in TableMode
    // Only Point in Table then go behind/in front of table
    if (SwCursorSelOverFlags::ChangePos & eFlags)
    {
        bool bSelTop = GetPoint()->GetNodeIndex() <
            ((SwCursorSelOverFlags::Toggle & eFlags)
                 ? m_vSavePos.back().nNode : GetMark()->GetNodeIndex());

        do { // loop for table after table
            SwNodeOffset nSEIdx = pPtNd->EndOfSectionIndex();
            SwNodeOffset nSttEndTable = nSEIdx + 1;

            if( bSelTop )
                nSttEndTable = rNds[ nSEIdx ]->StartOfSectionIndex() - 1;

            GetPoint()->Assign( nSttEndTable );
            const SwNode* pMyNd = &(GetPointNode());

            if( pMyNd->IsSectionNode() || ( pMyNd->IsEndNode() &&
                pMyNd->StartOfSectionNode()->IsSectionNode() ) )
            {
                pMyNd = bSelTop
                    ? SwNodes::GoPrevSection( GetPoint(),true,false )
                    : SwNodes::GoNextSection( GetPoint(),true,false );

                /* #i12312# Handle failure of Go{Prev|Next}Section */
                if ( nullptr == pMyNd)
                    break;

                pPtNd = pMyNd->FindTableNode();
                if( pPtNd )
                    continue;
            }

            // we permit these
            if( pMyNd->IsContentNode() &&
                ::CheckNodesRange( GetMark()->GetNode(),
                GetPoint()->GetNode(), true ))
            {
                // table in table
                const SwTableNode* pOuterTableNd = pMyNd->FindTableNode();
                if ( pOuterTableNd )
                    pMyNd = pOuterTableNd;
                else
                {
                    SwContentNode* pCNd = const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pMyNd));
                    GetPoint()->SetContent( bSelTop ? pCNd->Len() : 0 );
                    return false;
                }
            }
            if( bSelTop )
            {
                if ( !pMyNd->IsEndNode() )
                    break;
                pPtNd = pMyNd->FindTableNode();
            }
            else
                pPtNd = pMyNd->GetTableNode();
            if (!pPtNd)
                break;
        } while( true );
    }

    // stay on old position
    RestoreSavePos();
    return true;
}

bool SwCursor::IsInProtectTable( bool bMove, bool bChgCursor )
{
    SwContentNode* pCNd = GetPointContentNode();
    if( !pCNd )
        return false;

    // No table, no protected cell:
    const SwTableNode* pTableNode = pCNd->FindTableNode();
    if ( !pTableNode )
        return false;

    // Current position == last save position?
    if (m_vSavePos.back().nNode == GetPoint()->GetNodeIndex())
        return false;

    // Check for covered cell:
    bool bInCoveredCell = false;
    const SwStartNode* pTmpSttNode = pCNd->FindTableBoxStartNode();
    OSL_ENSURE( pTmpSttNode, "In table, therefore I expect to get a SwTableBoxStartNode" );
    const SwTableBox* pBox = pTmpSttNode ? pTableNode->GetTable().GetTableBox( pTmpSttNode->GetIndex() ) : nullptr; //Robust #151355
    if ( pBox && pBox->getRowSpan() < 1 ) // Robust #151270
        bInCoveredCell = true;

    // Positions of covered cells are not acceptable:
    if ( !bInCoveredCell )
    {
        // Position not protected?
        if ( !pCNd->IsProtect() )
            return false;

        // Cursor in protected cells allowed?
        if ( IsReadOnlyAvailable() )
            return false;
    }

    // If we reach this point, we are in a protected or covered table cell!

    if( !bMove )
    {
        if( bChgCursor )
            // restore the last save position
            RestoreSavePos();

        return true; // Cursor stays at old position
    }

    // We are in a protected table cell. Traverse top to bottom?
    if (m_vSavePos.back().nNode < GetPoint()->GetNodeIndex())
    {
        // search next valid box
        // if there is another StartNode after the EndNode of a cell then
        // there is another cell
        SwNodeIndex aCellStt( *GetPointNode().FindTableBoxStartNode()->EndOfSectionNode(), 1 );
        bool bProt = true;
GoNextCell:
        for (;;) {
            if( !aCellStt.GetNode().IsStartNode() )
                break;
            ++aCellStt;
            pCNd = aCellStt.GetNode().GetContentNode();
            if( !pCNd )
                pCNd = SwNodes::GoNext(&aCellStt);
            bProt = pCNd->IsProtect();
            if( !bProt )
                break;
            aCellStt.Assign( *pCNd->FindTableBoxStartNode()->EndOfSectionNode(), 1 );
        }

SetNextCursor:
        if( !bProt ) // found free cell
        {
            GetPoint()->Assign( aCellStt );
            SwContentNode* pTmpCNd = GetPointContentNode();
            if( pTmpCNd )
            {
                GetPoint()->SetContent( 0 );
                return false;
            }
            return IsSelOvr( SwCursorSelOverFlags::Toggle |
                             SwCursorSelOverFlags::ChangePos );
        }
        // end of table, so go to next node
        ++aCellStt;
        SwNode* pNd = &aCellStt.GetNode();
        if( pNd->IsEndNode() || HasMark())
        {
            // if only table in FlyFrame or SSelection then stay on old position
            if( bChgCursor )
                RestoreSavePos();
            return true;
        }
        else if( pNd->IsTableNode() )
        {
            ++aCellStt;
            goto GoNextCell;
        }

        bProt = false; // index is now on a content node
        goto SetNextCursor;
    }

    // search for the previous valid box
    {
        // if there is another EndNode in front of the StartNode than there
        // exists a previous cell
        SwNodeIndex aCellStt( *GetPointNode().FindTableBoxStartNode(), -1 );
        SwNode* pNd;
        bool bProt = true;
GoPrevCell:
        for (;;) {
            pNd = &aCellStt.GetNode();
            if( !pNd->IsEndNode() )
                break;
            aCellStt.Assign( *pNd->StartOfSectionNode(), +1 );
            pCNd = aCellStt.GetNode().GetContentNode();
            if( !pCNd )
                pCNd = SwNodes::GoNext(&aCellStt);
            bProt = pCNd->IsProtect();
            if( !bProt )
                break;
            aCellStt.Assign( *pNd->FindTableBoxStartNode(), -1 );
        }

SetPrevCursor:
        if( !bProt ) // found free cell
        {
            GetPoint()->Assign( aCellStt );
            SwContentNode* pTmpCNd = GetPointContentNode();
            if( pTmpCNd )
            {
                GetPoint()->SetContent(  0 );
                return false;
            }
            return IsSelOvr( SwCursorSelOverFlags::Toggle |
                             SwCursorSelOverFlags::ChangePos );
        }
        // at the beginning of a table, so go to next node
        --aCellStt;
        pNd = &aCellStt.GetNode();
        if( pNd->IsStartNode() || HasMark() )
        {
            // if only table in FlyFrame or SSelection then stay on old position
            if( bChgCursor )
                RestoreSavePos();
            return true;
        }
        else if( pNd->StartOfSectionNode()->IsTableNode() )
        {
            --aCellStt;
            goto GoPrevCell;
        }

        bProt = false; // index is now on a content node
        goto SetPrevCursor;
    }
}

/// Return <true> if cursor can be set to this position
bool SwCursor::IsAtValidPos( bool bPoint ) const
{
    const SwDoc& rDoc = GetDoc();
    const SwPosition* pPos = bPoint ? GetPoint() : GetMark();
    const SwNode* pNd = &pPos->GetNode();

    if( pNd->IsContentNode() && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ) &&
        !dynamic_cast<const SwUnoCursor*>(this) )
    {
        return false;
    }

    // #i45129# - in UI-ReadOnly everything is allowed
    const SwDocShell* pShell = rDoc.GetDocShell();
    if( !pShell || !pShell->IsReadOnlyUI() )
        return true;

    const bool bCursorInReadOnly = IsReadOnlyAvailable();
    if( !bCursorInReadOnly && pNd->IsProtect() )
        return false;

    const SwSectionNode* pSectNd = pNd->FindSectionNode();
    return !pSectNd
           || !(pSectNd->GetSection().IsHiddenFlag() ||
                ( !bCursorInReadOnly && pSectNd->GetSection().IsProtectFlag() ));
}

void SwCursor::SaveTableBoxContent( const SwPosition* ) {}

/// set range for search in document
SwMoveFnCollection const & SwCursor::MakeFindRange( SwDocPositions nStart,
                                SwDocPositions nEnd, SwPaM* pRange ) const
{
    pRange->SetMark();
    FillFindPos( nStart, *pRange->GetMark() );
    FillFindPos( nEnd, *pRange->GetPoint() );

    // determine direction of search
    return ( SwDocPositions::Start == nStart || SwDocPositions::OtherStart == nStart ||
              (SwDocPositions::Curr == nStart &&
                (SwDocPositions::End == nEnd || SwDocPositions::OtherEnd == nEnd ) ))
                ? fnMoveForward : fnMoveBackward;
}

static sal_Int32 lcl_FindSelection( SwFindParas& rParas, SwCursor* pCurrentCursor,
                        SwMoveFnCollection const & fnMove, SwCursor*& pFndRing,
                        SwPaM& aRegion, FindRanges eFndRngs,
                        bool bInReadOnly, bool& bCancel )
{
    SwDoc& rDoc = pCurrentCursor->GetDoc();
    bool const bDoesUndo = rDoc.GetIDocumentUndoRedo().DoesUndo();
    int nFndRet = 0;
    sal_Int32 nFound = 0;
    const bool bSrchBkwrd = &fnMove == &fnMoveBackward;
    SwPaM *pTmpCursor = pCurrentCursor, *pSaveCursor = pCurrentCursor;
    std::unique_ptr<SvxSearchItem> xSearchItem;

    // only create progress bar for ShellCursor
    bool bIsUnoCursor = dynamic_cast<SwUnoCursor*>(pCurrentCursor) !=  nullptr;
    std::unique_ptr<PercentHdl> pPHdl;
    sal_uInt16 nCursorCnt = 0;
    if( FindRanges::InSel & eFndRngs )
    {
        while( pCurrentCursor != ( pTmpCursor = pTmpCursor->GetNext() ))
            ++nCursorCnt;
        if( nCursorCnt && !bIsUnoCursor )
            pPHdl.reset(new PercentHdl( 0, nCursorCnt, rDoc.GetDocShell() ));
    }
    else
        pSaveCursor = pSaveCursor->GetPrev();

    bool bEnd = false;
    do {
        aRegion.SetMark();
        // independent from search direction: SPoint is always bigger than mark
        // if the search area is valid
        SwPosition *pSttPos = aRegion.GetMark(),
                        *pEndPos = aRegion.GetPoint();
        *pSttPos = *pTmpCursor->Start();
        *pEndPos = *pTmpCursor->End();
        if( bSrchBkwrd )
            aRegion.Exchange();

        if( !nCursorCnt && !pPHdl && !bIsUnoCursor )
            pPHdl.reset(new PercentHdl( aRegion ));

        // as long as found and not at same position
        while(  *pSttPos <= *pEndPos )
        {
            nFndRet = rParas.DoFind(*pCurrentCursor, fnMove, aRegion, bInReadOnly, xSearchItem);
            if( 0 == nFndRet ||
                ( pFndRing &&
                  *pFndRing->GetPoint() == *pCurrentCursor->GetPoint() &&
                  *pFndRing->GetMark() == *pCurrentCursor->GetMark() ))
                break;
            if( !( FIND_NO_RING & nFndRet ))
            {
                // #i24084# - create ring similar to the one in CreateCursor
                SwCursor* pNew = pCurrentCursor->Create( pFndRing );
                if( !pFndRing )
                    pFndRing = pNew;

                pNew->SetMark();
                *pNew->GetMark() = *pCurrentCursor->GetMark();
            }

            ++nFound;

            if( !( eFndRngs & FindRanges::InSelAll) )
            {
                bEnd = true;
                break;
            }

            if ((coSrchRplcThreshold == nFound)
                && rDoc.GetIDocumentUndoRedo().DoesUndo()
                && rParas.IsReplaceMode())
            {
                short nRet = pCurrentCursor->MaxReplaceArived();
                if( RET_YES == nRet )
                {
                    rDoc.GetIDocumentUndoRedo().DelAllUndoObj();
                    rDoc.GetIDocumentUndoRedo().DoUndo(false);
                }
                else
                {
                    bEnd = true;
                    if(RET_CANCEL == nRet)
                    {
                        bCancel = true;
                    }
                    break;
                }
            }

            if( bSrchBkwrd )
                // move pEndPos in front of the found area
                *pEndPos = *pCurrentCursor->Start();
            else
                // move pSttPos behind the found area
                *pSttPos = *pCurrentCursor->End();

            if( *pSttPos == *pEndPos )
                // in area but at the end => done
                break;

            if( !nCursorCnt && pPHdl )
            {
                pPHdl->NextPos( *aRegion.GetMark() );
            }
        }

        if( bEnd || !( eFndRngs & ( FindRanges::InSelAll | FindRanges::InSel )) )
            break;

        pTmpCursor = pTmpCursor->GetNext();
        if( nCursorCnt && pPHdl )
        {
            pPHdl->NextPos( ++pPHdl->nActPos );
        }

    } while( pTmpCursor != pSaveCursor && pTmpCursor->GetNext() != pTmpCursor);

    if( nFound && !pFndRing ) // if no ring should be created
        pFndRing = pCurrentCursor->Create();

    rDoc.GetIDocumentUndoRedo().DoUndo(bDoesUndo);
    return nFound;
}

static bool lcl_MakeSelFwrd( const SwNode& rSttNd, const SwNode& rEndNd,
                        SwPaM& rPam, bool bFirst )
{
    if( rSttNd.GetIndex() + 1 == rEndNd.GetIndex() )
        return false;

    rPam.DeleteMark();
    SwContentNode* pCNd;
    if( !bFirst )
    {
        rPam.GetPoint()->Assign(rSttNd);
        pCNd = SwNodes::GoNext(rPam.GetPoint());
        if( !pCNd )
            return false;
        rPam.GetPoint()->AssignStartIndex(*pCNd);
    }
    else if( rSttNd.GetIndex() > rPam.GetPoint()->GetNodeIndex() ||
             rPam.GetPoint()->GetNodeIndex() >= rEndNd.GetIndex() )
        // not in this section
        return false;

    rPam.SetMark();
    rPam.GetPoint()->Assign(rEndNd);
    pCNd = SwNodes::GoPrevious(rPam.GetPoint(), true);
    if( !pCNd )
        return false;
    rPam.GetPoint()->AssignEndIndex(*pCNd);

    return *rPam.GetMark() < *rPam.GetPoint();
}

static bool lcl_MakeSelBkwrd( const SwNode& rSttNd, const SwNode& rEndNd,
                        SwPaM& rPam, bool bFirst )
{
    if( rEndNd.GetIndex() + 1 == rSttNd.GetIndex() )
        return false;

    rPam.DeleteMark();
    SwContentNode* pCNd;
    if( !bFirst )
    {
        rPam.GetPoint()->Assign(rSttNd);
        pCNd = SwNodes::GoPrevious(rPam.GetPoint(), true);
        if( !pCNd )
            return false;
        rPam.GetPoint()->AssignEndIndex(*pCNd);
    }
    else if( rEndNd.GetIndex() > rPam.GetPoint()->GetNodeIndex() ||
             rPam.GetPoint()->GetNodeIndex() >= rSttNd.GetIndex() )
        return false;       // not in this section

    rPam.SetMark();
    rPam.GetPoint()->Assign(rEndNd);
    pCNd = SwNodes::GoNext(rPam.GetPoint());
    if( !pCNd )
        return false;
    rPam.GetPoint()->SetContent(0);

    return *rPam.GetPoint() < *rPam.GetMark();
}

// this method "searches" for all use cases because in SwFindParas is always the
// correct parameters and respective search method
sal_Int32 SwCursor::FindAll( SwFindParas& rParas,
                            SwDocPositions nStart, SwDocPositions nEnd,
                            FindRanges eFndRngs, bool& bCancel )
{
    bCancel = false;
    SwCursorSaveState aSaveState( *this );

    // create region without adding it to the ring
    SwPaM aRegion( *GetPoint() );
    SwMoveFnCollection const & fnMove = MakeFindRange( nStart, nEnd, &aRegion );

    sal_Int32 nFound = 0;
    const bool bMvBkwrd = &fnMove == &fnMoveBackward;
    bool bInReadOnly = IsReadOnlyAvailable();
    std::unique_ptr<SvxSearchItem> xSearchItem;

    SwCursor* pFndRing = nullptr;
    SwNodes& rNds = GetDoc().GetNodes();

    // search in selections?
    if( FindRanges::InSel & eFndRngs )
    {
        // if string was not found in region then get all selections (cursors
        // stays unchanged)
        nFound = lcl_FindSelection( rParas, this, fnMove,
                                    pFndRing, aRegion, eFndRngs,
                                    bInReadOnly, bCancel );
        if( 0 == nFound )
            return nFound;

        // found string at least once; it's all in new Cursor ring thus delete old one
        while( GetNext() != this )
            delete GetNext();

        *GetPoint() = *pFndRing->GetPoint();
        SetMark();
        *GetMark() = *pFndRing->GetMark();
        pFndRing->GetRingContainer().merge( GetRingContainer() );
        delete pFndRing;
    }
    else if( FindRanges::InOther & eFndRngs )
    {
        // put cursor as copy of current into ring
        // chaining points always to first created, so forward
        SwCursor* pSav = Create( this ); // save the current cursor

        // if already outside of body text search from this position or start at
        // 1. base section
        if( bMvBkwrd
            ? lcl_MakeSelBkwrd( rNds.GetEndOfExtras(),
                    *rNds.GetEndOfPostIts().StartOfSectionNode(),
                     *this, rNds.GetEndOfExtras().GetIndex() >=
                    GetPoint()->GetNodeIndex() )
            : lcl_MakeSelFwrd( *rNds.GetEndOfPostIts().StartOfSectionNode(),
                    rNds.GetEndOfExtras(), *this,
                    rNds.GetEndOfExtras().GetIndex() >=
                    GetPoint()->GetNodeIndex() ))
        {
            nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing,
                                        aRegion, eFndRngs, bInReadOnly, bCancel );
        }

        if( !nFound )
        {
            // put back the old one
            *GetPoint() = *pSav->GetPoint();
            if( pSav->HasMark() )
            {
                SetMark();
                *GetMark() = *pSav->GetMark();
            }
            else
                DeleteMark();
            return 0;
        }

        if( !( FindRanges::InSelAll & eFndRngs ))
        {
            // there should only be a single one, thus add it
            // independent from search direction: SPoint is always bigger than
            // mark if the search area is valid
            *GetPoint() = *pFndRing->GetPoint();
            SetMark();
            *GetMark() = *pFndRing->GetMark();
        }
        else
        {
            // found string at least once; it's all in new Cursor ring thus delete old one
            while( GetNext() != this )
                delete GetNext();

            *GetPoint() = *pFndRing->GetPoint();
            SetMark();
            *GetMark() = *pFndRing->GetMark();
            pFndRing->GetRingContainer().merge( GetRingContainer() );
        }
        delete pFndRing;
    }
    else if( FindRanges::InSelAll & eFndRngs )
    {
        SwCursor* pSav = Create( this );    // save the current cursor

        const SwNode* pSttNd = ( FindRanges::InBodyOnly & eFndRngs )
                            ? rNds.GetEndOfContent().StartOfSectionNode()
                            : rNds.GetEndOfPostIts().StartOfSectionNode();

        if( bMvBkwrd
            ? lcl_MakeSelBkwrd( rNds.GetEndOfContent(), *pSttNd, *this, false )
            : lcl_MakeSelFwrd( *pSttNd, rNds.GetEndOfContent(), *this, false ))
        {
            nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing,
                                        aRegion, eFndRngs, bInReadOnly, bCancel );
        }

        if( !nFound )
        {
            // put back the old one
            *GetPoint() = *pSav->GetPoint();
            if( pSav->HasMark() )
            {
                SetMark();
                *GetMark() = *pSav->GetMark();
            }
            else
                DeleteMark();
            return 0;
        }
        while( GetNext() != this )
            delete GetNext();

        *GetPoint() = *pFndRing->GetPoint();
        SetMark();
        *GetMark() = *pFndRing->GetMark();
        pFndRing->GetRingContainer().merge( GetRingContainer() );
        delete pFndRing;
    }
    else
    {
        // if a GetMark is set then keep the GetMark of the found object
        // This allows spanning an area with this search.
        SwPosition aMarkPos( *GetMark() );
        const bool bMarkPos = HasMark() && (eFndRngs == FindRanges::InBody);

        nFound = rParas.DoFind(*this, fnMove, aRegion, bInReadOnly, xSearchItem) ? 1 : 0;
        if (0 != nFound && bMarkPos)
            *GetMark() = std::move(aMarkPos);
    }

    if( nFound && SwCursor::IsSelOvr( SwCursorSelOverFlags::Toggle ) )
        nFound = 0;
    return nFound;
}

void SwCursor::FillFindPos( SwDocPositions ePos, SwPosition& rPos ) const
{
    bool bIsStart = true;
    SwContentNode* pCNd = nullptr;
    SwNodes& rNds = GetDoc().GetNodes();

    switch( ePos )
    {
    case SwDocPositions::Start:
        rPos.Assign(*rNds.GetEndOfContent().StartOfSectionNode());
        pCNd = SwNodes::GoNext(&rPos);
        break;
    case SwDocPositions::End:
        rPos.Assign(rNds.GetEndOfContent());
        pCNd = SwNodes::GoPrevious( &rPos );
        bIsStart = false;
        break;
    case SwDocPositions::OtherStart:
        rPos.Assign( *rNds[ SwNodeOffset(0) ] );
        pCNd = SwNodes::GoNext(&rPos);
        break;
    case SwDocPositions::OtherEnd:
        rPos.Assign( *rNds.GetEndOfContent().StartOfSectionNode() );
        pCNd = SwNodes::GoPrevious( &rPos );
        bIsStart = false;
        break;
    default:
        rPos = *GetPoint();
    }

    if( pCNd && !bIsStart )
    {
        rPos.AssignEndIndex( *pCNd );
    }
}

short SwCursor::MaxReplaceArived()
{
    return RET_YES;
}

namespace {

struct HideWrapper
{
    // either the frame's text or the node's text (possibly pre-filtered)
    OUString const* m_pText;
    // this is actually a TextFrameIndex but all of the i18n code uses sal_Int32
    sal_Int32 m_nPtIndex;
    // if mapping is needed, use this frame
    SwTextFrame * m_pFrame;
    // input in the constructor, output (via mapping) in the destructor
    SwTextNode *& m_rpTextNode;
    sal_Int32 & m_rPtPos;

    HideWrapper(SwRootFrame const*const pLayout,
            SwTextNode *& rpTextNode, sal_Int32 & rPtPos,
            OUString const*const pFilteredNodeText = nullptr)
        : m_pText(pFilteredNodeText)
        , m_pFrame(nullptr)
        , m_rpTextNode(rpTextNode)
        , m_rPtPos(rPtPos)
    {
        if (pLayout && pLayout->HasMergedParas())
        {
            m_pFrame = static_cast<SwTextFrame*>(rpTextNode->getLayoutFrame(pLayout));
            m_pText = &m_pFrame->GetText();
            m_nPtIndex = sal_Int32(m_pFrame->MapModelToView(rpTextNode, rPtPos));
        }
        else
        {
            if (!m_pText)
            {
                m_pText = &rpTextNode->GetText();
            }
            m_nPtIndex = rPtPos;
        }
    }
    ~HideWrapper()
    {
        AssignBack(m_rpTextNode, m_rPtPos);
    }
    void AssignBack(SwTextNode *& rpTextNode, sal_Int32 & rPtPos)
    {
        if (0 <= m_nPtIndex && m_pFrame)
        {
            std::pair<SwTextNode*, sal_Int32> const pos(
                    m_pFrame->MapViewToModel(TextFrameIndex(m_nPtIndex)));
            rpTextNode = pos.first;
            rPtPos = pos.second;
        }
        else
        {
            rPtPos = m_nPtIndex;
        }
    }
};

} // namespace

bool SwCursor::SelectWord( SwViewShell const * pViewShell, const Point* pPt )
{
    return SelectWordWT( pViewShell, WordType::ANYWORD_IGNOREWHITESPACES, pPt );
}

bool SwCursor::IsStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        HideWrapper w(pLayout, pTextNd, nPtPos);

        bRet = g_pBreakIt->GetBreakIter()->isBeginWord(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos )),
                            nWordType );
    }
    return bRet;
}

bool SwCursor::IsEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        HideWrapper w(pLayout, pTextNd, nPtPos);

        bRet = g_pBreakIt->GetBreakIter()->isEndWord(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                            nWordType );

    }
    return bRet;
}

bool SwCursor::IsInWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos);

            Boundary aBoundary = g_pBreakIt->GetBreakIter()->getWordBoundary(
                                *w.m_pText, w.m_nPtIndex,
                                g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                                nWordType,
                                true );

            bRet = aBoundary.startPos != aBoundary.endPos &&
                    aBoundary.startPos <= w.m_nPtIndex &&
                        w.m_nPtIndex <= aBoundary.endPos;
            w.m_nPtIndex = aBoundary.startPos; // hack: convert startPos back...
        }
        if(bRet)
        {
            const CharClass& rCC = GetAppCharClass();
            bRet = rCC.isLetterNumeric(pTextNd->GetText(), nPtPos);
        }
    }
    return bRet;
}

bool SwCursor::IsStartEndSentence(bool bEnd, SwRootFrame const*const pLayout) const
{
    bool bRet = bEnd ?
                    GetPointContentNode() && GetPoint()->GetContentIndex() == GetPointContentNode()->Len() :
                    GetPoint()->GetContentIndex() == 0;

    if ((pLayout != nullptr && pLayout->HasMergedParas()) || !bRet)
    {
        SwCursor aCursor(*GetPoint(), nullptr);
        SwPosition aOrigPos = *aCursor.GetPoint();
        aCursor.GoSentence(bEnd ? SwCursor::END_SENT : SwCursor::START_SENT, pLayout);
        bRet = aOrigPos == *aCursor.GetPoint();
    }
    return bRet;
}

bool SwCursor::GoStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        SwCursorSaveState aSave( *this );
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos);

            w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                            nWordType,
                            false ).startPos;
        }

        if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0)
        {
            GetPoint()->Assign(*pTextNd, nPtPos);
            if( !IsSelOvr() )
                bRet = true;
        }
    }
    return bRet;
}

bool SwCursor::GoEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        SwCursorSaveState aSave( *this );
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos);

            w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                            nWordType,
                            true ).endPos;
        }

        if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0 &&
            GetPoint()->GetContentIndex() != nPtPos )
        {
            GetPoint()->Assign(*pTextNd, nPtPos);
            if( !IsSelOvr() )
                bRet = true;
        }
    }
    return bRet;
}

bool SwCursor::GoNextWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        SwCursorSaveState aSave( *this );
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos);

            w.m_nPtIndex = g_pBreakIt->GetBreakIter()->nextWord(
                        *w.m_pText, w.m_nPtIndex,
                        g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ),
                        nWordType ).startPos;
        }

        if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0)
        {
            GetPoint()->Assign(*pTextNd, nPtPos);
            if( !IsSelOvr() )
                bRet = true;
        }
    }
    return bRet;
}

bool SwCursor::GoPrevWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        SwCursorSaveState aSave( *this );
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos);

            const sal_Int32 nPtStart = w.m_nPtIndex;
            if (w.m_nPtIndex)
            {
                --w.m_nPtIndex;
                w.AssignBack(pTextNd, nPtPos);
            }

            w.m_nPtIndex = g_pBreakIt->GetBreakIter()->previousWord(
                        *w.m_pText, nPtStart,
                        g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ),
                                nWordType ).startPos;
        }

        if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0)
        {
            GetPoint()->Assign(*pTextNd, nPtPos);
            if( !IsSelOvr() )
                bRet = true;
        }
    }
    return bRet;
}

bool SwCursor::SelectWordWT( SwViewShell const * pViewShell, sal_Int16 nWordType, const Point* pPt )
{
    SwCursorSaveState aSave( *this );

    bool bRet = false;
    DeleteMark();
    const SwRootFrame* pLayout = pViewShell->GetLayout();
    if( pPt && nullptr != pLayout )
    {
        // set the cursor to the layout position
        Point aPt( *pPt );
        pLayout->GetModelPositionForViewPoint( GetPoint(), aPt );
    }

    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        // Should we select the whole fieldmark?
        const IDocumentMarkAccess* pMarksAccess = GetDoc().getIDocumentMarkAccess( );
        sw::mark::Fieldmark const*const pMark(pMarksAccess->getInnerFieldmarkFor(*GetPoint()));
        if (pMark && (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK
                      || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))
        {
            *GetPoint() = sw::mark::FindFieldSep(*pMark);
            GetPoint()->AdjustContent(+1); // Don't select the separator

            const SwPosition& rEnd = pMark->GetMarkEnd();

            assert(pMark->GetMarkEnd() != *GetPoint());
            SetMark();
            *GetMark() = rEnd;
            GetMark()->AdjustContent(-1); // Don't select the end delimiter

            bRet = true;
        }
        else
        {
            sal_Int32 nPtPos = GetPoint()->GetContentIndex();

            HideWrapper w(pViewShell->GetLayout(), pTextNd, nPtPos);
            bool bForward = w.m_pText->getLength() == w.m_nPtIndex ? false : true;

            Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary(
                                *w.m_pText, w.m_nPtIndex,
                                g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                                nWordType,
                                bForward ));

            if (comphelper::LibreOfficeKit::isActive() && aBndry.startPos == aBndry.endPos && w.m_nPtIndex > 0)
            {
                // nPtPos is the end of the paragraph, select the last word then.
                --w.m_nPtIndex;
                w.AssignBack(pTextNd, nPtPos);

                aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
                                    *w.m_pText, w.m_nPtIndex,
                                    g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
                                    nWordType,
                                    bForward );

            }

            SwTextNode * pStartNode(pTextNd);
            sal_Int32 nStartIndex;
            w.m_nPtIndex = aBndry.startPos;
            w.AssignBack(pStartNode, nStartIndex);

            SwTextNode * pEndNode(pTextNd);
            sal_Int32 nEndIndex;
            w.m_nPtIndex = aBndry.endPos;
            w.AssignBack(pEndNode, nEndIndex);

            if( aBndry.startPos != aBndry.endPos )
            {
                GetPoint()->Assign(*pEndNode, nEndIndex);
                if( !IsSelOvr() )
                {
                    SetMark();
                    GetMark()->Assign(*pStartNode, nStartIndex);
                    if (sw::mark::AnnotationMark* pAnnotationMark = pMarksAccess->getAnnotationMarkFor(*GetPoint()))
                    {
                        // An annotation mark covers the selected word. Check
                        // if it covers only the word: in that case we select
                        // the comment anchor as well.
                        bool bStartMatch = GetMark()->GetNode() == pAnnotationMark->GetMarkStart().GetNode() &&
                            GetMark()->GetContentIndex() == pAnnotationMark->GetMarkStart().GetContentIndex();
                        bool bEndMatch = GetPoint()->GetNode() == pAnnotationMark->GetMarkEnd().GetNode() &&
                            GetPoint()->GetContentIndex() + 1 == pAnnotationMark->GetMarkEnd().GetContentIndex();
                        if (bStartMatch && bEndMatch)
                            GetPoint()->AdjustContent(+1);
                    }
                    if( !IsSelOvr() )
                        bRet = true;
                }
            }
        }
    }

    if( !bRet )
    {
        DeleteMark();
        RestoreSavePos();
    }
    return bRet;
}

static OUString lcl_MaskDeletedRedlines( const SwTextNode* pTextNd )
{
    OUString aRes;
    if (pTextNd)
    {
        //mask deleted redlines
        OUString sNodeText(pTextNd->GetText());
        const SwDoc& rDoc = pTextNd->GetDoc();
        const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
        if ( bShowChg )
        {
            SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *pTextNd, RedlineType::Any );
            for ( ; nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
            {
                const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
                if ( pRed->Start()->GetNode() > *pTextNd )
                    break;

                if( RedlineType::Delete == pRed->GetType() )
                {
                    sal_Int32 nStart, nEnd;
                    pRed->CalcStartEnd( pTextNd->GetIndex(), nStart, nEnd );

                    while ( nStart < nEnd && nStart < sNodeText.getLength() )
                        sNodeText = sNodeText.replaceAt( nStart++, 1, rtl::OUStringChar(CH_TXTATR_INWORD) );
                }
            }
        }
        aRes = sNodeText;
    }
    return aRes;
}

bool SwCursor::GoSentence(SentenceMoveType eMoveType, SwRootFrame const*const pLayout)
{
    bool bRet = false;
    SwTextNode* pTextNd = GetPointNode().GetTextNode();
    if (pTextNd)
    {
        OUString const sNodeText(lcl_MaskDeletedRedlines(pTextNd));

        SwCursorSaveState aSave( *this );
        sal_Int32 nPtPos = GetPoint()->GetContentIndex();

        {
            HideWrapper w(pLayout, pTextNd, nPtPos, &sNodeText);

            switch ( eMoveType )
            {
            case START_SENT: /* when modifying: see also ExpandToSentenceBorders below! */
                w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
                break;
            case END_SENT: /* when modifying: see also ExpandToSentenceBorders below! */
                w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
                break;
            case NEXT_SENT:
                {
                    w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
                    if (w.m_nPtIndex >= 0 && w.m_nPtIndex < w.m_pText->getLength())
                    {
                        do
                        {
                            ++w.m_nPtIndex;
                        }
                        while (w.m_nPtIndex < w.m_pText->getLength()
                               && (*w.m_pText)[w.m_nPtIndex] == ' ');
                    }
                    break;
                }
            case PREV_SENT:
                w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));

                if (w.m_nPtIndex == 0)
                    return false;   // the previous sentence is not in this paragraph
                if (w.m_nPtIndex > 0)
                {
                    w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
                            *w.m_pText, w.m_nPtIndex - 1,
                            g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
                }
                break;
            }
        }

        // it is allowed to place the PaM just behind the last
        // character in the text thus <= ...Len
        if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0)
        {
            GetPoint()->Assign(*pTextNd, nPtPos);
            if( !IsSelOvr() )
                bRet = true;
        }
    }
    return bRet;
}

void SwCursor::ExpandToSentenceBorders(SwRootFrame const*const pLayout)
{
    SwTextNode* pStartNd = Start()->GetNode().GetTextNode();
    SwTextNode* pEndNd   = End()->GetNode().GetTextNode();
    if (!pStartNd || !pEndNd)
        return;

    if (!HasMark())
        SetMark();

    OUString sStartText( lcl_MaskDeletedRedlines( pStartNd ) );
    OUString sEndText( pStartNd == pEndNd? sStartText : lcl_MaskDeletedRedlines( pEndNd ) );

    SwCursorSaveState aSave( *this );
    sal_Int32 nStartPos = Start()->GetContentIndex();
    sal_Int32 nEndPos   = End()->GetContentIndex();

    {
        HideWrapper w(pLayout, pStartNd, nStartPos, &sStartText);

        w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pStartNd->GetLang( nStartPos ) ) );
    }
    {
        HideWrapper w(pLayout, pEndNd, nEndPos, &sEndText);

        w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
                            *w.m_pText, w.m_nPtIndex,
                            g_pBreakIt->GetLocale( pEndNd->GetLang( nEndPos ) ) );
    }

    // it is allowed to place the PaM just behind the last
    // character in the text thus <= ...Len
    if (nStartPos <= pStartNd->GetText().getLength() && nStartPos >= 0)
    {
        GetMark()->Assign(*pStartNd, nStartPos);
    }
    if (nEndPos <= pEndNd->GetText().getLength() && nEndPos >= 0)
    {
        GetPoint()->Assign(*pEndNd, nEndPos);
    }
}

bool SwTableCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode /*nMode*/,
    bool /*bVisualAllowed*/, bool /*bSkipHidden*/, bool /*bInsertCursor*/,
    SwRootFrame const*, bool /*isFieldNames*/)
{
    return bLeft ? GoPrevCell( nCnt )
                 : GoNextCell( nCnt );
}

// calculate cursor bidi level: extracted from LeftRight()
const SwContentFrame*
SwCursor::DoSetBidiLevelLeftRight(
    bool & io_rbLeft, bool bVisualAllowed, bool bInsertCursor)
{
    // calculate cursor bidi level
    const SwContentFrame* pSttFrame = nullptr;
    SwNode& rNode = GetPoint()->GetNode();

    if( rNode.IsTextNode() )
    {
        const SwTextNode& rTNd = *rNode.GetTextNode();
        sal_Int32 nPos = GetPoint()->GetContentIndex();

        if ( bVisualAllowed && SvtCTLOptions::IsCTLFontEnabled() &&
             SvtCTLOptions::MOVEMENT_VISUAL == SvtCTLOptions::GetCTLCursorMovement() )
        {
            // for visual cursor travelling (used in bidi layout)
            // we first have to convert the logic to a visual position
            Point aPt;
            std::pair<Point, bool> const tmp(aPt, true);
            pSttFrame = rTNd.getLayoutFrame(
                    GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
                    GetPoint(), &tmp);
            if( pSttFrame )
            {
                sal_uInt8 nCursorLevel = GetCursorBidiLevel();
                bool bForward = ! io_rbLeft;
                SwTextFrame *const pTF(const_cast<SwTextFrame*>(
                            static_cast<const SwTextFrame*>(pSttFrame)));
                TextFrameIndex nTFIndex(pTF->MapModelToViewPos(*GetPoint()));
                pTF->PrepareVisualMove( nTFIndex, nCursorLevel,
                                                         bForward, bInsertCursor );
                *GetPoint() = pTF->MapViewToModelPos(nTFIndex);
                SetCursorBidiLevel( nCursorLevel );
                io_rbLeft = ! bForward;
            }
        }
        else
        {
            SwTextFrame const* pFrame;
            const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo(rTNd, &pFrame);
            if ( pSI )
            {
                const sal_Int32 nMoveOverPos = io_rbLeft ?
                                               ( nPos ? nPos - 1 : 0 ) :
                                                nPos;
                TextFrameIndex nIndex(pFrame->MapModelToView(&rTNd, nMoveOverPos));
                SetCursorBidiLevel( pSI->DirType(nIndex) );
            }
        }
    }
    return pSttFrame;
}

static const SwTextFrame* GetTextFrame(const SwNode& rNode, SwRootFrame const*const pLayout)
{
    if (!pLayout)
        return nullptr;

    SwContentFrame const* pFrame(rNode.GetContentNode()->getLayoutFrame(pLayout));
    if (!pFrame)
        return nullptr;

    while (pFrame->GetPrecede())
    {
        pFrame = static_cast<SwContentFrame const*>(pFrame->GetPrecede());
    }

    return pFrame->DynCastTextFrame();
}

bool SwCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
                          bool bVisualAllowed,bool bSkipHidden, bool bInsertCursor,
                          SwRootFrame const*const pLayout, bool isFieldNames)
{
    // calculate cursor bidi level
    SwNode& rNode = GetPoint()->GetNode();
    const SwContentFrame* pSttFrame = // may side-effect bLeft!
        DoSetBidiLevelLeftRight(bLeft, bVisualAllowed, bInsertCursor);

    // can the cursor be moved n times?
    SwCursorSaveState aSave( *this );
    SwMoveFnCollection const & fnMove = bLeft ? fnMoveBackward : fnMoveForward;

    SwGoInDoc fnGo;
    if ( bSkipHidden )
        fnGo = SwCursorSkipMode::Cells == nMode ? GoInContentCellsSkipHidden : GoInContentSkipHidden;
    else
        fnGo = SwCursorSkipMode::Cells == nMode ? GoInContentCells : GoInContent;

    SwTextFrame const* pFrame = GetTextFrame(rNode, pLayout);

    while( nCnt )
    {
        SwNodeIndex aOldNodeIdx( GetPoint()->GetNode() );

        TextFrameIndex beforeIndex(-1);
        if (pFrame)
        {
            beforeIndex = pFrame->MapModelToViewPos(*GetPoint());
        }

        if (!bLeft && pLayout && pLayout->GetFieldmarkMode() == sw::FieldmarkMode::ShowResult)
        {
            SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
            SAL_WARN_IF(!pNode, "sw.core", "expected a SwTextNode");
            if (pNode && pNode->Len() != GetPoint()->GetContentIndex()
                && pNode->GetText()[GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDSTART)
            {
                IDocumentMarkAccess const& rIDMA(*GetDoc().getIDocumentMarkAccess());
                sw::mark::Fieldmark const*const pMark(rIDMA.getFieldmarkAt(*GetPoint()));
                assert(pMark);
                *GetPoint() = sw::mark::FindFieldSep(*pMark);
            }
        }

        if ( !Move( fnMove, fnGo ) )
        {
            const SwEditShell* pSh = GetDoc().GetEditShell();
            const SwViewOption* pViewOptions = pSh ? pSh->GetViewOptions() : nullptr;
            if (pViewOptions && pViewOptions->IsShowOutlineContentVisibilityButton())
            {
                // Fixes crash that occurs in documents with outline content folded at the end of
                // the document. When the cursor is at the end of the visible document and
                // right arrow key is pressed Move fails after moving the cursor to the
                // end of the document model, which doesn't have a node frame and causes
                // weird numbers to be displayed in the statusbar page number count. Left
                // arrow, when in this state, causes a crash without RestoredSavePos() added here.
                RestoreSavePos();
            }
            break;
        }

        if (pFrame)
        {
            SwTextFrame const* pNewFrame = GetTextFrame(GetPoint()->GetNode(), pLayout);
            // sw_redlinehide: fully redline-deleted nodes don't have frames...
            if (pFrame == pNewFrame || !pNewFrame)
            {
                if (!pNewFrame || beforeIndex == pFrame->MapModelToViewPos(*GetPoint()))
                {
                    continue; // moving inside delete redline, doesn't count...
                }
            }
            else
            {
                // assume iteration is stable & returns the same frame
                assert(!pFrame->IsAnFollow(pNewFrame) && !pNewFrame->IsAnFollow(pFrame));
                pFrame = pNewFrame;
            }
        }

        if (bLeft && pLayout && pLayout->GetFieldmarkMode() == sw::FieldmarkMode::ShowCommand)
        {
            SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
            assert(pNode);
            if (pNode->Len() != GetPoint()->GetContentIndex()
                && pNode->GetText()[GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDEND)
            {
                IDocumentMarkAccess const& rIDMA(*GetDoc().getIDocumentMarkAccess());
                sw::mark::Fieldmark const*const pMark(rIDMA.getFieldmarkAt(*GetPoint()));
                assert(pMark);
                *GetPoint() = sw::mark::FindFieldSep(*pMark);
            }
        }

        if (isFieldNames)
        {
            SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
            assert(pNode);
            SwTextAttr const*const pInputField(pNode->GetTextAttrAt(
                GetPoint()->GetContentIndex(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
            if (pInputField)
            {
                continue; // skip over input fields
            }
        }

        // If we were located inside a covered cell but our position has been
        // corrected, we check if the last move has moved the cursor to a
        // different table cell. In this case we set the cursor to the stored
        // covered position and redo the move:
        if (m_nRowSpanOffset)
        {
            const SwNode* pOldTabBoxSttNode = aOldNodeIdx.GetNode().FindTableBoxStartNode();
            const SwTableNode* pOldTabSttNode = pOldTabBoxSttNode ? pOldTabBoxSttNode->FindTableNode() : nullptr;
            const SwNode* pNewTabBoxSttNode = GetPoint()->GetNode().FindTableBoxStartNode();
            const SwTableNode* pNewTabSttNode = pNewTabBoxSttNode ? pNewTabBoxSttNode->FindTableNode() : nullptr;

            const bool bCellChanged = pOldTabSttNode && pNewTabSttNode &&
                                      pOldTabSttNode == pNewTabSttNode &&
                                      pOldTabBoxSttNode && pNewTabBoxSttNode &&
                                      pOldTabBoxSttNode != pNewTabBoxSttNode;

            if ( bCellChanged )
            {
                // Set cursor to start/end of covered cell:
                SwTableBox* pTableBox = pOldTabBoxSttNode->GetTableBox();
                if ( pTableBox && pTableBox->getRowSpan() > 1 )
                {
                    pTableBox = & pTableBox->FindEndOfRowSpan(
                        pOldTabSttNode->GetTable(),
                        o3tl::narrowing<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset));
                    SwPosition& rPtPos = *GetPoint();
                    SwNodeIndex aNewIdx( *pTableBox->GetSttNd() );
                    rPtPos.Assign( aNewIdx );

                    SwNodes::GoNextSection(&rPtPos, false, false);
                    SwContentNode* pContentNode = GetPointContentNode();
                    if ( pContentNode )
                    {
                        GetPoint()->SetContent( bLeft ? pContentNode->Len() : 0 );

                        // Redo the move:
                        if ( !Move( fnMove, fnGo ) )
                            break;
                    }
                }
                m_nRowSpanOffset = 0;
            }
        }

        // Check if I'm inside a covered cell. Correct cursor if necessary and
        // store covered cell:
        const SwNode* pTableBoxStartNode = GetPoint()->GetNode().FindTableBoxStartNode();
        if ( pTableBoxStartNode )
        {
            const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox();
            if ( pTableBox && pTableBox->getRowSpan() < 1 )
            {
                // Store the row span offset:
                m_nRowSpanOffset = pTableBox->getRowSpan();

                // Move cursor to non-covered cell:
                const SwTableNode* pTableNd = pTableBoxStartNode->FindTableNode();
                pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() );
                SwPosition& rPtPos = *GetPoint();
                SwNodeIndex aNewIdx( *pTableBox->GetSttNd() );
                rPtPos.Assign( aNewIdx );

                SwNodes::GoNextSection(&rPtPos, false, false);
                SwContentNode* pContentNode = GetPointContentNode();
                if ( pContentNode )
                {
                    GetPoint()->SetContent( bLeft ? pContentNode->Len() : 0 );
                }
            }
        }
        --nCnt;
    }

    // here come some special rules for visual cursor travelling
    if ( pSttFrame )
    {
        SwNode& rTmpNode = GetPoint()->GetNode();
        if ( &rTmpNode != &rNode && rTmpNode.IsTextNode() )
        {
            Point aPt;
            std::pair<Point, bool> const tmp(aPt, true);
            const SwContentFrame* pEndFrame = rTmpNode.GetTextNode()->getLayoutFrame(
                GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
                GetPoint(), &tmp);
            if ( pEndFrame )
            {
                if ( ! pEndFrame->IsRightToLeft() != ! pSttFrame->IsRightToLeft() )
                {
                    if ( ! bLeft )
                        pEndFrame->RightMargin( this );
                    else
                        pEndFrame->LeftMargin( this );
                }
            }
        }
    }

    return 0 == nCnt && !IsInProtectTable( true ) &&
            !IsSelOvr( SwCursorSelOverFlags::Toggle |
                       SwCursorSelOverFlags::ChangePos );
}

// calculate cursor bidi level: extracted from UpDown()
void SwCursor::DoSetBidiLevelUpDown()
{
    SwNode& rNode = GetPoint()->GetNode();
    if ( !rNode.IsTextNode() )
        return;

    SwTextFrame const* pFrame;
    const SwScriptInfo* pSI =
        SwScriptInfo::GetScriptInfo( *rNode.GetTextNode(), &pFrame );
    if ( !pSI )
        return;

    const sal_Int32 nPos = GetPoint()->GetContentIndex();

    if (!(nPos && nPos < rNode.GetTextNode()->GetText().getLength()))
        return;

    TextFrameIndex const nIndex(pFrame->MapModelToView(rNode.GetTextNode(), nPos));
    const sal_uInt8 nCurrLevel = pSI->DirType( nIndex );
    const sal_uInt8 nPrevLevel = pSI->DirType( nIndex - TextFrameIndex(1) );

    if ( nCurrLevel % 2 != nPrevLevel % 2 )
    {
        // set cursor level to the lower of the two levels
        SetCursorBidiLevel( std::min( nCurrLevel, nPrevLevel ) );
    }
    else
        SetCursorBidiLevel( nCurrLevel );
}

bool SwCursor::UpDown( bool bUp, sal_uInt16 nCnt,
                       Point const * pPt, tools::Long nUpDownX,
                       SwRootFrame & rLayout)
{
    SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(this);
    bool bAdjustTableCursor = false;

    // If the point/mark of the table cursor in the same box then set cursor to
    // beginning of the box
    if( pTableCursor && GetPointNode().StartOfSectionNode() ==
                    GetMarkNode().StartOfSectionNode() )
    {
        if ( End() != GetPoint() )
            Exchange();
        bAdjustTableCursor = true;
    }

    bool bRet = false;
    Point aPt;
    if( pPt )
        aPt = *pPt;
    std::pair<Point, bool> const temp(aPt, true);
    SwContentFrame* pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &temp);

    if( pFrame )
    {
        SwCursorSaveState aSave( *this );

        if( !pPt )
        {
            SwRect aTmpRect;
            pFrame->GetCharRect( aTmpRect, *GetPoint() );
            aPt = aTmpRect.Pos();

            nUpDownX = pFrame->IsVertical() ?
                aPt.getY() - pFrame->getFrameArea().Top() :
                aPt.getX() - pFrame->getFrameArea().Left();
        }

        // It is allowed to move footnotes in other footnotes but not sections
        const bool bChkRange = !pFrame->IsInFootnote() || HasMark();
        const SwPosition aOldPos( *GetPoint() );
        const bool bInReadOnly = IsReadOnlyAvailable();

        if ( bAdjustTableCursor && !bUp )
        {
            // Special case: We have a table cursor but the start box has more
            // than one paragraph. If we want to go down, we have to set the
            // point to the last frame in the table box. This is only necessary
            // if we do not already have a table selection
            const SwStartNode* pTableNd = GetPointNode().FindTableBoxStartNode();
            OSL_ENSURE( pTableNd, "pTableCursor without SwTableNode?" );

            if ( pTableNd ) // safety first
            {
                const SwNode* pEndNd = pTableNd->EndOfSectionNode();
                GetPoint()->Assign( *pEndNd );
                pTableCursor->Move( fnMoveBackward, GoInNode );
                std::pair<Point, bool> const tmp(aPt, true);
                pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
            }
        }

        while( nCnt &&
               (bUp ? pFrame->UnitUp( this, nUpDownX, bInReadOnly )
                    : pFrame->UnitDown( this, nUpDownX, bInReadOnly ) ) &&
                CheckNodesRange( aOldPos.GetNode(), GetPoint()->GetNode(), bChkRange ))
        {
            std::pair<Point, bool> const tmp(aPt, true);
            pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
            --nCnt;
        }

        // iterate over whole number of items?
        if( !nCnt && !IsSelOvr( SwCursorSelOverFlags::Toggle |
                                SwCursorSelOverFlags::ChangePos ) )
        {
            if( !pTableCursor )
            {
                // try to position the cursor at half of the char-rect's height
                DisableCallbackAction a(rLayout);
                std::pair<Point, bool> const tmp(aPt, true);
                pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
                SwCursorMoveState eTmpState( CursorMoveState::UpDown );
                eTmpState.m_bSetInReadOnly = bInReadOnly;
                SwRect aTmpRect;
                pFrame->GetCharRect( aTmpRect, *GetPoint(), &eTmpState );
                if ( pFrame->IsVertical() )
                {
                    aPt.setX(aTmpRect.Center().getX());
                    pFrame->Calc(rLayout.GetCurrShell()->GetOut());
                    aPt.setY(pFrame->getFrameArea().Top() + nUpDownX);
                }
                else
                {
                    aPt.setY(aTmpRect.Center().getY());
                    pFrame->Calc(rLayout.GetCurrShell()->GetOut());
                    aPt.setX(pFrame->getFrameArea().Left() + nUpDownX);
                }
                pFrame->GetModelPositionForViewPoint( GetPoint(), aPt, &eTmpState );
            }
            bRet = !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos );
        }
        else if (!pFrame->IsInFootnote()) // tdf#150457 Jump to the begin/end
                                          // of the first/last line only if the
                                          // cursor is not inside a footnote
        {
            sal_Int32 nOffset = 0;

            // Jump to beginning or end of line when the cursor at first or last line.
            if(!bUp)
            {
                SwTextNode* pTextNd = GetPoint()->GetNode().GetTextNode();
                if (pTextNd)
                    nOffset = pTextNd->GetText().getLength();
            }
            const SwPosition aPos(*GetPointContentNode(), nOffset);

            //if cursor has already been at start or end of file,
            //Update cursor to change nUpDownX.
            if ( aOldPos.GetContentIndex() == nOffset )
            {
                if (SwEditShell* pSh = GetDoc().GetEditShell())
                    pSh->UpdateCursor();
                bRet = false;
            }
            else{
                *GetPoint() = aPos; // just give a new position
                bRet = true;
            }

        }
        else
            *GetPoint() = aOldPos;

        DoSetBidiLevelUpDown(); // calculate cursor bidi level
    }
    return bRet;
}

bool SwCursor::LeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI)
{
    Point aPt;
    std::pair<Point, bool> const tmp(aPt, true);
    SwContentFrame const*const pFrame = GetPointContentNode()->getLayoutFrame(
        &rLayout, GetPoint(), &tmp);

    // calculate cursor bidi level
    if ( pFrame )
        SetCursorBidiLevel( pFrame->IsRightToLeft() ? 1 : 0 );

    SwCursorSaveState aSave( *this );
    return pFrame
           && (bLeft ? pFrame->LeftMargin( this ) : pFrame->RightMargin( this, bAPI ) )
           && !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos );
}

bool SwCursor::IsAtLeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI) const
{
    bool bRet = false;
    Point aPt;
    std::pair<Point, bool> const tmp(aPt, true);
    SwContentFrame const*const pFrame = GetPointContentNode()->getLayoutFrame(
        &rLayout, GetPoint(), &tmp);
    if( pFrame )
    {
        SwPaM aPam( *GetPoint() );
        if( !bLeft && aPam.GetPoint()->GetContentIndex() )
            aPam.GetPoint()->AdjustContent(-1);
        bRet = (bLeft ? pFrame->LeftMargin( &aPam )
                      : pFrame->RightMargin( &aPam, bAPI ))
                && (!pFrame->IsTextFrame()
                    || static_cast<SwTextFrame const*>(pFrame)->MapModelToViewPos(*aPam.GetPoint())
                        == static_cast<SwTextFrame const*>(pFrame)->MapModelToViewPos(*GetPoint()));
    }
    return bRet;
}

bool SwCursor::SttEndDoc( bool bStt )
{
    SwCursorSaveState aSave( *this );
    // Never jump over section boundaries during selection!
    // Can the cursor still moved on?
    SwMoveFnCollection const & fnMove = bStt ? fnMoveBackward : fnMoveForward;
    bool bRet = (!HasMark() || !IsNoContent() ) &&
                    Move( fnMove, GoInDoc ) &&
                    !IsInProtectTable( true ) &&
                    !IsSelOvr( SwCursorSelOverFlags::Toggle |
                               SwCursorSelOverFlags::ChangePos |
                               SwCursorSelOverFlags::EnableRevDirection );
    return bRet;
}

bool SwCursor::GoPrevNextCell( bool bNext, sal_uInt16 nCnt )
{
    const SwTableNode* pTableNd = GetPoint()->GetNode().FindTableNode();
    if( !pTableNd )
        return false;

    // If there is another EndNode in front of the cell's StartNode then there
    // exists a previous cell
    SwCursorSaveState aSave( *this );
    SwPosition& rPtPos = *GetPoint();

    while( nCnt-- )
    {
        const SwNode* pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
        const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox();

        // Check if we have to move the cursor to a covered cell before
        // proceeding:
        if (m_nRowSpanOffset)
        {
            if ( pTableBox && pTableBox->getRowSpan() > 1 )
            {
                pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(),
                    o3tl::narrowing<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset));
                rPtPos.Assign( *pTableBox->GetSttNd() );
                pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
            }
            m_nRowSpanOffset = 0;
        }

        const SwNode* pTmpNode = bNext ?
                                 pTableBoxStartNode->EndOfSectionNode() :
                                 pTableBoxStartNode;

        SwNodeIndex aCellIdx( *pTmpNode, bNext ? 1 : -1 );
        if(  (bNext && !aCellIdx.GetNode().IsStartNode()) ||
            (!bNext && !aCellIdx.GetNode().IsEndNode()) )
            return false;

        if (bNext)
            rPtPos.Assign( aCellIdx );
        else
            rPtPos.Assign(*aCellIdx.GetNode().StartOfSectionNode());

        pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
        pTableBox = pTableBoxStartNode->GetTableBox();
        if ( pTableBox && pTableBox->getRowSpan() < 1 )
        {
            m_nRowSpanOffset = pTableBox->getRowSpan();
            // move cursor to non-covered cell:
            pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() );
            rPtPos.Assign( *pTableBox->GetSttNd() );
        }
    }

    rPtPos.Adjust(SwNodeOffset(1));
    if( !rPtPos.GetNode().IsContentNode() )
        SwNodes::GoNextSection(&rPtPos, true, false);
    GetPoint()->SetContent( 0 );

    return !IsInProtectTable( true );
}

bool SwTableCursor::GotoTable( const UIName& )
{
    return false; // invalid action
}

bool SwCursor::GotoTable( const UIName& rName )
{
    bool bRet = false;
    if ( !HasMark() )
    {
        SwTable* pTmpTable = SwTable::FindTable( GetDoc().FindTableFormatByName( rName ) );
        if( pTmpTable )
        {
            // a table in a normal nodes array
            SwCursorSaveState aSave( *this );
            GetPoint()->Assign( *pTmpTable->GetTabSortBoxes()[ 0 ]->
                                GetSttNd()->FindTableNode() );
            Move( fnMoveForward, GoInContent );
            bRet = !IsSelOvr();
        }
    }
    return bRet;
}

bool SwCursor::GotoTableBox( const OUString& rName )
{
    bool bRet = false;
    const SwTableNode* pTableNd = GetPoint()->GetNode().FindTableNode();
    if( pTableNd )
    {
        // retrieve box by name
        const SwTableBox* pTableBox = pTableNd->GetTable().GetTableBox( rName );
        if( pTableBox && pTableBox->GetSttNd() &&
            ( !pTableBox->GetFrameFormat()->GetProtect().IsContentProtected() ||
              IsReadOnlyAvailable() ) )
        {
            SwCursorSaveState aSave( *this );
            GetPoint()->Assign( *pTableBox->GetSttNd() );
            Move( fnMoveForward, GoInContent );
            bRet = !IsSelOvr();
        }
    }
    return bRet;
}

bool SwCursor::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
{
    // for optimization test something before
    const SwNode* pNd = &GetPoint()->GetNode();
    bool bShortCut = false;
    if ( fnWhichPara == GoCurrPara )
    {
        // #i41048#
        // If fnWhichPara == GoCurrPara then (*fnWhichPara)( *this, fnPosPara )
        // can already move the cursor to a different text node. In this case
        // we better check if IsSelOvr().
        const SwContentNode* pContentNd = pNd->GetContentNode();
        if ( pContentNd )
        {
            const sal_Int32 nSttEnd = &fnPosPara == &fnMoveForward ? 0 : pContentNd->Len();
            if ( GetPoint()->GetContentIndex() != nSttEnd )
                bShortCut = true;
        }
    }
    else
    {
        if ( pNd->IsTextNode() &&
             pNd->GetNodes()[ pNd->GetIndex() +
                    SwNodeOffset(fnWhichPara == GoNextPara ? 1 : -1 ) ]->IsTextNode() )
            bShortCut = true;
    }

    if ( bShortCut )
        return (*fnWhichPara)( *this, fnPosPara );

    // else we must use the SaveStructure, because the next/prev is not
    // a same node type.
    SwCursorSaveState aSave( *this );
    return (*fnWhichPara)( *this, fnPosPara ) &&
            !IsInProtectTable( true ) &&
            !IsSelOvr( SwCursorSelOverFlags::Toggle |
                       SwCursorSelOverFlags::ChangePos );
}

bool SwCursor::MoveSection( SwWhichSection fnWhichSect,
                                SwMoveFnCollection const & fnPosSect)
{
    SwCursorSaveState aSave( *this );
    return (*fnWhichSect)( *this, fnPosSect ) &&
            !IsInProtectTable( true ) &&
            !IsSelOvr( SwCursorSelOverFlags::Toggle |
                       SwCursorSelOverFlags::ChangePos );
}

void SwCursor::RestoreSavePos()
{
    // This method is not supposed to be used in cases when nodes may be
    // deleted; detect such cases, but do not crash (example: fdo#40831).
    SwNodeOffset uNodeCount(GetPoint()->GetNodes().Count());
    OSL_ENSURE(m_vSavePos.empty() || m_vSavePos.back().nNode < uNodeCount,
        "SwCursor::RestoreSavePos: invalid node: "
        "probably something was deleted; consider using SwUnoCursor instead");
    if (m_vSavePos.empty() || m_vSavePos.back().nNode >= uNodeCount)
        return;

    GetPoint()->Assign( m_vSavePos.back().nNode );

    sal_Int32 nIdx = 0;
    if ( GetPointContentNode() )
    {
        if (m_vSavePos.back().nContent <= GetPointContentNode()->Len())
            nIdx = m_vSavePos.back().nContent;
        else
        {
            nIdx = GetPointContentNode()->Len();
            OSL_FAIL("SwCursor::RestoreSavePos: invalid content index");
        }
    }
    GetPoint()->SetContent( nIdx );
}

bool SwCursor::IsInHyphenatedWord(SwRootFrame const& rLayout) const
{
    // skip, if the selected text contains multiple nodes, long text or space,
    // or not in word starting or word ending positions
    if ( HasMark() && ( GetPoint()->GetNode() != GetMark()->GetNode() ||
            abs(GetPoint()->GetContentIndex() - GetMark()->GetContentIndex()) > 100 ||
            GetText().indexOf(' ') > -1 ||
            !( IsStartWordWT(css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, &rLayout) ||
                    IsEndWordWT(css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, &rLayout) ) ) )
        return false;

    // skip, if no selection and the cursor is not in a word
    if ( !HasMark() && !IsInWordWT(css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, &rLayout) )
        return false;

    bool bRet = false;
    Point aPt;
    std::pair<Point, bool> const tmp(aPt, true);
    SwContentFrame const*const pFrame = GetPointContentNode()->getLayoutFrame(
        &rLayout, GetPoint(), &tmp);
    if( pFrame && pFrame->IsTextFrame() )
    {
        SwPaM aPam( *GetPoint() );
        bRet = static_cast<SwTextFrame const*>(pFrame)->IsInHyphenatedWord( &aPam, HasMark() );
    }
    return bRet;
}

SwTableCursor::SwTableCursor( const SwPosition &rPos )
    : SwCursor( rPos, nullptr )
{
    m_bParked = false;
    m_bChanged = false;
    m_nTablePtNd = SwNodeOffset(0);
    m_nTableMkNd = SwNodeOffset(0);
    m_nTablePtCnt = 0;
    m_nTableMkCnt = 0;
}

SwTableCursor::~SwTableCursor() {}

static bool
lcl_SeekEntry(const SwSelBoxes& rTmp, SwStartNode const*const pSrch,
        size_t & o_rFndPos)
{
    SwNodeOffset nIdx = pSrch->GetIndex();

    size_t nO = rTmp.size();
    if( nO > 0 )
    {
        nO--;
        size_t nU = 0;
        while( nU <= nO )
        {
            size_t nM = nU + ( nO - nU ) / 2;
            if( rTmp[ nM ]->GetSttNd() == pSrch )
            {
                o_rFndPos = nM;
                return true;
            }
            else if( rTmp[ nM ]->GetSttIdx() < nIdx )
                nU = nM + 1;
            else if( nM == 0 )
                return false;
            else
                nO = nM - 1;
        }
    }
    return false;
}

SwCursor* SwTableCursor::MakeBoxSels( SwCursor* pCurrentCursor )
{
    if (m_bChanged)
    {
        if (m_bParked)
        {
            // move back into content
            Exchange();
            Move( fnMoveForward );
            Exchange();
            Move( fnMoveForward );
            m_bParked = false;
        }

        m_bChanged = false;

        // create temporary copies so that all boxes that
        // have already cursors can be removed
        SwSelBoxes aTmp(m_SelectedBoxes);

        // compare old and new ones
        SwNodes& rNds = pCurrentCursor->GetDoc().GetNodes();
        const SwStartNode* pSttNd;
        SwCursor* pCur = pCurrentCursor;
        do {
            size_t nPos;
            bool bDel = false;
            pSttNd = pCur->GetPoint()->GetNode().FindTableBoxStartNode();
            if( !pCur->HasMark() || !pSttNd ||
                pSttNd != pCur->GetMark()->GetNode().FindTableBoxStartNode() )
                bDel = true;

            else if( lcl_SeekEntry( aTmp, pSttNd, nPos ))
            {
                SwNodeIndex aIdx( *pSttNd, 1 );
                const SwNode* pNd = &aIdx.GetNode();
                if( !pNd->IsContentNode() )
                    pNd = SwNodes::GoNextSection(&aIdx, true, false);

                SwPosition* pPos = pCur->GetMark();
                if( pNd != &pPos->GetNode() )
                    pPos->Assign( *pNd );
                pPos->SetContent( 0 );

                aIdx.Assign( *pSttNd->EndOfSectionNode(), - 1 );
                pNd = &aIdx.GetNode();
                if( !pNd->IsContentNode() )
                    pNd = SwNodes::GoPrevSection( &aIdx, true, false );

                pPos = pCur->GetPoint();
                if (pNd && pNd != &pPos->GetNode())
                    pPos->Assign( *pNd );
                pPos->SetContent( pNd ? static_cast<const SwContentNode*>(pNd)->Len() : 0);

                aTmp.erase( aTmp.begin() + nPos );
            }
            else
                bDel = true;

            pCur = pCur->GetNext();
            if( bDel )
            {
                SwCursor* pDel = pCur->GetPrev();
                if (pDel == dynamic_cast<SwShellCursor*>(pCurrentCursor))
                    pCurrentCursor = pDel->GetPrev();

                if( pDel == pCurrentCursor )
                    pCurrentCursor->DeleteMark();
                else
                    delete pDel;
            }
        } while ( pCurrentCursor != pCur );

        for (size_t nPos = 0; nPos < aTmp.size(); ++nPos)
        {
            pSttNd = aTmp[ nPos ]->GetSttNd();

            SwNodeIndex aIdx( *pSttNd, 1 );
            if( &aIdx.GetNodes() != &rNds )
                break;
            SwNode* pNd = &aIdx.GetNode();
            if( !pNd->IsContentNode() )
                pNd = SwNodes::GoNextSection(&aIdx, true, false);

            SwPaM *const pNew = (!pCurrentCursor->IsMultiSelection() && !pCurrentCursor->HasMark())
                ? pCurrentCursor
                : pCurrentCursor->Create( pCurrentCursor );
            pNew->GetPoint()->Assign( *pNd );
            pNew->SetMark();

            SwPosition* pPos = pNew->GetPoint();
            pPos->Assign( *pSttNd->EndOfSectionNode(), - 1 );
            pNd = &pPos->GetNode();
            if( !pNd->IsContentNode() )
                pNd = SwNodes::GoPrevSection( pPos, true, false );
            if (pNd)
                pPos->AssignEndIndex(*static_cast<SwContentNode*>(pNd));
        }
    }
    return pCurrentCursor;
}

void SwTableCursor::InsertBox( const SwTableBox& rTableBox )
{
    SwTableBox* pBox = const_cast<SwTableBox*>(&rTableBox);
    m_SelectedBoxes.insert(pBox);
    m_bChanged = true;
}

void SwTableCursor::DeleteBox(size_t const nPos)
{
    m_SelectedBoxes.erase(m_SelectedBoxes.begin() + nPos);
    m_bChanged = true;
}

bool SwTableCursor::NewTableSelection()
{
    bool bRet = false;
    const SwNode *pStart = GetPointNode().FindTableBoxStartNode();
    const SwNode *pEnd = GetMarkNode().FindTableBoxStartNode();
    if( pStart && pEnd )
    {
        const SwTableNode *pTableNode = pStart->FindTableNode();
        if( pTableNode == pEnd->FindTableNode() &&
            pTableNode->GetTable().IsNewModel() )
        {
            bRet = true;
            SwSelBoxes aNew(m_SelectedBoxes);
            pTableNode->GetTable().CreateSelection( pStart, pEnd, aNew,
                SwTable::SEARCH_NONE, false );
            ActualizeSelection( aNew );
        }
    }
    return bRet;
}

void SwTableCursor::ActualizeSelection( const SwSelBoxes &rNew )
{
    size_t nOld = 0, nNew = 0;
    while (nOld < m_SelectedBoxes.size() && nNew < rNew.size())
    {
        SwTableBox const*const pPOld = m_SelectedBoxes[ nOld ];
        const SwTableBox* pPNew = rNew[ nNew ];
        if( pPOld == pPNew )
        {   // this box will stay
            ++nOld;
            ++nNew;
        }
        else if( pPOld->GetSttIdx() < pPNew->GetSttIdx() )
        {
            DeleteBox( nOld ); // this box has to go
        }
        else
        {
            InsertBox( *pPNew ); // this is a new one
            ++nOld;
            ++nNew;
        }
    }

    while (nOld < m_SelectedBoxes.size())
    {
        DeleteBox( nOld ); // some more to delete
    }

    for ( ; nNew < rNew.size(); ++nNew ) // some more to insert
    {
        InsertBox( *rNew[ nNew ] );
    }
}

bool SwTableCursor::IsCursorMovedUpdate()
{
    if( !IsCursorMoved() )
        return false;

    m_nTableMkNd = GetMark()->GetNodeIndex();
    m_nTablePtNd = GetPoint()->GetNodeIndex();
    m_nTableMkCnt = GetMark()->GetContentIndex();
    m_nTablePtCnt = GetPoint()->GetContentIndex();
    return true;
}

/// park table cursor on the boxes' start node
void SwTableCursor::ParkCursor()
{
    // de-register index from text node
    SwNode* pNd = &GetPoint()->GetNode();
    if( !pNd->IsStartNode() )
        pNd = pNd->StartOfSectionNode();
    GetPoint()->Assign(*pNd);

    pNd = &GetMark()->GetNode();
    if( !pNd->IsStartNode() )
        pNd = pNd->StartOfSectionNode();
    GetMark()->Assign(*pNd);

    m_bChanged = true;
    m_bParked = true;
}

bool SwTableCursor::HasReadOnlyBoxSel() const
{
    bool bRet = false;
    for (size_t n = m_SelectedBoxes.size(); n; )
    {
        if (m_SelectedBoxes[--n]->GetFrameFormat()->GetProtect().IsContentProtected())
        {
            bRet = true;
            break;
        }
    }
    return bRet;
}

bool SwTableCursor::HasHiddenBoxSel() const
{
    bool bRet = false;
    for (size_t n = m_SelectedBoxes.size(); n; )
    {
        if (m_SelectedBoxes[--n]->GetFrameFormat()->IsHidden())
        {
            bRet = true;
            break;
        }
    }
    return bRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
