/* -*- 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 <config_wasm_strip.h>
 
#include <com/sun/star/text/XTextRange.hpp>
 
#include <hintids.hxx>
#include <svx/srchdlg.hxx>
#include <sfx2/viewsh.hxx>
#include <SwSmartTagMgr.hxx>
#include <doc.hxx>
#include <rootfrm.hxx>
#include <pagefrm.hxx>
#include <cntfrm.hxx>
#include <viewimp.hxx>
#include <pam.hxx>
#include <swselectionlist.hxx>
#include "BlockCursor.hxx"
#include <ndtxt.hxx>
#include <flyfrm.hxx>
#include <dview.hxx>
#include <viewopt.hxx>
#include <crsrsh.hxx>
#include <tabfrm.hxx>
#include <txtfrm.hxx>
#include <sectfrm.hxx>
#include <swtable.hxx>
#include "callnk.hxx"
#include <viscrs.hxx>
#include <section.hxx>
#include <docsh.hxx>
#include <scriptinfo.hxx>
#include <globdoc.hxx>
#include <pamtyp.hxx>
#include <mdiexp.hxx>
#include <fmteiro.hxx>
#include <wrong.hxx>
#include <unotextrange.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <GrammarContact.hxx>
#include <OnlineAccessibilityCheck.hxx>
#include <comphelper/flagguard.hxx>
#include <strings.hrc>
#include <IDocumentLayoutAccess.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <comphelper/sequence.hxx>
#include <sfx2/lokhelper.hxx>
#include <editeng/editview.hxx>
#include <editeng/frmdir.hxx>
#include <sal/log.hxx>
#include <PostItMgr.hxx>
#include <DocumentSettingManager.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <tabcol.hxx>
#include <wrtsh.hxx>
#include <undobj.hxx>
#include <view.hxx>
#include <hints.hxx>
#include <tools/json_writer.hxx>
#include <redline.hxx>
#include <boost/property_tree/ptree.hpp>
 
using namespace com::sun::star;
 
/**
 * Check if pCurrentCursor points into already existing ranges and delete those.
 * @param Pointer to SwCursor object
 */
static void CheckRange( SwCursor* pCurrentCursor )
{
    auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition*
 
    SwPaM *pTmpDel = nullptr,
          *pTmp = pCurrentCursor->GetNext();
 
    // Search the complete ring
    while( pTmp != pCurrentCursor )
    {
        auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
        if( *pStt <= *pTmpStt )
        {
            if( *pEnd > *pTmpStt ||
                ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
                pTmpDel = pTmp;
        }
        else
            if( *pStt < *pTmpEnd )
                pTmpDel = pTmp;
 
         // If Point or Mark is within the Cursor range, we need to remove the old
        // range. Take note that Point does not belong to the range anymore.
        pTmp = pTmp->GetNext();
        delete pTmpDel;         // Remove old range
        pTmpDel = nullptr;
    }
}
 
// SwCursorShell
 
/**
 * Add a copy of current cursor, append it after current, and collapse current cursor.
 * @return - Returns a newly created copy of current cursor.
 */
SwPaM * SwCursorShell::CreateCursor()
{
    // don't create new Cursor with active table Selection
    assert(!IsTableMode());
 
    // ensure that m_pCurrentCursor is valid; if it's invalid it would be
    // copied to pNew and then pNew would be deleted in UpdateCursor() below
    ClearUpCursors();
 
    // New cursor as copy of current one. Add to the ring.
    // Links point to previously created one, ie forward.
    SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
 
    // Hide PaM logically, to avoid undoing the inverting from
    // copied PaM (#i75172#)
    pNew->swapContent(*m_pCurrentCursor);
 
    m_pCurrentCursor->DeleteMark();
 
    UpdateCursor( SwCursorShell::SCROLLWIN );
    return pNew;
}
 
/**
 * Delete current Cursor, making the following one the current.
 * Note, this function does not delete anything if there is no other cursor.
 * @return - returns true if there was another cursor and we deleted one.
 */
void SwCursorShell::DestroyCursor()
{
    // don't delete Cursor with active table Selection
    assert(!IsTableMode());
 
    // Is there a next one? Don't do anything if not.
    if(!m_pCurrentCursor->IsMultiSelection())
        return;
 
    SwCallLink aLk( *this ); // watch Cursor-Moves
    SwCursor* pNextCursor = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
    delete m_pCurrentCursor;
    m_pCurrentCursor = dynamic_cast<SwShellCursor*>(pNextCursor);
    UpdateCursor();
}
 
/**
 * Create and return a new shell cursor.
 * Simply returns the current shell cursor if there is no selection
 * (HasSelection()).
 */
SwCursor & SwCursorShell::CreateNewShellCursor()
{
    if (HasSelection())
    {
        (void) CreateCursor(); // n.b. returns old cursor
    }
    return *GetCursor();
}
 
/**
 * Return the current shell cursor
 * @return - returns current `SwPaM` shell cursor
 */
SwCursor & SwCursorShell::GetCurrentShellCursor()
{
    return *GetCursor();
}
 
/**
 * Return pointer to the current shell cursor
 * @return - returns pointer to current `SwCursor` shell cursor
 */
SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const
{
    if( m_pTableCursor )
    {
        if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() )
        {
            //don't re-create 'parked' cursors
            if( m_pTableCursor->GetPoint()->GetNodeIndex() &&
                m_pTableCursor->GetMark()->GetNodeIndex() )
            {
                const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode();
                if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
                {
                    pCNd = m_pTableCursor->GetMarkContentNode();
                    if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
                    {
                        SwShellTableCursor* pTC = m_pTableCursor;
                        GetLayout()->MakeTableCursors( *pTC );
                    }
                }
            }
        }
 
        if( m_pTableCursor->IsChgd() )
        {
            const_cast<SwCursorShell*>(this)->m_pCurrentCursor =
                dynamic_cast<SwShellCursor*>(m_pTableCursor->MakeBoxSels( m_pCurrentCursor ));
        }
    }
    return m_pCurrentCursor;
}
 
void SwCursorShell::StartAction()
{
    if( !ActionPend() )
    {
        // save for update of the ribbon bar
        const SwNode& rNd = m_pCurrentCursor->GetPoint()->GetNode();
        m_nCurrentNode = rNd.GetIndex();
        m_nCurrentContent = m_pCurrentCursor->GetPoint()->GetContentIndex();
        m_nCurrentNdTyp = rNd.GetNodeType();
        if( rNd.IsTextNode() )
            m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true );
        else
            m_nLeftFramePos = 0;
    }
    SwViewShell::StartAction(); // to the SwViewShell
}
 
void SwCursorShell::EndAction( const bool bIdleEnd )
{
    comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
    bool bVis = m_bSVCursorVis;
 
    // Idle-formatting?
    if( bIdleEnd && Imp()->HasPaintRegion() )
    {
        m_pCurrentCursor->Hide();
    }
 
    // Update all invalid numberings before the last action
    if( 1 == mnStartAction )
        GetDoc()->UpdateNumRule();
 
    // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call.
    //           Only the UpdateCursor shows the cursor.
    bool bSavSVCursorVis = m_bSVCursorVis;
    m_bSVCursorVis = false;
 
    SwViewShell::EndAction( bIdleEnd );   // have SwViewShell go first
 
    m_bSVCursorVis = bSavSVCursorVis;
 
    if( ActionPend() )
    {
        if( bVis )    // display SV-Cursor again
            m_pVisibleCursor->Show();
 
        return;
    }
 
    sal_uInt16 eFlags = SwCursorShell::CHKRANGE;
    if ( !bIdleEnd )
        eFlags |= SwCursorShell::SCROLLWIN;
 
    UpdateCursor( eFlags, bIdleEnd );      // Show Cursor changes
 
    {
        SwCallLink aLk( *this );           // Watch cursor moves,
        aLk.m_nNode = m_nCurrentNode;        // possibly call the link
        aLk.m_nNodeType = m_nCurrentNdTyp;
        aLk.m_nContent = m_nCurrentContent;
        aLk.m_nLeftFramePos = m_nLeftFramePos;
 
        if( !m_nCursorMove ||
            ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) )
            // display Cursor & Selections again
            ShowCursors( m_bSVCursorVis );
    }
    // call ChgCall if there is still one
    if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() )
    {
        m_aChgLnk.Call(nullptr);
        m_bChgCallFlag = false;       // reset flag
    }
}
 
void SwCursorShell::SttCursorMove()
{
#ifdef DBG_UTIL
    OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." );
#endif
    ++m_nCursorMove;
    StartAction();
}
 
void SwCursorShell::EndCursorMove( const bool bIdleEnd )
{
#ifdef DBG_UTIL
    OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." );
#endif
    EndAction( bIdleEnd );
    --m_nCursorMove;
#ifdef DBG_UTIL
    if( !m_nCursorMove )
        m_bInCMvVisportChgd = false;
#endif
}
 
bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
                             bool bVisualAllowed )
{
    if( IsTableMode() )
        return bLeft ? GoPrevCell() : GoNextCell();
 
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    bool bRet = false;
 
    // #i27615# Handle cursor in front of label.
    const SwTextNode* pTextNd = nullptr;
 
    if( m_pBlockCursor )
        m_pBlockCursor->clearPoints();
 
    // 1. CASE: Cursor is in front of label. A move to the right
    // will simply reset the bInFrontOfLabel flag:
    SwShellCursor* pShellCursor = getShellCursor( true );
    if ( !bLeft && pShellCursor->IsInFrontOfLabel() )
    {
        SetInFrontOfLabel( false );
        bRet = true;
    }
    // 2. CASE: Cursor is at beginning of numbered paragraph. A move
    // to the left will simply set the bInFrontOfLabel flag:
    else if (bLeft
        && pShellCursor->GetPoint()->GetNode().IsTextNode()
        && static_cast<SwTextFrame const*>(
            pShellCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout())
            )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0)
        && !pShellCursor->IsInFrontOfLabel()
        && !pShellCursor->HasMark()
        && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->GetNode()))
        && pTextNd->HasVisibleNumberingOrBullet())
    {
        SetInFrontOfLabel( true );
        bRet = true;
    }
    // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag:
    else
    {
        const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar();
        // #i107447#
        // To avoid loop the reset of <bInFrontOfLabel> flag is no longer
        // reflected in the return value <bRet>.
        const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false );
        bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed,
                                      bSkipHidden, !IsOverwriteCursor(),
                                      GetLayout(),
                                      GetViewOptions()->IsFieldName());
        if ( !bRet && bLeft && bResetOfInFrontOfLabel )
        {
            // undo reset of <bInFrontOfLabel> flag
            SetInFrontOfLabel( true );
        }
    }
 
    if( bRet )
    {
        UpdateCursor();
    }
 
    return bRet;
}
 
void SwCursorShell::MarkListLevel( const OUString& sListId,
                                 const int nListLevel )
{
    if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel)
        return;
 
    // Writer redraws the "marked" list with the field shading, if there
    // is no field shading then the marked list would be redrawn for no
    // visually identifiable reason, so skip the mark if field shadings
    // are disabled.
    const bool bVisuallyMarked(GetViewOptions()->IsFieldShadings());
    if (bVisuallyMarked)
    {
        if ( !m_sMarkedListId.isEmpty() )
            mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false );
 
        if ( !sListId.isEmpty() )
            mxDoc->MarkListLevel( sListId, nListLevel, true );
    }
 
    m_sMarkedListId = sListId;
    m_nMarkedListLevel = nListLevel;
}
 
void SwCursorShell::UpdateMarkedListLevel()
{
    SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(),
            GetCursor_()->GetPoint()->GetNode());
 
    if ( !pTextNd )
        return;
 
    if (!pTextNd->IsNumbered(GetLayout()))
    {
        m_pCurrentCursor->SetInFrontOfLabel_( false );
        MarkListLevel( OUString(), 0 );
    }
    else if ( m_pCurrentCursor->IsInFrontOfLabel() )
    {
        if ( pTextNd->IsInList() )
        {
            assert(pTextNd->GetActualListLevel() >= 0 &&
                   pTextNd->GetActualListLevel() < MAXLEVEL);
            MarkListLevel( pTextNd->GetListId(),
                           pTextNd->GetActualListLevel() );
        }
    }
    else
    {
        MarkListLevel( OUString(), 0 );
    }
}
 
void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage)
{
#ifdef ACCESSIBLE_LAYOUT
    if( Imp()->IsAccessible() )
        Imp()->FirePageChangeEvent( nOldPage, nNewPage );
#else
    (void)nOldPage;
    (void)nNewPage;
#endif
}
 
void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn)
{
#ifdef ACCESSIBLE_LAYOUT
    if( Imp()->IsAccessible() )
        Imp()->FireColumnChangeEvent( nOldColumn,  nNewColumn);
#else
    (void)nOldColumn;
    (void)nNewColumn;
#endif
}
 
void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection)
{
#ifdef ACCESSIBLE_LAYOUT
    if( Imp()->IsAccessible() )
        Imp()->FireSectionChangeEvent( nOldSection, nNewSection );
#else
    (void)nOldSection;
    (void)nNewSection;
#endif
}
 
bool SwCursorShell::bColumnChange()
{
    SwFrame* pCurrFrame = GetCurrFrame(false);
 
    if (pCurrFrame == nullptr)
    {
        return false;
    }
 
    SwFrame* pCurrCol=pCurrFrame->FindColFrame();
 
    while(pCurrCol== nullptr && pCurrFrame!=nullptr )
    {
        SwLayoutFrame* pParent = pCurrFrame->GetUpper();
        if(pParent!=nullptr)
        {
            pCurrCol=static_cast<SwFrame*>(pParent)->FindColFrame();
            pCurrFrame = pParent;
        }
        else
        {
            break;
        }
    }
 
    if(m_oldColFrame == pCurrCol)
        return false;
    else
    {
        m_oldColFrame = pCurrCol;
        return true;
    }
}
 
bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt )
{
    CurrShell aCurr( this );
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
 
    bool bTableMode = IsTableMode();
    SwShellCursor* pTmpCursor = getShellCursor( true );
 
    bool bRet = pTmpCursor->UpDown( bUp, nCnt );
    // #i40019# UpDown should always reset the bInFrontOfLabel flag:
    bRet |= SetInFrontOfLabel(false);
 
    if( m_pBlockCursor )
        m_pBlockCursor->clearPoints();
 
    if( bRet )
    {
        m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint
        if( !ActionPend() )
        {
            CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN;
            if( !bTableMode )
                eUpdateMode = static_cast<CursorFlag>(eUpdateMode
                            | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE);
            UpdateCursor( o3tl::narrowing<sal_uInt16>(eUpdateMode) );
        }
    }
    return bRet;
}
 
bool SwCursorShell::LRMargin( bool bLeft, bool bAPI)
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    CurrShell aCurr( this );
    m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint
 
    const bool bTableMode = IsTableMode();
    SwShellCursor* pTmpCursor = getShellCursor( true );
 
    if( m_pBlockCursor )
        m_pBlockCursor->clearPoints();
 
    const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI);
 
    bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI);
 
    if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() )
    {
        const SwTextNode * pTextNd = GetCursor_()->GetPointNode().GetTextNode();
        assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == pTextNd);
        if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() )
            SetInFrontOfLabel( true );
    }
    else if ( !bLeft )
    {
        bRet = SetInFrontOfLabel( false ) || bRet;
    }
 
    if( bRet )
    {
        UpdateCursor();
    }
    return bRet;
}
 
bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const
{
    const SwShellCursor* pTmpCursor = getShellCursor( true );
    return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI);
}
 
bool SwCursorShell::SttEndDoc( bool bStt )
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
 
    SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor;
    bool bRet = pTmpCursor->SttEndDoc( bStt );
    if( bRet )
    {
        if( bStt )
            pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header)
        if( m_pBlockCursor )
        {
            m_pBlockCursor->clearPoints();
            RefreshBlockCursor();
        }
 
        UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
    }
    return bRet;
}
 
const SwTableNode* SwCursorShell::IsCursorInTable() const
{
    if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount())
    {   // find the table that has the selected boxes
        return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode();
    }
    return m_pCurrentCursor->GetPointNode().FindTableNode();
}
 
// fun cases to consider:
// * outermost table
//   - into para => SA/ESA
//   - into prev/next table => continue...
//   - no prev/next => done
// * inner table
//   - into containing cell => SA/ESA
//   - into prev/next of containing cell
//      + into para
//      + into table nested in prev/next cell
//   - out of table -> as above
// => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table
// - parent reached => SA/ESA depending
// - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable
// - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable
//   => select-all cannot select a sequence of table with no para at same level; only 1 table
// - no parent, no prev/next => TrySelectOuterTable
 
bool SwCursorShell::MoveOutOfTable()
{
    SwPosition const point(*getShellCursor(false)->GetPoint());
    SwPosition const mark(*getShellCursor(false)->GetMark());
 
    for (auto const fnMove : {&fnMoveBackward, &fnMoveForward})
    {
        Push();
        SwCursor *const pCursor(getShellCursor(false));
 
        pCursor->Normalize(fnMove == &fnMoveBackward);
        pCursor->DeleteMark();
        SwTableNode const*const pTable(pCursor->GetPoint()->GetNode().FindTableNode());
        assert(pTable);
        while (MovePara(GoInContent, *fnMove))
        {
            SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode());
            if (!pBox)
            {
                Pop(SwCursorShell::PopMode::DeleteStack);
                return true; // moved to paragraph at top-level of text
            }
            if (pBox->GetIndex() < pTable->GetIndex()
                && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex())
            {
                Pop(SwCursorShell::PopMode::DeleteStack);
                return true; // pBox contains start position (pTable)
            }
        }
 
        Pop(SwCursorShell::PopMode::DeleteCurrent);
        // FIXME: Pop doesn't restore original cursor if nested tables
        *getShellCursor(false)->GetPoint() = point;
        getShellCursor(false)->SetMark();
        *getShellCursor(false)->GetMark() = mark;
    }
    return false;
}
 
bool SwCursorShell::TrySelectOuterTable()
{
    assert(m_pTableCursor);
    SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->GetNode().FindTableNode());
    SwNodes const& rNodes(rInnerTable.GetNodes());
    SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode());
    if (!pOuterTable)
    {
        return false;
    }
 
    // manually select boxes of pOuterTable
    SwNodeIndex firstCell(*pOuterTable, +1);
    SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode());
    SwSelBoxes aNew;
    pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(),
            aNew, SwTable::SEARCH_NONE, false);
    // set table cursor to 1st / last content which may be in inner table
    SwContentNode* const pStart = SwNodes::GoNext(&firstCell);
    assert(pStart); // must at least find the previous point node
    lastCell = *lastCell.GetNode().EndOfSectionNode();
    SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell);
    assert(pEnd); // must at least find the previous point node
    delete m_pTableCursor;
    m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(),
            SwPosition(*pEnd, 0), Point());
    m_pTableCursor->ActualizeSelection( aNew );
    m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes
 
    // this will update m_pCurrentCursor based on m_pTableCursor
    UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
 
    return true;
}
 
/// find XText start node
static SwStartNode const* FindTextStart(SwPosition const& rPos)
{
    SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode());
    while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode()))
    {
        pStartNode = pStartNode->StartOfSectionNode();
    }
    return pStartNode;
}
 
static SwStartNode const* FindParentText(SwShellCursor const& rCursor)
{
    // find closest section containing both start and end - ignore Sections
    SwStartNode const* pStartNode(FindTextStart(*rCursor.Start()));
    SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode());
    while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex())
    {
        pStartNode = pStartNode->StartOfSectionNode();
    }
    while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex())
    {
        pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode();
    }
    assert(pStartNode->EndOfSectionNode() == pEndNode);
 
    return (pStartNode->IsSectionNode() || pStartNode->IsTableNode())
        ? FindTextStart(SwPosition(*pStartNode))
        : pStartNode;
}
 
bool SwCursorShell::MoveStartText()
{
    SwPosition const old(*m_pCurrentCursor->GetPoint());
    SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
    assert(pStartNode);
    SwTableNode const*const pTable(pStartNode->FindTableNode());
    m_pCurrentCursor->GetPoint()->Assign(*pStartNode);
    SwNodes::GoNext(m_pCurrentCursor->GetPoint());
    while (auto* pFoundTable = m_pCurrentCursor->GetPoint()->GetNode().FindTableNode())
    {
        if (pFoundTable == pTable)
            break;
        if (pTable && pTable->GetIndex() >= pFoundTable->GetIndex())
            break;
        if (!MoveOutOfTable())
            break;
    }
    UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
    return old != *m_pCurrentCursor->GetPoint();
}
 
// select all inside the current XText, with table or hidden para at start/end
void SwCursorShell::ExtendedSelectAll(bool bFootnotes)
{
    // find common ancestor node of both ends of cursor
    SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
    assert(pStartNode);
    if (IsTableMode())
    {   // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode
        TableCursorToCursor();
    }
    SwNodes& rNodes = GetDoc()->GetNodes();
    m_pCurrentCursor->Normalize(true);
    SwPosition* pPos = m_pCurrentCursor->GetPoint();
    pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode));
    SwNodes::GoNext(pPos);
    pPos = m_pCurrentCursor->GetMark();
    pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode()));
    SwContentNode* pCNd = SwNodes::GoPrevious( pPos );
    if (pCNd)
        pPos->AssignEndIndex(*pCNd);
}
 
static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart)
{
    for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i)
    {
        SwNode const& rNode(*rStart.GetNodes()[i]);
        switch (rNode.GetNodeType())
        {
            case SwNodeType::Section:
                if (rNode.GetSectionNode()->GetSection().IsHidden())
                    return SwCursorShell::StartsWith::HiddenSection;
                continue;
            case SwNodeType::Table:
                return SwCursorShell::StartsWith::Table;
            case SwNodeType::Text:
                if (rNode.GetTextNode()->IsHidden())
                {
                    return SwCursorShell::StartsWith::HiddenPara;
                }
                return SwCursorShell::StartsWith::None;
            default:
                return SwCursorShell::StartsWith::None;
        }
    }
    return SwCursorShell::StartsWith::None;
}
 
static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart)
{
    for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i)
    {
        SwNode const& rNode(*rStart.GetNodes()[i]);
        switch (rNode.GetNodeType())
        {
            case SwNodeType::End:
                if (auto pStartNode = rNode.StartOfSectionNode(); pStartNode->IsTableNode())
                {
                    return SwCursorShell::StartsWith::Table;
                }
                else if (pStartNode->IsSectionNode())
                {
                    if (pStartNode->GetSectionNode()->GetSection().IsHidden())
                        return SwCursorShell::StartsWith::HiddenSection;
                }
                    //TODO buggy SwUndoRedline in testTdf137503?                assert(rNode.StartOfSectionNode()->IsSectionNode());
            break;
            case SwNodeType::Text:
                if (rNode.GetTextNode()->IsHidden())
                {
                    return SwCursorShell::StartsWith::HiddenPara;
                }
                return SwCursorShell::StartsWith::None;
            default:
                return SwCursorShell::StartsWith::None;
        }
    }
    return SwCursorShell::StartsWith::None;
}
 
// return the node that is the start of the extended selection (to include table
// or section start nodes; looks like extending for end nodes is not required)
SwCursorShell::ExtendedSelection SwCursorShell::ExtendedSelectedAll() const
{
    if (m_pTableCursor)
    {
        return {};
    }
 
    SwNodes& rNodes = GetDoc()->GetNodes();
    SwShellCursor const*const pShellCursor = getShellCursor(false);
    SwStartNode const* pStartNode(FindParentText(*pShellCursor));
 
    SwNodeIndex nNode(*pStartNode);
    SwContentNode* pStart = SwNodes::GoNext(&nNode);
    if (!pStart)
    {
        return {};
    }
 
    nNode = *pStartNode->EndOfSectionNode();
    SwContentNode* pEnd = SwNodes::GoPrevious(&nNode);
    if (!pEnd)
    {
        return {};
    }
 
    SwPosition aStart(*pStart, 0);
    SwPosition aEnd(*pEnd, pEnd->Len());
    if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End()))
    {
        return {};
    }
 
    auto const ends(::EndsWith(*pStartNode));
    if (::StartsWith(*pStartNode) == StartsWith::None
        && ends == StartsWith::None)
    {
        return {}; // "ordinary" selection will work
    }
 
    ::std::vector<SwTableNode*> tablesAtEnd;
    if (ends == StartsWith::Table)
    {
        SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]);
        while (pLastNode->IsEndNode())
        {
            SwNode *const pNode(pLastNode->StartOfSectionNode());
            if (pNode->IsTableNode())
            {
                tablesAtEnd.push_back(pNode->GetTableNode());
                pLastNode = rNodes[pNode->GetIndex() - 1];
            }
            else if (pNode->IsSectionNode())
            {
                pLastNode = rNodes[pLastNode->GetIndex() - 1];
            }
        }
        assert(!tablesAtEnd.empty());
    }
 
    // tdf#133990 ensure directly containing section is included in SwUndoDelete
    while (pStartNode->IsSectionNode()
        && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1
        && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex())
    {
        pStartNode = pStartNode->StartOfSectionNode();
    }
 
    // pStartNode is the node that fully contains the selection - the first
    // node of the selection is the first node inside pStartNode
    return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd);
}
 
typename SwCursorShell::StartsWith SwCursorShell::StartsWith_()
{
    SwShellCursor const*const pShellCursor = getShellCursor(false);
    // first, check if this is invalid; ExtendedSelectAll(true) may result in
    // a) an ordinary selection that is valid
    // b) a selection that is extended
    // c) a selection that is invalid and will cause FindParentText to loop
    SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras());
    if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex()
        && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex())
    {
        return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling!
    }
    SwStartNode const*const pStartNode(FindParentText(*pShellCursor));
    if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None)
    {
        return ret;
    }
    if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None)
    {
        return ret;
    }
    return StartsWith::None;
}
 
bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage )
{
    bool bRet = false;
 
    // never jump of section borders at selection
    if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() )
    {
        SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
        CurrShell aCurr( this );
 
        SwCursorSaveState aSaveState( *m_pCurrentCursor );
        Point& rPt = m_pCurrentCursor->GetPtPos();
        std::pair<Point, bool> tmp(rPt, false);
        SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()->
            getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
        if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) &&
            !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
                                 SwCursorSelOverFlags::ChangePos ))
        {
            UpdateCursor();
            bRet = true;
        }
    }
    return bRet;
}
 
bool SwCursorShell::isInHiddenFrame(SwShellCursor* pShellCursor)
{
    SwContentNode *pCNode = pShellCursor->GetPointContentNode();
    std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
    SwContentFrame *const pFrame = pCNode
        ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp)
        : nullptr;
    return !pFrame || pFrame->IsHiddenNow();
}
 
// sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara
static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell,
    SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara)
{
    SwContentNode *const pCNode = pShellCursor->GetPointContentNode();
    assert(pCNode); // surely can't have moved otherwise?
    std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
    SwContentFrame const*const pFrame = pCNode->getLayoutFrame(
            pShell->GetLayout(), pShellCursor->GetPoint(), &tmp);
    if (!pFrame || !pFrame->IsTextFrame())
    {
        return false;
    }
    SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(*pFrame));
    TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint()));
    if (&fnParaStart == &fnPosPara)
    {
        return ix == TextFrameIndex(0);
    }
    else
    {
        assert(&fnParaEnd == &fnPosPara);
        return ix == TextFrameIndex(rTextFrame.GetText().getLength());
    }
}
 
bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    SwShellCursor* pTmpCursor = getShellCursor( true );
    bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara );
    if( bRet )
    {
        //keep going until we get something visible, i.e. skip
        //over hidden paragraphs, don't get stuck at the start
        //which is what SwCursorShell::UpdateCursorPos will reset
        //the position to if we pass it a position in an
        //invisible hidden paragraph field
        while (isInHiddenFrame(pTmpCursor)
                || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara))
        {
            if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara))
                break;
        }
 
        UpdateCursor();
    }
    return bRet;
}
 
bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect,
                                SwMoveFnCollection const & fnPosSect)
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    SwCursor* pTmpCursor = getShellCursor( true );
    bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect );
    if( bRet )
        UpdateCursor();
    return bRet;
 
}
 
// position cursor
 
static SwFrame* lcl_IsInHeaderFooter( SwNode& rNd, Point& rPt )
{
    SwFrame* pFrame = nullptr;
    SwContentNode* pCNd = rNd.GetContentNode();
    if( pCNd )
    {
        std::pair<Point, bool> tmp(rPt, false);
        SwContentFrame *pContentFrame = pCNd->getLayoutFrame(
            pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
            nullptr, &tmp);
        pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr;
        while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() )
            pFrame = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->AnchorFrame()
                                    : pFrame->GetUpper();
    }
    return pFrame;
}
 
bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const
{
    Point aPt;
    SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->GetNode(), aPt );
    if( pFrame && pbInHeader )
        *pbInHeader = pFrame->IsHeaderFrame();
    return nullptr != pFrame;
}
 
int SwCursorShell::SetCursor(const Point& rLPt, bool bOnlyText, bool bBlock, bool bFieldInfo)
{
    CurrShell aCurr( this );
 
    SwShellCursor* pCursor = getShellCursor( bBlock );
    SwPosition aPos( *pCursor->GetPoint() );
    Point aPt( rLPt );
    Point & rCurrentCursorPt = pCursor->GetPtPos();
    SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel :
                                    bOnlyText ?  CursorMoveState::SetOnlyText : CursorMoveState::NONE );
    aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
    aTmpState.m_bFieldInfo = bFieldInfo; // always set cursor at field-start if point is over field
    aTmpState.m_bPosMatchesBounds = bFieldInfo; // always set cursor at character-start if over char
 
    SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
 
    if ( pTextNd && !IsTableMode() &&
        // #i37515# No bInFrontOfLabel during selection
        !pCursor->HasMark() &&
        pTextNd->HasVisibleNumberingOrBullet() )
    {
        aTmpState.m_bInFrontOfLabel = true; // #i27615#
    }
    else
    {
        aTmpState.m_bInFrontOfLabel = false;
    }
 
    int bRet = CRSR_POSOLD |
                ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState )
                    ? 0 : CRSR_POSCHG );
 
    const bool bOldInFrontOfLabel = IsInFrontOfLabel();
    const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel;
 
    pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel );
 
    if( CursorMoveState::RightMargin == aTmpState.m_eState )
        m_eMvState = CursorMoveState::RightMargin;
    // is the new position in header or footer?
    SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.GetNode(), aPt );
    if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() ==
        pCursor->GetPoint()->GetNode().StartOfSectionNode() )
        // same table column and not in header/footer -> back
        return bRet;
 
    if( m_pBlockCursor && bBlock )
    {
        m_pBlockCursor->setEndPoint( rLPt );
        if( !pCursor->HasMark() )
            m_pBlockCursor->setStartPoint( rLPt );
        else if( !m_pBlockCursor->getStartPoint() )
            m_pBlockCursor->setStartPoint( pCursor->GetMkPos() );
    }
    if( !pCursor->HasMark() )
    {
        // is at the same position and if in header/footer -> in the same
        if( aPos == *pCursor->GetPoint() &&
            bOldInFrontOfLabel == bNewInFrontOfLabel )
        {
            if( pFrame )
            {
                if( pFrame->getFrameArea().Contains( rCurrentCursorPt ))
                    return bRet;
            }
            else if( aPos.GetNode().IsContentNode() )
            {
                // in the same frame?
                std::pair<Point, bool> tmp(m_aCharRect.Pos(), false);
                SwFrame* pOld = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
                                GetLayout(), nullptr, &tmp);
                tmp.first = aPt;
                SwFrame* pNew = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
                                GetLayout(), nullptr, &tmp);
                if( pNew == pOld )
                    return bRet;
            }
        }
    }
    else
    {
        // SSelection over not allowed sections or if in header/footer -> different
        if( !CheckNodesRange( aPos.GetNode(), pCursor->GetMark()->GetNode(), true )
            || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) ))
            return bRet;
 
        // is at same position but not in header/footer
        if( aPos == *pCursor->GetPoint() )
            return bRet;
    }
 
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    SwCursorSaveState aSaveState( *pCursor );
 
    *pCursor->GetPoint() = aPos;
    rCurrentCursorPt = aPt;
 
    // #i41424# Only update the marked number levels if necessary
    // Force update of marked number levels if necessary.
    if ( bNewInFrontOfLabel || bOldInFrontOfLabel )
        m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel );
    SetInFrontOfLabel( bNewInFrontOfLabel );
 
    if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) )
    {
        UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE );
        bRet &= ~CRSR_POSOLD;
    }
    else if( bOnlyText && !m_pCurrentCursor->HasMark() )
    {
        if( FindValidContentNode( bOnlyText ) )
        {
            // position cursor in a valid content
            if( aPos == *pCursor->GetPoint() )
                bRet = CRSR_POSOLD;
            else
            {
                UpdateCursor();
                bRet &= ~CRSR_POSOLD;
            }
        }
        else
        {
            // there is no valid content -> hide cursor
            m_pVisibleCursor->Hide(); // always hide visible cursor
            m_eMvState = CursorMoveState::NONE; // status for Cursor travelling
            m_bAllProtect = true;
            if( GetDoc()->GetDocShell() )
            {
                GetDoc()->GetDocShell()->SetReadOnlyUI();
                CallChgLnk(); // notify UI
            }
        }
    }
    return bRet;
}
 
void SwCursorShell::TableCursorToCursor()
{
    assert(m_pTableCursor);
    delete m_pTableCursor;
    m_pTableCursor = nullptr;
}
 
void SwCursorShell::BlockCursorToCursor()
{
    assert(m_pBlockCursor);
    if( m_pBlockCursor && !HasSelection() )
    {
        SwPaM& rPam = m_pBlockCursor->getShellCursor();
        m_pCurrentCursor->SetMark();
        *m_pCurrentCursor->GetPoint() = *rPam.GetPoint();
        if( rPam.HasMark() )
            *m_pCurrentCursor->GetMark() = *rPam.GetMark();
        else
            m_pCurrentCursor->DeleteMark();
    }
    delete m_pBlockCursor;
    m_pBlockCursor = nullptr;
}
 
void SwCursorShell::CursorToBlockCursor()
{
    if( !m_pBlockCursor )
    {
        SwPosition aPos( *m_pCurrentCursor->GetPoint() );
        m_pBlockCursor = new SwBlockCursor( *this, aPos );
        SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
        rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos();
        if( m_pCurrentCursor->HasMark() )
        {
            rBlock.SetMark();
            *rBlock.GetMark() = *m_pCurrentCursor->GetMark();
            rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos();
        }
    }
    m_pBlockCursor->clearPoints();
    RefreshBlockCursor();
}
 
void SwCursorShell::ClearMark()
{
    // is there any GetMark?
    if( m_pTableCursor )
    {
        std::vector<SwPaM*> vCursors;
        for(auto& rCursor : m_pCurrentCursor->GetRingContainer())
            if(&rCursor != m_pCurrentCursor)
                vCursors.push_back(&rCursor);
        for(auto pCursor : vCursors)
            delete pCursor;
        m_pTableCursor->DeleteMark();
 
        m_pCurrentCursor->DeleteMark();
 
        *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
        m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
        delete m_pTableCursor;
        m_pTableCursor = nullptr;
        m_pCurrentCursor->SwSelPaintRects::Show();
    }
    else
    {
        if( !m_pCurrentCursor->HasMark() )
            return;
        m_pCurrentCursor->DeleteMark();
        if( !m_nCursorMove )
            m_pCurrentCursor->SwSelPaintRects::Show();
    }
}
 
void SwCursorShell::NormalizePam(bool bPointFirst)
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    m_pCurrentCursor->Normalize(bPointFirst);
}
 
void SwCursorShell::SwapPam()
{
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    m_pCurrentCursor->Exchange();
}
 
//TODO: provide documentation
/** Search in the selected area for a Selection that covers the given point.
 
    It checks if a Selection exists but does
    not move the current cursor.
 
    @param rPt      The point to search at.
    @param bTstHit ???
*/
bool SwCursorShell::TestCurrPam(
    const Point & rPt,
    bool bTstHit )
{
    CurrShell aCurr( this );
 
    // check if the SPoint is in a table selection
    if( m_pTableCursor )
        return m_pTableCursor->Contains( rPt );
 
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    // search position <rPt> in document
    SwPosition aPtPos( *m_pCurrentCursor->GetPoint() );
    Point aPt( rPt );
 
    SwCursorMoveState aTmpState( CursorMoveState::NONE );
    aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
    aTmpState.m_bPosMatchesBounds = true; // treat last half of character same as first half
    if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit )
        return false;
 
    // search in all selections for this position
    SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor
    do
    {
        if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos)
            return true;               // return without update
        pCmp = pCmp->GetNext();
    } while (m_pCurrentCursor != pCmp);
    return false;
}
 
void SwCursorShell::KillPams()
{
    // Does any exist for deletion?
    if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() )
        return;
 
    while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
        delete m_pCurrentCursor->GetNext();
    m_pCurrentCursor->SetColumnSelection( false );
 
    if( m_pTableCursor )
    {
        // delete the ring of cursors
        m_pCurrentCursor->DeleteMark();
        *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
        m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
        delete m_pTableCursor;
        m_pTableCursor = nullptr;
    }
    else if( m_pBlockCursor )
    {
        // delete the ring of cursors
        m_pCurrentCursor->DeleteMark();
        SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
        *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint();
        m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos();
        rBlock.DeleteMark();
        m_pBlockCursor->clearPoints();
    }
    UpdateCursor( SwCursorShell::SCROLLWIN );
}
 
int SwCursorShell::CompareCursorStackMkCurrPt() const
{
    int nRet = 0;
    const SwPosition *pFirst = nullptr, *pSecond = nullptr;
    const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor;
    // cursor on stack is needed if we compare against stack
    if( pStack  )
    {
        pFirst = pStack->GetMark();
        pSecond = pCur->GetPoint();
    }
    if( !pFirst || !pSecond )
        nRet = INT_MAX;
    else if( *pFirst < *pSecond )
        nRet = -1;
    else if( *pFirst == *pSecond )
        nRet = 0;
    else
        nRet = 1;
    return nRet;
}
 
bool SwCursorShell::IsSelOnePara() const
{
    if (m_pCurrentCursor->IsMultiSelection())
    {
        return false;
    }
    if (m_pCurrentCursor->GetPoint()->GetNode() == m_pCurrentCursor->GetMark()->GetNode())
    {
        return true;
    }
    if (GetLayout()->HasMergedParas())
    {
        SwContentFrame const*const pFrame(GetCurrFrame(false));
        auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex());
        return FrameContainsNode(*pFrame, n);
    }
    return false;
}
 
bool SwCursorShell::IsSelStartPara() const
{
    if (m_pCurrentCursor->IsMultiSelection())
    {
        return false;
    }
    if (m_pCurrentCursor->GetPoint()->GetContentIndex() == 0 ||
                    m_pCurrentCursor->GetMark()->GetContentIndex() == 0)
    {
        return true;
    }
    if (GetLayout()->HasMergedParas())
    {
        SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
        if (pNode)
        {
            SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
                        pNode->getLayoutFrame(GetLayout())));
            if (pFrame)
            {
                return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
                    == TextFrameIndex(0);
            }
        }
        SwTextNode const*const pNode2(m_pCurrentCursor->GetMark()->GetNode().GetTextNode());
        if (pNode2)
        {
            SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
                        pNode2->getLayoutFrame(GetLayout())));
            if (pFrame)
            {
                return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetMark())
                    == TextFrameIndex(0);
            }
        }
    }
    return false;
}
 
bool SwCursorShell::IsSttPara() const
{
    if (GetLayout()->HasMergedParas())
    {
        SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
        if (pNode)
        {
            SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
                        pNode->getLayoutFrame(GetLayout())));
            if (pFrame)
            {
                return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
                    == TextFrameIndex(0);
            }
        }
    }
    return m_pCurrentCursor->GetPoint()->GetContentIndex() == 0;
}
 
bool SwCursorShell::IsEndPara() const
{
    if (GetLayout()->HasMergedParas())
    {
        SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
        if (pNode)
        {
            SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
                        pNode->getLayoutFrame(GetLayout())));
            if (pFrame)
            {
                return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
                    == TextFrameIndex(pFrame->GetText().getLength());
            }
        }
    }
    return m_pCurrentCursor->GetPoint()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->Len();
}
 
bool SwCursorShell::IsEndOfTable() const
{
    if (IsTableMode() || IsBlockMode() || !IsEndPara())
    {
        return false;
    }
    SwTableNode const*const pTableNode( IsCursorInTable() );
    if (!pTableNode)
    {
        return false;
    }
    SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode());
    SwNodeIndex const lastNode(*pEndTableNode, -2);
    SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core",
            "text node expected");
    return (lastNode == m_pCurrentCursor->GetPoint()->GetNode());
}
 
bool SwCursorShell::IsCursorInFootnote() const
{
    SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType();
    return aStartNodeType == SwStartNodeType::SwFootnoteStartNode;
}
 
Point SwCursorShell::GetCursorPagePos() const
{
    Point aRet(-1, -1);
    if (SwFrame *pFrame = GetCurrFrame())
    {
        if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame())
        {
            const Point& rDocPos = GetCursorDocPos();
            aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft();
        }
    }
    return aRet;
}
 
bool SwCursorShell::IsInFrontOfLabel() const
{
    return m_pCurrentCursor->IsInFrontOfLabel();
}
 
bool SwCursorShell::SetInFrontOfLabel( bool bNew )
{
    if ( bNew != IsInFrontOfLabel() )
    {
        m_pCurrentCursor->SetInFrontOfLabel_( bNew );
        UpdateMarkedListLevel();
        return true;
    }
    return false;
}
 
namespace {
 
void collectUIInformation(const OUString& aPage)
{
    EventDescription aDescription;
    aDescription.aAction = "GOTO";
    aDescription.aParameters = {{"PAGE", aPage}};
    aDescription.aID = "writer_edit";
    aDescription.aKeyWord = "SwEditWinUIObject";
    aDescription.aParent = "MainWindow";
    UITestLogger::getInstance().logEvent(aDescription);
}
 
}
 
bool SwCursorShell::GotoPage( sal_uInt16 nPage )
{
    CurrShell aCurr( this );
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    SwCursorSaveState aSaveState( *m_pCurrentCursor );
    bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) &&
                    !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
                                         SwCursorSelOverFlags::ChangePos );
    if( bRet )
        UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
 
    collectUIInformation(OUString::number(nPage));
    return bRet;
}
 
void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos)
{
    SwContentFrame* pFrame = GetCurrFrame();
    pFrame->GetCharRect( rRect, *pPos );
}
 
void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum,
                              bool bAtCursorPos, const bool bCalcFrame )
{
    CurrShell aCurr( this );
    // page number: first visible page or the one at the cursor
    const SwContentFrame* pCFrame;
    const SwPageFrame *pPg = nullptr;
 
    if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) ||
                       nullptr == (pPg   = pCFrame->FindPageFrame()) )
    {
        pPg = Imp()->GetFirstVisPage(GetOut());
        while( pPg && pPg->IsEmptyPage() )
            pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
    }
    // pPg has to exist with a default of 1 for the special case "Writerstart"
    rnPhyNum  = pPg? pPg->GetPhyPageNum() : 1;
    rnVirtNum = pPg? pPg->GetVirtPageNum() : 1;
}
 
sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty()
{
    CurrShell aCurr(this);
    // page number: first visible page or the one at the cursor
    const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true);
    const SwPageFrame* pPg = nullptr;
 
    if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame()))
    {
        pPg = Imp()->GetFirstVisPage(GetOut());
        while (pPg && pPg->IsEmptyPage())
            pPg = static_cast<const SwPageFrame*>(pPg->GetNext());
    }
 
    sal_uInt16 nPageNo = 0;
    while (pPg)
    {
        if (!pPg->IsEmptyPage())
            ++nPageNo;
        pPg = static_cast<const SwPageFrame*>(pPg->GetPrev());
    }
    return nPageNo;
}
 
sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext )
{
    CurrShell aCurr( this );
    // page number: first visible page or the one at the cursor
    const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut());
    if( pPg )
    {
        const SwTwips nPageTop = pPg->getFrameArea().Top();
 
        if( bNext )
        {
            // go to next view layout row:
            do
            {
                pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
            }
            while( pPg && pPg->getFrameArea().Top() == nPageTop );
 
            while( pPg && pPg->IsEmptyPage() )
                pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
        }
        else
        {
            // go to previous view layout row:
            do
            {
                pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
            }
            while( pPg && pPg->getFrameArea().Top() == nPageTop );
 
            while( pPg && pPg->IsEmptyPage() )
                pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
        }
    }
    // pPg has to exist with a default of 1 for the special case "Writerstart"
    return pPg ? pPg->GetPhyPageNum() : USHRT_MAX;
}
 
sal_uInt16 SwCursorShell::GetPageCnt()
{
    CurrShell aCurr( this );
    // return number of pages
    return GetLayout()->GetPageNum();
}
 
OUString SwCursorShell::getPageRectangles()
{
    CurrShell aCurr(this);
    SwRootFrame* pLayout = GetLayout();
    OUStringBuffer aBuf;
    for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
    {
        aBuf.append(OUString::number(pFrame->getFrameArea().Left())
            + ", "
            + OUString::number(pFrame->getFrameArea().Top())
            + ", "
            + OUString::number(pFrame->getFrameArea().Width())
            + ", "
            + OUString::number(pFrame->getFrameArea().Height())
            + "; ");
    }
    if (!aBuf.isEmpty())
        aBuf.setLength( aBuf.getLength() - 2); // remove the last "; "
    return aBuf.makeStringAndClear();
}
 
void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const
{
    auto pView = const_cast<SdrView*>(GetDrawView());
    if (pView->GetTextEditObject())
    {
        // Blinking cursor.
        EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
        rEditView.RegisterOtherShell(pOtherShell);
        rEditView.ShowCursor();
        rEditView.RegisterOtherShell(nullptr);
        // Text selection, if any.
        rEditView.DrawSelectionXOR(pOtherShell);
 
        // Shape text lock.
        if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView())
        {
            OString sRect = pOutlinerView->GetOutputArea().toString();
            SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect);
        }
    }
    else
    {
        // Cursor position.
        m_pVisibleCursor->SetPosAndShow(pOtherShell);
        // Cursor visibility.
        if (GetSfxViewShell() != pOtherShell)
        {
            OString aPayload = OString::boolean(m_bSVCursorVis);
            SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
        }
        // Text selection.
        m_pCurrentCursor->Show(pOtherShell);
        // Graphic selection.
        pView->AdjustMarkHdl(pOtherShell);
    }
}
 
/// go to the next SSelection
bool SwCursorShell::GoNextCursor()
{
    if( !m_pCurrentCursor->IsMultiSelection() )
        return false;
 
    CurrShell aCurr( this );
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    m_pCurrentCursor = m_pCurrentCursor->GetNext();
 
    // #i24086#: show also all others
    if( !ActionPend() )
    {
        UpdateCursor();
        m_pCurrentCursor->Show(nullptr);
    }
    return true;
}
 
/// go to the previous SSelection
bool SwCursorShell::GoPrevCursor()
{
    if( !m_pCurrentCursor->IsMultiSelection() )
        return false;
 
    CurrShell aCurr( this );
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    m_pCurrentCursor = m_pCurrentCursor->GetPrev();
 
    // #i24086#: show also all others
    if( !ActionPend() )
    {
        UpdateCursor();
        m_pCurrentCursor->Show(nullptr);
    }
    return true;
}
 
void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext)
{
    SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
 
    if( !m_pCurrentCursor->IsMultiSelection() )
    {
        if( !m_pCurrentCursor->HasMark() )
            SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
        return;
    }
 
    if (bNext)
        GoNextCursor();
    else
        GoPrevCursor();
}
 
void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect)
{
    comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
    CurrShell aCurr( this );
 
    // always switch off all cursors when painting
    SwRect aRect( rRect );
 
    bool bVis = false;
    // if a cursor is visible then hide the SV cursor
    if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) )
    {
        bVis = true;
        m_pVisibleCursor->Hide();
    }
 
    // re-paint area
    SwViewShell::Paint(rRenderContext, rRect);
 
    if( m_bHasFocus && !m_bBasicHideCursor )
    {
        SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
 
        if( !ActionPend() )
        {
            // so that right/bottom borders will not be cropped
            pCurrentCursor->Invalidate( VisArea() );
            pCurrentCursor->Show(nullptr);
        }
        else
            pCurrentCursor->Invalidate( aRect );
 
    }
 
    if (SwPostItMgr* pPostItMgr = GetPostItMgr())
    {
        // No point in showing the cursor for Writer text when there is an
        // active annotation edit.
        if (bVis)
            bVis = !pPostItMgr->HasActiveSidebarWin();
    }
 
    if( m_bSVCursorVis && bVis ) // also show SV cursor again
        m_pVisibleCursor->Show();
}
 
void SwCursorShell::VisPortChgd( const SwRect & rRect )
{
    CurrShell aCurr( this );
    bool bVis; // switch off all cursors when scrolling
 
    // if a cursor is visible then hide the SV cursor
    bVis = m_pVisibleCursor->IsVisible();
    if( bVis )
        m_pVisibleCursor->Hide();
 
    m_bVisPortChgd = true;
    m_aOldRBPos.setX(VisArea().Right());
    m_aOldRBPos.setY(VisArea().Bottom());
 
    // For not having problems with the SV cursor, Update() is called for the
    // Window in SwViewShell::VisPo...
    // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact?
    SwViewShell::VisPortChgd( rRect ); // move area
 
    if( m_bSVCursorVis && bVis ) // show SV cursor again
        m_pVisibleCursor->Show();
 
    if( comphelper::LibreOfficeKit::isActive() && !rRect.Overlaps( m_aCharRect ))
    {
        boost::property_tree::ptree aParams;
        tools::Rectangle aRect(rRect.TopLeft(), Size(1, 1));
 
        aParams.put("rectangle", aRect.toString());
        aParams.put("scroll", true);
        aParams.put("hyperlink", "");
 
        SfxLokHelper::notifyOtherView(GetSfxViewShell(),
                                      GetSfxViewShell(),
                                      LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
                                      aParams);
    }
 
    if( m_nCursorMove )
        m_bInCMvVisportChgd = true;
 
    m_bVisPortChgd = false;
}
 
/** Set the cursor back into content.
 
    This should only be called if the cursor was moved (e.g. when deleting a
    text frame). The new position is calculated from its current position
    in the layout.
*/
void SwCursorShell::UpdateCursorPos()
{
    CurrShell aCurr( this );
    ++mnStartAction;
    SwShellCursor* pShellCursor = getShellCursor( true );
    Size aOldSz( GetDocSize() );
 
    if (isInHiddenFrame(pShellCursor) && !ExtendedSelectedAll())
    {
        SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText);
        aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
        GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(),
                                     &aTmpState );
        pShellCursor->DeleteMark();
        // kde45196-1.html: try to get to a non-hidden paragraph, there must
        // be one in the document body
        while (isInHiddenFrame(pShellCursor))
        {
            if (!pShellCursor->MovePara(GoNextPara, fnParaStart))
            {
                break;
            }
        }
        while (isInHiddenFrame(pShellCursor))
        {
            if (!pShellCursor->MovePara(GoPrevPara, fnParaStart))
            {
                break;
            }
        }
    }
    auto* pDoc = GetDoc();
    if (pDoc)
    {
        pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint());
        pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint());
    }
 
    --mnStartAction;
    if( aOldSz != GetDocSize() )
        SizeChgNotify();
}
 
// #i65475# - if Point/Mark in hidden sections, move them out
static bool lcl_CheckHiddenSection( SwPosition& rPos )
{
    bool bOk = true;
    const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode();
    if( pSectNd && pSectNd->GetSection().IsHiddenFlag() )
    {
        const SwNode* pFrameNd =
            rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() );
        bOk = pFrameNd != nullptr;
        SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames");
        rPos.Assign( *(bOk ? pFrameNd : pSectNd) );
    }
    return bOk;
}
 
/// Try to set the cursor to the next visible content node.
static void lcl_CheckHiddenPara( SwPosition& rPos )
{
    SwNodeIndex aTmp( rPos.GetNode() );
    SwTextNode* pTextNd = aTmp.GetNode().GetTextNode();
    while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) )
    {
        SwContentNode* pContent = SwNodes::GoNext(&aTmp);
        if ( pContent && pContent->IsTextNode() )
            pTextNd = pContent->GetTextNode();
        else
            pTextNd = nullptr;
    }
 
    if ( pTextNd )
        rPos.Assign( *pTextNd, 0 );
}
 
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
namespace {
 
// #i27301# - helper class that notifies the accessibility about invalid text
// selections in its destructor
class SwNotifyAccAboutInvalidTextSelections
{
    private:
        SwCursorShell& mrCursorSh;
 
    public:
        explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh )
            : mrCursorSh( _rCursorSh )
        {}
 
        ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE
        {
            mrCursorSh.InvalidateAccessibleParaTextSelection();
        }
};
 
}
#endif
 
void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd )
{
    Point nOldPos = m_pCurrentCursor->GetPtPos();
    CurrShell aCurr( this );
    ClearUpCursors();
 
    if (ActionPend())
    {
        if ( eFlags & SwCursorShell::READONLY )
            m_bIgnoreReadonly = true;
        return; // if not then no update
    }
 
    if (m_bNeedLayoutOnCursorUpdate)
    {
        // A previous spell check skipped a word that had a spelling error, because that word
        // had cursor. Now schedule the idle to call SwViewShell::LayoutIdle, to repeat the
        // spell check, in the hope that the cursor has left the word.
        m_aLayoutIdle.Start();
        m_bNeedLayoutOnCursorUpdate = false;
    }
 
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this );
#endif
 
    if ( m_bIgnoreReadonly )
    {
        m_bIgnoreReadonly = false;
        eFlags |= SwCursorShell::READONLY;
    }
 
    if( eFlags & SwCursorShell::CHKRANGE )    // check all cursor moves for
        CheckRange( m_pCurrentCursor );             // overlapping ranges
 
    if( !bIdleEnd )
        CheckTableBoxContent();
 
    // If the current cursor is in a table and point/mark in different boxes,
    // then the table mode is active (also if it is already active: m_pTableCursor)
    SwPaM* pTstCursor = getShellCursor( true );
    if( pTstCursor->HasMark() && !m_pBlockCursor &&
        SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) &&
          ( m_pTableCursor ||
            pTstCursor->GetPointNode().StartOfSectionNode() !=
            pTstCursor->GetMarkNode().StartOfSectionNode() ) && !mbSelectAll)
    {
        SwShellCursor* pITmpCursor = getShellCursor( true );
        Point aTmpPt( pITmpCursor->GetPtPos() );
        Point aTmpMk( pITmpCursor->GetMkPos() );
        SwPosition* pPos = pITmpCursor->GetPoint();
 
        // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out
        lcl_CheckHiddenSection( *pPos );
        lcl_CheckHiddenSection( *pITmpCursor->GetMark() );
 
        // Move cursor out of hidden paragraphs
        if ( !GetViewOptions()->IsShowHiddenChar() )
        {
            lcl_CheckHiddenPara( *pPos );
            lcl_CheckHiddenPara( *pITmpCursor->GetMark() );
        }
 
        std::pair<Point, bool> const tmp(aTmpPt, false);
        SwContentFrame *pTableFrame = pPos->GetNode().GetContentNode()->
                              getLayoutFrame( GetLayout(), pPos, &tmp);
 
        OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" );
 
        // --> Make code robust. The table cursor may point
        // to a table in a currently inactive header.
        SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr;
 
        if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 )
        {
            // First check if point is in repeated headline:
            bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame );
 
            // Second check if mark is in repeated headline:
            if ( !bInRepeatedHeadline )
            {
                std::pair<Point, bool> const tmp1(aTmpMk, false);
                SwContentFrame* pMarkTableFrame = pITmpCursor->GetMarkContentNode()->
                    getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1);
                OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" );
 
                if ( pMarkTableFrame )
                {
                    SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame();
                    OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" );
 
                    // Make code robust:
                    if ( pMarkTab )
                    {
                        bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame );
                    }
                }
            }
 
            // No table cursor in repeated headlines:
            if ( bInRepeatedHeadline )
            {
                pTableFrame = nullptr;
 
                // then only select inside the Box
                if( m_pTableCursor )
                {
                    SwMoveFnCollection const & fnPosSect = *pPos <  *pITmpCursor->GetMark()
                                                ? fnSectionStart
                                                : fnSectionEnd;
 
                    m_pCurrentCursor->SetMark();
                    *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark();
                    m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos();
                    m_pTableCursor->DeleteMark();
                    m_pTableCursor->SwSelPaintRects::Hide();
 
                    *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
                    GoCurrSection( *m_pCurrentCursor, fnPosSect );
                }
                else
                {
                    eFlags &= SwCursorShell::UPDOWN;
                    *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
                }
            }
        }
 
        // we really want a table selection
        if( pTab && pTableFrame )
        {
            if( !m_pTableCursor )
            {
                m_pTableCursor = new SwShellTableCursor( *this,
                                *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(),
                                *pPos, aTmpPt );
                m_pCurrentCursor->DeleteMark();
                m_pCurrentCursor->SwSelPaintRects::Hide();
 
                CheckTableBoxContent();
                if(!m_pTableCursor)
                {
                    SAL_WARN("sw.core", "fdo#74854: "
                        "this should not happen, but better lose the selection "
                        "rather than crashing");
                    return;
                }
            }
 
            SwCursorMoveState aTmpState( CursorMoveState::NONE );
            aTmpState.m_bRealHeight = true;
            {
                DisableCallbackAction a(*GetLayout());
                if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState))
                {
                    Point aCentrPt( m_aCharRect.Center() );
                    aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
                    pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState);
                    bool const bResult =
                        pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint());
                    OSL_ENSURE( bResult, "GetCharRect failed." );
                }
            }
 
            m_pVisibleCursor->Hide(); // always hide visible Cursor
            // scroll Cursor to visible area
            if( eFlags & SwCursorShell::SCROLLWIN &&
                (HasSelection() || eFlags & SwCursorShell::READONLY ||
                 !IsCursorReadonly()) )
            {
                SwFrame* pBoxFrame = pTableFrame;
                while( pBoxFrame && !pBoxFrame->IsCellFrame() )
                    pBoxFrame = pBoxFrame->GetUpper();
                if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() )
                    MakeVisible( pBoxFrame->getFrameArea() );
                else
                    MakeVisible( m_aCharRect );
            }
 
            // let Layout create the Cursors in the Boxes
            if( m_pTableCursor->IsCursorMovedUpdate() )
                GetLayout()->MakeTableCursors( *m_pTableCursor );
            if( m_bHasFocus && !m_bBasicHideCursor )
                m_pTableCursor->Show(nullptr);
 
            // set Cursor-Points to the new Positions
            m_pTableCursor->GetPtPos().setX(m_aCharRect.Left());
            m_pTableCursor->GetPtPos().setY(m_aCharRect.Top());
 
            if( m_bSVCursorVis )
            {
                m_aCursorHeight.setX(0);
                m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
                                  -m_aCharRect.Width() : m_aCharRect.Height());
                m_pVisibleCursor->Show(); // show again
            }
            m_eMvState = CursorMoveState::NONE;  // state for cursor travelling - GetModelPositionForViewPoint
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
            if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
                Imp()->InvalidateAccessibleCursorPosition( pTableFrame );
#endif
            return;
        }
    }
 
    if( m_pTableCursor )
    {
        // delete Ring
        while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
            delete m_pCurrentCursor->GetNext();
        m_pCurrentCursor->DeleteMark();
        *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
        m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
        delete m_pTableCursor;
        m_pTableCursor = nullptr;
    }
 
    m_pVisibleCursor->Hide(); // always hide visible Cursor
 
    // are we perhaps in a protected / hidden Section ?
    {
        SwShellCursor* pShellCursor = getShellCursor( true );
        bool bChgState = true;
        const SwSectionNode* pSectNd = pShellCursor->GetPointNode().FindSectionNode();
        if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
            ( !IsReadOnlyAvailable() &&
              pSectNd->GetSection().IsProtectFlag() &&
             ( !mxDoc->GetDocShell() ||
               !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) )
        {
            if( !FindValidContentNode( !HasDrawView() ||
                    0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
            {
                // everything protected/hidden -> special mode
                if( m_bAllProtect && !IsReadOnlyAvailable() &&
                    pSectNd->GetSection().IsProtectFlag() )
                    bChgState = false;
                else
                {
                    m_eMvState = CursorMoveState::NONE;     // state for cursor travelling
                    m_bAllProtect = true;
                    if( GetDoc()->GetDocShell() )
                    {
                        GetDoc()->GetDocShell()->SetReadOnlyUI();
                        CallChgLnk();       // notify UI!
                    }
                    return;
                }
            }
        }
        if( bChgState )
        {
            bool bWasAllProtect = m_bAllProtect;
            m_bAllProtect = false;
            if( bWasAllProtect && GetDoc()->GetDocShell() &&
                GetDoc()->GetDocShell()->IsReadOnlyUI() )
            {
                GetDoc()->GetDocShell()->SetReadOnlyUI( false );
                CallChgLnk();       // notify UI!
            }
        }
    }
 
    UpdateCursorPos();
 
    // The cursor must always point into content; there's some code
    // that relies on this. (E.g. in SwEditShell::GetScriptType, which always
    // loops _behind_ the last node in the selection, which always works if you
    // are in content.) To achieve this, we'll force cursor(s) to point into
    // content, if UpdateCursorPos() hasn't already done so.
    for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer())
    {
        // start will move forwards, end will move backwards
        bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() );
 
        // move point; forward if it's the start, backwards if it's the end
        if( ! rCmp.GetPoint()->GetNode().IsContentNode() )
            rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward,
                        GoInContent );
 
        // move mark (if exists); forward if it's the start, else backwards
        if( rCmp.HasMark() )
        {
            if( ! rCmp.GetMark()->GetNode().IsContentNode() )
            {
                rCmp.Exchange();
                rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward,
                            GoInContent );
                rCmp.Exchange();
            }
        }
    }
 
    SwRect aOld( m_aCharRect );
    bool bFirst = true;
    SwContentFrame *pFrame;
    int nLoopCnt = 100;
    SwShellCursor* pShellCursor = getShellCursor( true );
 
    do {
        bool bAgainst;
        do {
            bAgainst = false;
            std::pair<Point, bool> const tmp1(pShellCursor->GetPtPos(), false);
            pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(GetLayout(),
                        pShellCursor->GetPoint(), &tmp1);
            // if the Frame doesn't exist anymore, the complete Layout has to be
            // created, because there used to be a Frame here!
            if ( !pFrame )
            {
                // skip, if it is a hidden deleted cell without frame
                if ( GetLayout()->IsHideRedlines() )
                {
                    const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode();
                    if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete )
                        return;
                }
 
                do
                {
                    CalcLayout();
                    std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false);
                    pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(
                                GetLayout(), pShellCursor->GetPoint(), &tmp);
                }  while( !pFrame );
            }
            else if ( Imp()->IsIdleAction() )
                // Guarantee everything's properly formatted
                pFrame->PrepareCursor();
 
            // In protected Fly? but ignore in case of frame selection
            if( !IsReadOnlyAvailable() && pFrame->IsProtected() &&
                ( !Imp()->GetDrawView() ||
                  !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) &&
                (!mxDoc->GetDocShell() ||
                 !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) )
            {
                // look for a valid position
                bool bChgState = true;
                if( !FindValidContentNode(!HasDrawView() ||
                    0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
                {
                    // everything is protected / hidden -> special Mode
                    if( m_bAllProtect )
                        bChgState = false;
                    else
                    {
                        m_eMvState = CursorMoveState::NONE;     // state for cursor travelling
                        m_bAllProtect = true;
                        if( GetDoc()->GetDocShell() )
                        {
                            GetDoc()->GetDocShell()->SetReadOnlyUI();
                            CallChgLnk();       // notify UI!
                        }
                        return;
                    }
                }
 
                if( bChgState )
                {
                    bool bWasAllProtect = m_bAllProtect;
                    m_bAllProtect = false;
                    if( bWasAllProtect && GetDoc()->GetDocShell() &&
                        GetDoc()->GetDocShell()->IsReadOnlyUI() )
                    {
                        GetDoc()->GetDocShell()->SetReadOnlyUI( false );
                        CallChgLnk();       // notify UI!
                    }
                    m_bAllProtect = false;
                    bAgainst = true; // look for the right Frame again
                }
            }
        } while( bAgainst );
 
        SwCursorMoveState aTmpState( m_eMvState );
        aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
        aTmpState.m_bRealHeight = true;
        aTmpState.m_bRealWidth = IsOverwriteCursor();
        aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel();
 
        // #i27615#,#i30453#
        SwSpecialPos aSpecialPos;
        aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE;
        if (pShellCursor->IsInFrontOfLabel())
        {
            aTmpState.m_pSpecialPos = &aSpecialPos;
        }
 
        {
            DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action
            if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState))
            {
                Point& rPt = pShellCursor->GetPtPos();
                rPt = m_aCharRect.Center();
                pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState );
            }
        }
        UISizeNotify(); // tdf#96256 update view size
 
        if( !pShellCursor->HasMark() )
            m_aCursorHeight = aTmpState.m_aRealHeight;
        else
        {
            m_aCursorHeight.setX(0);
            m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
                              -m_aCharRect.Width() : m_aCharRect.Height());
        }
 
        if( !bFirst && aOld == m_aCharRect )
            break;
 
        // if the layout says that we are after the 100th iteration still in
        // flow then we should always take the current position for granted.
        // (see bug: 29658)
        if( !--nLoopCnt )
        {
            OSL_ENSURE( false, "endless loop? CharRect != OldCharRect ");
            break;
        }
        aOld = m_aCharRect;
        bFirst = false;
 
        // update cursor Points to the new Positions
        pShellCursor->GetPtPos().setX(m_aCharRect.Left());
        pShellCursor->GetPtPos().setY(m_aCharRect.Top());
 
        if( !(eFlags & SwCursorShell::UPDOWN ))   // delete old Pos. of Up/Down
        {
            DisableCallbackAction a(*GetLayout());
            pFrame->Calc(GetOut());
            m_nUpDownX = pFrame->IsVertical() ?
                       m_aCharRect.Top() - pFrame->getFrameArea().Top() :
                       m_aCharRect.Left() - pFrame->getFrameArea().Left();
        }
 
        // scroll Cursor to visible area
        if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
            (HasSelection() || eFlags & SwCursorShell::READONLY ||
             !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) )
        {
            // in case of scrolling this EndAction doesn't show the SV cursor
            // again, thus save and reset the flag here
            bool bSav = m_bSVCursorVis;
            m_bSVCursorVis = false;
            MakeSelVisible();
            m_bSVCursorVis = bSav;
        }
 
    } while( eFlags & SwCursorShell::SCROLLWIN );
 
    assert(pFrame);
 
    if( m_pBlockCursor )
        RefreshBlockCursor();
 
    // We should not restrict cursor update to the active view when using LOK
    bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive();
 
    if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor )
    {
        if( m_pTableCursor )
            m_pTableCursor->SwSelPaintRects::Show();
        else
        {
            m_pCurrentCursor->SwSelPaintRects::Show();
            if( m_pBlockCursor )
            {
                SwShellCursor* pNxt = m_pCurrentCursor->GetNext();
                while( pNxt && pNxt != m_pCurrentCursor )
                {
                    pNxt->SwSelPaintRects::Show();
                    pNxt = pNxt->GetNext();
                }
            }
        }
    }
 
    m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
 
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
        Imp()->InvalidateAccessibleCursorPosition( pFrame );
#endif
 
    // switch from blinking cursor to read-only-text-selection cursor
    const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings().
                            GetCursorBlinkTime();
 
    if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) ==
        ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) )
    {
        // non blinking cursor in read only - text selection mode
        AllSettings aSettings = GetOut()->GetSettings();
        StyleSettings aStyleSettings = aSettings.GetStyleSettings();
        const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ?
                                   Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() :
                                   STYLE_CURSOR_NOBLINKTIME;
        aStyleSettings.SetCursorBlinkTime( nNewBlinkTime );
        aSettings.SetStyleSettings( aStyleSettings );
        GetOut()->SetSettings( aSettings );
    }
 
    // Do not notify the cursor if the position didn't change
    Point nNewPos = m_pCurrentCursor->GetPtPos();
    m_bIsCursorPosChanged = nOldPos != nNewPos;
 
    if( m_bSVCursorVis )
        m_pVisibleCursor->Show(); // show again
 
    if (comphelper::LibreOfficeKit::isActive())
        sendLOKCursorUpdates();
 
    getIDocumentMarkAccess()->NotifyCursorUpdate(*this);
    m_bIsCursorPosChanged = false; // reset to default
}
 
void SwCursorShell::sendLOKCursorUpdates()
{
    SwView* pView = static_cast<SwView*>(GetSfxViewShell());
    if (!pView || !pView->GetWrtShellPtr())
        return;
 
    SwWrtShell* pShell = &pView->GetWrtShell();
 
    SwFrame* pCurrentFrame = GetCurrFrame();
    SelectionType eType = pShell->GetSelectionType();
 
    tools::JsonWriter aJsonWriter;
 
    if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab())
    {
        const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr);
 
        {
            auto columnsNode = aJsonWriter.startNode("columns");
            SwTabCols aTabCols;
            pShell->GetTabCols(aTabCols);
 
            const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left();
 
            aJsonWriter.put("left", aTabCols.GetLeft());
            aJsonWriter.put("right", aTabCols.GetRight());
            aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nColumnOffset));
 
            {
                auto entriesNode = aJsonWriter.startArray("entries");
                for (size_t i = 0; i < aTabCols.Count(); ++i)
                {
                    auto entryNode = aJsonWriter.startStruct();
                    auto const & rEntry = aTabCols.GetEntry(i);
                    aJsonWriter.put("position", rEntry.nPos);
                    aJsonWriter.put("min", rEntry.nMin);
                    aJsonWriter.put("max", rEntry.nMax);
                    aJsonWriter.put("hidden", rEntry.bHidden);
                }
            }
        }
 
        {
            auto rowsNode = aJsonWriter.startNode("rows");
            SwTabCols aTabRows;
            pShell->GetTabRows(aTabRows);
 
            const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top();
 
            aJsonWriter.put("left", aTabRows.GetLeft());
            aJsonWriter.put("right", aTabRows.GetRight());
            aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nRowOffset));
 
            {
                auto entriesNode = aJsonWriter.startArray("entries");
                for (size_t i = 0; i < aTabRows.Count(); ++i)
                {
                    auto entryNode = aJsonWriter.startStruct();
                    auto const & rEntry = aTabRows.GetEntry(i);
                    aJsonWriter.put("position", rEntry.nPos);
                    aJsonWriter.put("min", rEntry.nMin);
                    aJsonWriter.put("max", rEntry.nMax);
                    aJsonWriter.put("hidden", rEntry.bHidden);
                }
            }
        }
    }
 
    OString pChar = aJsonWriter.finishAndGetAsOString();
    GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar);
}
 
void SwCursorShell::RefreshBlockCursor()
{
    assert(m_pBlockCursor);
    SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
    Point aPt = rBlock.GetPtPos();
    std::pair<Point, bool> const tmp(aPt, false);
    SwContentFrame* pFrame = rBlock.GetPointContentNode()->getLayoutFrame(
            GetLayout(), rBlock.GetPoint(), &tmp);
    Point aMk;
    if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() )
    {
        aPt = *m_pBlockCursor->getStartPoint();
        aMk = *m_pBlockCursor->getEndPoint();
    }
    else
    {
        aPt = rBlock.GetPtPos();
        if( pFrame )
        {
            if( pFrame->IsVertical() )
                aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX());
            else
                aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX());
        }
        aMk = rBlock.GetMkPos();
    }
    SwRect aRect( aMk, aPt );
    aRect.Justify();
    SwSelectionList aSelList( pFrame );
 
    if( !GetLayout()->FillSelection( aSelList, aRect ) )
        return;
 
    SwCursor* pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
    while( pNxt != m_pCurrentCursor )
    {
        delete pNxt;
        pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
    }
 
    std::list<SwPaM*>::iterator pStart = aSelList.getStart();
    std::list<SwPaM*>::iterator pPam = aSelList.getEnd();
    OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" );
    m_pCurrentCursor->SetMark();
    --pPam;
    // If there is only one text portion inside the rectangle, a simple
    // selection is created
    if( pPam == pStart )
    {
        *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint();
        if( (*pPam)->HasMark() )
            *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
        else
            m_pCurrentCursor->DeleteMark();
        delete *pPam;
        m_pCurrentCursor->SetColumnSelection( false );
    }
    else
    {
        // The order of the SwSelectionList has to be preserved but
        // the order inside the ring created by CreateCursor() is not like
        // expected => First create the selections before the last one
        // downto the first selection.
        // At least create the cursor for the last selection
        --pPam;
        *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections)
        if( (*pPam)->HasMark() )
            *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
        else
            m_pCurrentCursor->DeleteMark();
        delete *pPam;
        m_pCurrentCursor->SetColumnSelection( true );
        while( pPam != pStart )
        {
            --pPam;
 
            SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
            pNew->insert( pNew->begin(), m_pCurrentCursor->begin(),  m_pCurrentCursor->end());
            m_pCurrentCursor->clear();
            m_pCurrentCursor->DeleteMark();
 
            *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1
            if( (*pPam)->HasMark() )
            {
                m_pCurrentCursor->SetMark();
                *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
            }
            else
                m_pCurrentCursor->DeleteMark();
            m_pCurrentCursor->SetColumnSelection( true );
            delete *pPam;
        }
        {
            SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
            pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() );
            m_pCurrentCursor->clear();
            m_pCurrentCursor->DeleteMark();
        }
        pPam = aSelList.getEnd();
        --pPam;
        *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection
        if( (*pPam)->HasMark() )
        {
            m_pCurrentCursor->SetMark();
            *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
        }
        else
            m_pCurrentCursor->DeleteMark();
        m_pCurrentCursor->SetColumnSelection( true );
        delete *pPam;
    }
}
 
/// create a copy of the cursor and save it in the stack
void SwCursorShell::Push()
{
    // fdo#60513: if we have a table cursor, copy that; else copy current.
    // This seems to work because UpdateCursor() will fix this up on Pop(),
    // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring.
    SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor);
    m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(),
                                    pCurrent->GetPtPos(), m_pStackCursor );
 
    if (pCurrent->HasMark())
    {
        m_pStackCursor->SetMark();
        *m_pStackCursor->GetMark() = *pCurrent->GetMark();
    }
}
 
/** delete cursor
 
    @param eDelete  delete from stack, or delete current
                    and assign the one from stack as the new current cursor.
    @return <true> if there was one on the stack, <false> otherwise
*/
bool SwCursorShell::Pop(PopMode const eDelete)
{
    std::optional<SwCallLink> aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed
    return Pop(eDelete, aLink);
}
 
bool SwCursorShell::Pop(PopMode const eDelete,
        [[maybe_unused]] std::optional<SwCallLink>& roLink)
{
    // parameter exists only to be deleted before return
    assert(roLink);
    comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } );
 
    // are there any left?
    if (nullptr == m_pStackCursor)
        return false;
 
    SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor;
 
    // the successor becomes the current one
    if (m_pStackCursor->GetNext() != m_pStackCursor)
    {
        pTmp = m_pStackCursor->GetNext();
    }
 
    if (PopMode::DeleteStack == eDelete)
        delete m_pStackCursor;
 
    m_pStackCursor = pTmp; // assign new one
 
    if (PopMode::DeleteCurrent == eDelete)
    {
        ::std::optional<SwCursorSaveState> oSaveState( *m_pCurrentCursor );
 
        // If the visible SSelection was not changed
        const Point& rPoint = pOldStack->GetPtPos();
        if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos())
        {
            // move "Selections Rectangles"
            m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() );
            pOldStack->clear();
        }
 
        if( pOldStack->HasMark() )
        {
            m_pCurrentCursor->SetMark();
            *m_pCurrentCursor->GetMark() = *pOldStack->GetMark();
            m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos();
        }
        else
            // no selection so revoke old one and set to old position
            m_pCurrentCursor->DeleteMark();
        *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint();
        m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos();
        delete pOldStack;
 
        if( !m_pCurrentCursor->IsInProtectTable( true ) &&
            !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
                                 SwCursorSelOverFlags::ChangePos ) )
        {
            oSaveState.reset(); // prevent UAF
            UpdateCursor(); // update current cursor
            if (m_pTableCursor)
            { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table
                m_pTableCursor->SetChgd();
            }
        }
    }
    return true;
}
 
/** Combine two cursors
 
    Delete topmost from stack and use its GetMark in the current.
*/
void SwCursorShell::Combine()
{
    // any others left?
    if (nullptr == m_pStackCursor)
        return;
 
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    // rhbz#689053: IsSelOvr must restore the saved stack position, not the
    // current one, because current point + stack mark may be invalid PaM
    SwCursorSaveState aSaveState(*m_pStackCursor);
    // stack cursor & current cursor in same Section?
    assert(!m_pStackCursor->HasMark() ||
            CheckNodesRange(m_pStackCursor->GetMark()->GetNode(),
                            m_pCurrentCursor->GetPoint()->GetNode(), true));
    *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint();
    m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos();
 
    SwShellCursor * pTmp = nullptr;
    if (m_pStackCursor->GetNext() != m_pStackCursor)
    {
        pTmp = m_pStackCursor->GetNext();
    }
    delete m_pCurrentCursor;
    m_pCurrentCursor = m_pStackCursor;
    m_pStackCursor->MoveTo(nullptr); // remove from ring
    m_pStackCursor = pTmp;
    if( !m_pCurrentCursor->IsInProtectTable( true ) &&
        !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
                             SwCursorSelOverFlags::ChangePos ) )
    {
        UpdateCursor(); // update current cursor
    }
}
 
void SwCursorShell::HideCursors()
{
    if( !m_bHasFocus || m_bBasicHideCursor )
        return;
 
    // if cursor is visible then hide SV cursor
    if( m_pVisibleCursor->IsVisible() )
    {
        CurrShell aCurr( this );
        m_pVisibleCursor->Hide();
    }
    // revoke inversion of SSelection
    SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
    pCurrentCursor->Hide();
}
 
void SwCursorShell::ShowCursors( bool bCursorVis )
{
    if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor )
        return;
 
    CurrShell aCurr( this );
    SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
    pCurrentCursor->Show(nullptr);
 
    if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again
        m_pVisibleCursor->Show();
}
 
void SwCursorShell::ShowCursor()
{
    if( m_bBasicHideCursor )
        return;
 
    comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
 
    m_bSVCursorVis = true;
    m_pCurrentCursor->SetShowTextInputFieldOverlay( true );
    m_pCurrentCursor->SetShowContentControlOverlay(true);
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        const OString aPayload = OString::boolean(m_bSVCursorVis);
        GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
        SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
    }
 
    UpdateCursor();
}
 
void SwCursorShell::HideCursor()
{
    if( m_bBasicHideCursor )
        return;
 
    m_bSVCursorVis = false;
    // possibly reverse selected areas!!
    CurrShell aCurr( this );
    m_pCurrentCursor->SetShowTextInputFieldOverlay( false );
    m_pCurrentCursor->SetShowContentControlOverlay(false);
    m_pVisibleCursor->Hide();
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        OString aPayload = OString::boolean(m_bSVCursorVis);
        GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
        SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
    }
}
 
void SwCursorShell::ShellLoseFocus()
{
    if( !m_bBasicHideCursor )
        HideCursors();
    m_bHasFocus = false;
}
 
void SwCursorShell::ShellGetFocus()
{
    comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
 
    m_bHasFocus = true;
    if( !m_bBasicHideCursor && VisArea().Width() )
    {
        UpdateCursor( o3tl::narrowing<sal_uInt16>( SwCursorShell::CHKRANGE ) );
        ShowCursors( m_bSVCursorVis );
    }
}
 
/** Get current frame in which the cursor is positioned. */
SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const
{
    CurrShell aCurr( const_cast<SwCursorShell*>(this) );
    SwContentFrame *pRet = nullptr;
    SwContentNode *pNd = m_pCurrentCursor->GetPointContentNode();
    if ( pNd )
    {
        if ( bCalcFrame )
        {
            sal_uInt16* pST = const_cast<sal_uInt16*>(&mnStartAction);
            ++(*pST);
            const Size aOldSz( GetDocSize() );
            std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), true);
            pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
            --(*pST);
            if( aOldSz != GetDocSize() )
                const_cast<SwCursorShell*>(this)->SizeChgNotify();
        }
        else
        {
            std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), false);
            pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
        }
    }
    return pRet;
}
 
//TODO: provide documentation
/** forward all attribute/format changes at the current node to the Link
 
    @param pOld ???
    @param pNew ???
*/
void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::SwPostGraphicArrived && m_aGrfArrivedLnk.IsSet())
    {
        m_aGrfArrivedLnk.Call(*this);
        return;
    }
    if (rHint.GetId() != SfxHintId::SwLegacyModify)
        return;
    auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
    auto nWhich = pLegacy->GetWhich();
    if(!nWhich)
        nWhich = RES_OBJECTDYING;
    if( m_bCallChgLnk &&
        ( !isFormatMessage(nWhich)
                || nWhich == RES_FMT_CHG
                || nWhich == RES_UPDATE_ATTR
                || nWhich == RES_ATTRSET_CHG ))
        // messages are not forwarded
        // #i6681#: RES_UPDATE_ATTR is implicitly unset in
        // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do
        // not need to send the expensive RES_FMT_CHG in Insert.
        CallChgLnk();
    if( nWhich == RES_OBJECTDYING )
    {
        EndListeningAll();
    }
 
}
 
/** Does the current cursor create a selection?
 
    This means checking if GetMark is set and if SPoint and GetMark differ.
*/
bool SwCursorShell::HasSelection() const
{
    const SwPaM* pCursor = getShellCursor( true );
    return IsTableMode()
        || (pCursor->HasMark() &&
                (*pCursor->GetPoint() != *pCursor->GetMark()
                || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End())));
}
 
void SwCursorShell::CallChgLnk()
{
    // Do not make any call in StartAction/EndAction but just set the flag.
    // This will be handled in EndAction.
    if (ActionPend())
        m_bChgCallFlag = true; // remember change
    else if( m_aChgLnk.IsSet() )
    {
        if( m_bCallChgLnk )
            m_aChgLnk.Call(nullptr);
        m_bChgCallFlag = false; // reset flag
    }
}
 
/// get selected text of a node at current cursor
OUString SwCursorShell::GetSelText() const
{
    OUString aText;
    if (GetLayout()->HasMergedParas())
    {
        SwContentFrame const*const pFrame(GetCurrFrame(false));
        if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->GetNodeIndex()))
        {
            OUStringBuffer buf;
            SwPosition const*const pStart(m_pCurrentCursor->Start());
            SwPosition const*const pEnd(m_pCurrentCursor->End());
            for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i)
            {
                SwNode const& rNode(*pStart->GetNodes()[i]);
                assert(!rNode.IsEndNode());
                if (rNode.IsStartNode())
                {
                    i = rNode.EndOfSectionIndex();
                }
                else if (rNode.IsTextNode())
                {
                    sal_Int32 const nStart(i == pStart->GetNodeIndex()
                            ? pStart->GetContentIndex()
                            : 0);
                    sal_Int32 const nEnd(i == pEnd->GetNodeIndex()
                            ? pEnd->GetContentIndex()
                            : rNode.GetTextNode()->Len());
                    buf.append(rNode.GetTextNode()->GetExpandText(
                                GetLayout(),
                                nStart, nEnd - nStart, false, false, false,
                                ExpandMode::HideDeletions));
 
                }
            }
            aText = buf.makeStringAndClear();
        }
    }
    else if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
        m_pCurrentCursor->GetMark()->GetNodeIndex() )
    {
        SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode();
        if( pTextNd )
        {
            const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex();
            aText = pTextNd->GetExpandText(GetLayout(), nStt,
                    m_pCurrentCursor->End()->GetContentIndex() - nStt );
        }
    }
    return aText;
}
 
/** get the nth character of the current SSelection
    in the same paragraph as the start/end.
 
    @param bEnd    Start counting from the end? From start otherwise.
    @param nOffset position of the character
*/
sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset )
{
    if( IsTableMode() ) // not possible in table mode
        return 0;
 
    const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint()
                                : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
    SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
    if( !pTextNd )
        return 0;
 
    SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTextNd->getLayoutFrame(GetLayout())));
    if (!pFrame)
    {
        return 0;
    }
 
    const sal_Int32 nPos(sal_Int32(pFrame->MapModelToViewPos(*pPos)));
    const OUString& rStr(pFrame->GetText());
    sal_Unicode cCh = 0;
 
    if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength())
        cCh = rStr[nPos + nOffset];
 
    return cCh;
}
 
/** extend current SSelection by n characters
 
    @param bEnd   Start counting from the end? From start otherwise.
    @param nCount Number of characters.
*/
bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount )
{
    if( !m_pCurrentCursor->HasMark() || IsTableMode() )
        return false; // no selection
 
    SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
    SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
    assert(pTextNd);
 
    sal_Int32 nPos = pPos->GetContentIndex();
    if( bEnd )
    {
        if ((nPos + nCount) <= pTextNd->GetText().getLength())
            nPos = nPos + nCount;
        else
            return false; // not possible
    }
    else if( nPos >= nCount )
        nPos = nPos - nCount;
    else
        return false; // not possible anymore
 
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
 
    pPos->SetContent(nPos)  ;
    UpdateCursor();
 
    return true;
}
 
/** Move visible cursor to given position in document.
 
    @param rPt The position to move the visible cursor to.
    @return <false> if SPoint was corrected by the layout.
*/
bool SwCursorShell::SetVisibleCursor( const Point &rPt )
{
    CurrShell aCurr( this );
    Point aPt( rPt );
    SwPosition aPos( *m_pCurrentCursor->GetPoint() );
    SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
    aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
    aTmpState.m_bRealHeight = true;
 
    const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ );
 
    SetInFrontOfLabel( false ); // #i27615#
 
    // show only in TextNodes
    SwTextNode* pTextNd = aPos.GetNode().GetTextNode();
    if( !pTextNd )
        return false;
 
    const SwSectionNode* pSectNd = pTextNd->FindSectionNode();
    if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() ||
                    ( !IsReadOnlyAvailable() &&
                      pSectNd->GetSection().IsProtectFlag())) )
        return false;
 
    std::pair<Point, bool> const tmp(aPt, true);
    SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp);
    if ( Imp()->IsIdleAction() )
        pFrame->PrepareCursor();
    SwRect aTmp( m_aCharRect );
 
    pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState );
 
    // #i10137#
    if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() )
        return true;
 
    m_pVisibleCursor->Hide(); // always hide visible cursor
    if( IsScrollMDI( this, m_aCharRect ))
    {
        MakeVisible( m_aCharRect );
        m_pCurrentCursor->Show(nullptr);
    }
 
    {
        if( aTmpState.m_bRealHeight )
            m_aCursorHeight = aTmpState.m_aRealHeight;
        else
        {
            m_aCursorHeight.setX(0);
            m_aCursorHeight.setY(m_aCharRect.Height());
        }
 
        m_pVisibleCursor->SetDragCursor();
        m_pVisibleCursor->Show(); // show again
    }
    return bRet;
}
 
SwVisibleCursor* SwCursorShell::GetVisibleCursor() const
{
    return m_pVisibleCursor;
}
 
bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const
{
    Point aPt( rPt );
    SwPaM aPam( *m_pCurrentCursor->GetPoint() );
    GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt );
    // form view
    return aPam.HasReadonlySel(GetViewOptions()->IsFormView(), false);
}
 
/** Get the number of elements in the ring of cursors
 
    @param bAll If <false> get only spanned ones (= with selections) (Basic).
*/
sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const
{
    SwPaM* pTmp = GetCursor()->GetNext();
    sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() &&
                    *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0;
    while( pTmp != m_pCurrentCursor )
    {
        if( bAll || ( pTmp->HasMark() &&
                *pTmp->GetPoint() != *pTmp->GetMark()))
            ++n;
        pTmp = pTmp->GetNext();
    }
    return n;
}
 
bool SwCursorShell::IsStartOfDoc() const
{
    if( m_pCurrentCursor->GetPoint()->GetContentIndex() )
        return false;
 
    // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd)
    SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 );
    if( !aIdx.GetNode().IsContentNode() )
        SwNodes::GoNext(&aIdx);
    return aIdx == m_pCurrentCursor->GetPoint()->GetNode();
}
 
bool SwCursorShell::IsEndOfDoc() const
{
    SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 );
    SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
    if( !pCNd )
        pCNd = SwNodes::GoPrevious( &aIdx );
 
    return aIdx == m_pCurrentCursor->GetPoint()->GetNode() && pCNd &&
            pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex();
}
 
/** Invalidate cursors
 
    Delete all created cursors, set table crsr and last crsr to their TextNode
    (or StartNode?). They will then all re-created at the next ::GetCursor() call.
 
    This is needed for Drag&Drop/ Clipboard-paste in tables.
*/
bool SwCursorShell::ParkTableCursor()
{
    if( !m_pTableCursor )
        return false;
 
    m_pTableCursor->ParkCursor();
 
    while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
        delete m_pCurrentCursor->GetNext();
 
    // *always* move cursor's Point and Mark
    m_pCurrentCursor->DeleteMark();
    *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
 
    return true;
}
 
void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing )
{
    auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition*
 
    SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing;
 
    // search over the whole ring
    bool bGoNext;
    do {
 
        if (!pTmp)
            break;
 
        auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
        // If a SPoint or GetMark are in a cursor area then cancel the old area.
        // During comparison keep in mind that End() is outside the area.
        if( *pStt <= *pTmpStt )
        {
            if( *pEnd > *pTmpStt ||
                ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
                pTmpDel = pTmp;
        }
        else
            if( *pStt < *pTmpEnd )
                pTmpDel = pTmp;
 
        bGoNext = true;
        if (pTmpDel) // is the pam in the range -> delete
        {
            bool bDelete = true;
            if( *ppDelRing == pTmpDel )
            {
                if( *ppDelRing == m_pCurrentCursor )
                {
                    bDelete = GoNextCursor();
                    if( bDelete )
                    {
                        bGoNext = false;
                        pTmp = pTmp->GetNext();
                    }
                }
                else
                    bDelete = false; // never delete the StackCursor
            }
 
            if( bDelete )
            {
                if (pTmp == pTmpDel)
                    pTmp = nullptr;
                delete pTmpDel; // invalidate old area
            }
            else
            {
                pTmpDel->GetPoint()->Assign(SwNodeOffset(0));
                pTmpDel->DeleteMark();
            }
            pTmpDel = nullptr;
        }
        if( bGoNext && pTmp )
            pTmp = pTmp->GetNext();
 
    } while( !bGoNext || *ppDelRing != pTmp );
}
 
//TODO: provide documentation
/** Remove selections and additional cursors of all shells.
 
    The remaining cursor of the shell is parked.
 
    @param rIdx ???
*/
void SwCursorShell::ParkCursor( const SwNode &rIdx )
{
    const SwNode *pNode = &rIdx;
 
    // create a new PaM
    SwPaM aNew( *GetCursor()->GetPoint() );
    if( pNode->GetStartNode() )
    {
        pNode = pNode->StartOfSectionNode();
        if( pNode->IsTableNode() )
        {
            // the given node is in a table, thus park cursor to table node
            // (outside of the table)
            aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
        }
        else
            // Also on the start node itself. Then we need to request the start
            // node always via its end node! (StartOfSelection of StartNode is
            // the parent)
            aNew.GetPoint()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() );
    }
    else
        aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
    aNew.SetMark();
    aNew.GetPoint()->Assign(*pNode->EndOfSectionNode());
 
    // take care of all shells
    for(SwViewShell& rTmp : GetRingContainer())
    {
        if( auto pSh = dynamic_cast<SwCursorShell *>(&rTmp))
        {
            if (pSh->m_pStackCursor)
                pSh->ParkPams(&aNew, &pSh->m_pStackCursor);
 
            pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor );
            if( pSh->m_pTableCursor )
            {
                // set table cursor always to 0 and the current one always to
                // the beginning of the table
                SwPaM* pTCursor = pSh->GetTableCrs();
                SwNode* pTableNd = pTCursor->GetPoint()->GetNode().FindTableNode();
                if ( pTableNd )
                {
                    pTCursor->GetPoint()->Assign(SwNodeOffset(0));
                    pTCursor->DeleteMark();
                    pSh->m_pCurrentCursor->GetPoint()->Assign( *pTableNd );
                }
            }
        }
    }
}
 
/** Copy constructor
 
    Copy cursor position and add it to the ring.
    All views of a document are in the ring of the shell.
*/
SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin )
    : SwViewShell( rShell, pInitWin )
    , sw::BroadcastingModify()
    , m_pStackCursor( nullptr )
    , m_pBlockCursor( nullptr )
    , m_pTableCursor( nullptr )
    , m_pBoxIdx( nullptr )
    , m_pBoxPtr( nullptr )
    , m_nUpDownX(0)
    , m_nLeftFramePos(0)
    , m_nCurrentNode(0)
    , m_nCurrentContent(0)
    , m_nCurrentNdTyp(SwNodeType::NONE)
    , m_nCursorMove( 0 )
    , m_eMvState( CursorMoveState::NONE )
    , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
    , m_nMarkedListLevel( 0 )
    , m_oldColFrame(nullptr)
    , m_aLayoutIdle("SwCursorShell m_aLayoutIdle")
{
    CurrShell aCurr( this );
    // only keep the position of the current cursor of the copy shell
    m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) );
    m_pCurrentCursor->GetPointContentNode()->Add(*this);
 
    m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
    m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
    m_bOverwriteCursor = false;
    m_bIsCursorPosChanged = false;
    m_bSendAccessibleCursorEvents = true;
    m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
    m_bSVCursorVis = true;
    m_bSetCursorInReadOnly = true;
    m_pVisibleCursor = new SwVisibleCursor( this );
    m_bMacroExecAllowed = rShell.IsMacroExecAllowed();
 
    m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
    m_aLayoutIdle.SetInvokeHandler(LINK(this, SwCursorShell, DoLayoutIdle));
}
 
/// default constructor
SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin,
                            const SwViewOption *pInitOpt )
    : SwViewShell( rDoc, pInitWin, pInitOpt )
    , sw::BroadcastingModify()
    , m_pStackCursor( nullptr )
    , m_pBlockCursor( nullptr )
    , m_pTableCursor( nullptr )
    , m_pBoxIdx( nullptr )
    , m_pBoxPtr( nullptr )
    , m_nUpDownX(0)
    , m_nLeftFramePos(0)
    , m_nCurrentNode(0)
    , m_nCurrentContent(0)
    , m_nCurrentNdTyp(SwNodeType::NONE)
    , m_nCursorMove( 0 )
    , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint
    , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
    , m_nMarkedListLevel( 0 )
    , m_oldColFrame(nullptr)
    , m_aLayoutIdle("SwCursorShell m_aLayoutIdle")
{
    CurrShell aCurr( this );
    // create initial cursor and set it to first content position
    SwNodes& rNds = rDoc.GetNodes();
 
    SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() );
    SwContentNode* pCNd = SwNodes::GoNext(&aNodeIdx); // go to the first ContentNode
 
    m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, pCNd, 0 ) );
 
    // Register shell as dependent at current node. As a result all attribute
    // changes can be forwarded via the Link.
    pCNd->Add(*this);
 
    m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
    m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
    m_bOverwriteCursor = false;
    m_bIsCursorPosChanged = false;
    m_bSendAccessibleCursorEvents = true;
    m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
    m_bSVCursorVis = true;
    m_bSetCursorInReadOnly = true;
 
    m_pVisibleCursor = new SwVisibleCursor( this );
    m_bMacroExecAllowed = true;
 
    m_aLayoutIdle.SetPriority(TaskPriority::LOWEST);
    m_aLayoutIdle.SetInvokeHandler(LINK(this, SwCursorShell, DoLayoutIdle));
}
 
SwCursorShell::~SwCursorShell()
{
    m_aLayoutIdle.Stop();
 
    // if it is not the last view then at least the field should be updated
    if( !unique() )
        CheckTableBoxContent( m_pCurrentCursor->GetPoint() );
    else
        ClearTableBoxContent();
 
    delete m_pVisibleCursor;
    delete m_pBlockCursor;
    delete m_pTableCursor;
 
    // release cursors
    while(m_pCurrentCursor->GetNext() != m_pCurrentCursor)
        delete m_pCurrentCursor->GetNext();
    delete m_pCurrentCursor;
 
    // free stack
    if (m_pStackCursor)
    {
        while (m_pStackCursor->GetNext() != m_pStackCursor)
            delete m_pStackCursor->GetNext();
        delete m_pStackCursor;
    }
 
    // #i54025# - do not give a HTML parser that might potentially hang as
    // a client at the cursor shell the chance to hang itself on a TextNode
    EndListeningAll();
}
 
IMPL_LINK_NOARG(SwCursorShell, DoLayoutIdle, Timer*, void) { LayoutIdle(); }
 
SwShellCursor* SwCursorShell::getShellCursor( bool bBlock )
{
    if( m_pTableCursor )
        return m_pTableCursor;
    if( m_pBlockCursor && bBlock )
        return &m_pBlockCursor->getShellCursor();
    return m_pCurrentCursor;
}
 
/** Should WaitPtr be switched on for the clipboard?
 
    Wait for TableMode, multiple selections and more than x selected paragraphs.
*/
bool SwCursorShell::ShouldWait() const
{
    if ( IsTableMode() || GetCursorCnt() > 1 )
        return true;
 
    if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() )
        return true;
 
    SwPaM* pPam = GetCursor();
    return pPam->Start()->GetNodeIndex() + SwNodeOffset(10) <
            pPam->End()->GetNodeIndex();
}
 
size_t SwCursorShell::UpdateTableSelBoxes()
{
    if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount()))
    {
         GetLayout()->MakeTableCursors( *m_pTableCursor );
    }
    return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0;
}
 
/// show the current selected "object"
void SwCursorShell::MakeSelVisible()
{
    OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" );
    if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() )
    {
        SwRect aTmp( m_aCharRect );
        tools::Long nDiff = m_aCharRect.Height() - VisArea().Height();
        if( nDiff < m_aCursorHeight.getX() )
            aTmp.Top( nDiff + m_aCharRect.Top() );
        else
        {
            aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() );
            aTmp.Height( m_aCursorHeight.getY() );
        }
        if( !aTmp.HasArea() )
        {
            aTmp.AddHeight(1 );
            aTmp.AddWidth(1 );
        }
        MakeVisible( aTmp );
    }
    else
    {
        if( m_aCharRect.HasArea() )
            MakeVisible( m_aCharRect );
        else
        {
            SwRect aTmp( m_aCharRect );
            aTmp.AddHeight(1 );
            aTmp.AddWidth(1 );
            MakeVisible( aTmp );
        }
    }
}
 
/// search a valid content position (not protected/hidden)
bool SwCursorShell::FindValidContentNode( bool bOnlyText )
{
    if( m_pTableCursor )
    {
        assert(!"Did not remove table selection!");
        return false;
    }
 
    // #i45129# - everything is allowed in UI-readonly
    if( !m_bAllProtect && GetDoc()->GetDocShell() &&
        GetDoc()->GetDocShell()->IsReadOnlyUI() )
        return true;
 
    if( m_pCurrentCursor->HasMark() && !mbSelectAll )
        ClearMark();
 
    // first check for frames
    SwPosition& rNdPos = *m_pCurrentCursor->GetPoint();
    SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup
    SwNodes& rNds = mxDoc->GetNodes();
    SwContentNode* pCNd = rNdPos.GetNode().GetContentNode();
    const SwContentFrame * pFrame;
 
    if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) &&
        !IsReadOnlyAvailable() && pFrame->IsProtected() &&
        nNdIdx < rNds.GetEndOfExtras().GetIndex() )
    {
        // skip protected frame
        SwPaM aPam( *m_pCurrentCursor->GetPoint() );
        aPam.SetMark();
        aPam.GetMark()->Assign( rNds.GetEndOfContent() );
        aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() );
 
        bool bFirst = false;
        if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward )))
        {
            aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() );
            pCNd = ::GetNode( aPam, bFirst, fnMoveBackward );
        }
 
        if( !pCNd ) // should *never* happen
        {
            rNdPos.Assign(nNdIdx); // back to old node
            return false;
        }
        *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
    }
    else if( bOnlyText && pCNd && pCNd->IsNoTextNode() )
    {
        // set to beginning of document
        rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() );
        SwNodes::GoNext(&rNdPos);
        nNdIdx = rNdPos.GetNodeIndex();
    }
 
    bool bOk = true;
 
    // #i9059# cursor may not stand in protected cells
    //         (unless cursor in protected areas is OK.)
    const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode();
    if( !IsReadOnlyAvailable()  &&
        pTableNode != nullptr  &&  rNdPos.GetNode().IsProtect() )
    {
        // we're in a table, and we're in a protected area, so we're
        // probably in a protected cell.
 
        // move forward into non-protected area.
        SwPaM aPam( rNdPos.GetNode(), 0 );
        while( aPam.GetPointNode().IsProtect() &&
               aPam.Move( fnMoveForward, GoInContent ) )
            ; // nothing to do in the loop; the aPam.Move does the moving!
 
        // didn't work? then go backwards!
        if( aPam.GetPointNode().IsProtect() )
        {
            SwPaM aTmpPaM( rNdPos.GetNode(), 0 );
            aPam = aTmpPaM;
            while( aPam.GetPointNode().IsProtect() &&
                   aPam.Move( fnMoveBackward, GoInContent ) )
                ; // nothing to do in the loop; the aPam.Move does the moving!
        }
 
        // if we're successful, set the new position
        if( ! aPam.GetPointNode().IsProtect() )
        {
            *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
        }
    }
 
    // in a protected frame
    const SwSectionNode* pSectNd = rNdPos.GetNode().FindSectionNode();
    if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
        ( !IsReadOnlyAvailable() &&
           pSectNd->GetSection().IsProtectFlag() )) )
    {
        bOk = false;
        bool bGoNextSection = true;
        for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt )
        {
            bool bContinue;
            do {
                bContinue = false;
                for (;;)
                {
                    if (bGoNextSection)
                        pCNd = SwNodes::GoNextSection( &rNdPos,
                                            true, !IsReadOnlyAvailable() );
                    else
                        pCNd = SwNodes::GoPrevSection( &rNdPos,
                                            true, !IsReadOnlyAvailable() );
                    if ( pCNd == nullptr) break;
                    // moved inside a table -> check if it is protected
                    if( pCNd->FindTableNode() )
                    {
                        SwCallLink aTmp( *this );
                        SwCursorSaveState aSaveState( *m_pCurrentCursor );
                        aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR
                        if( !m_pCurrentCursor->IsInProtectTable( true ) )
                        {
                            const SwSectionNode* pSNd = pCNd->FindSectionNode();
                            if( !pSNd || !pSNd->GetSection().IsHiddenFlag()
                                || (!IsReadOnlyAvailable()  &&
                                    pSNd->GetSection().IsProtectFlag() ))
                            {
                                bOk = true;
                                break; // found non-protected cell
                            }
                            continue; // continue search
                        }
                    }
                    else
                    {
                        bOk = true;
                        break; // found non-protected cell
                    }
                }
 
                if( bOk && rNdPos.GetNodeIndex() < rNds.GetEndOfExtras().GetIndex() )
                {
                    // also check for Fly - might be protected as well
                    pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr);
                    if (nullptr == pFrame ||
                        ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) ||
                        ( bOnlyText && pCNd->IsNoTextNode() ) )
                    {
                        // continue search
                        bOk = false;
                        bContinue = true;
                    }
                }
            } while( bContinue );
 
            if( !bOk )
            {
                if( !nLoopCnt )
                    bGoNextSection = false;
                rNdPos.Assign( nNdIdx );
            }
        }
    }
    if( bOk )
    {
        pCNd = rNdPos.GetNode().GetContentNode();
        const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0;
        m_pCurrentCursor->GetPoint()->SetContent( nContent );
    }
    else
    {
        pCNd = rNdPos.GetNode().GetContentNode();
        // if cursor in hidden frame, always move it
        if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr))
        {
            SwCursorMoveState aTmpState( CursorMoveState::NONE );
            aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
            GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(),
                                        &aTmpState );
        }
    }
    return bOk;
}
 
bool SwCursorShell::IsCursorReadonly() const
{
    if ( GetViewOptions()->IsReadonly() ||
         GetViewOptions()->IsFormView() /* Formula view */ )
    {
        SwFrame *pFrame = GetCurrFrame( false );
        const SwFlyFrame* pFly;
        const SwSection* pSection;
 
        if( pFrame && pFrame->IsInFly() &&
            (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
            pFly->Lower() &&
            !pFly->Lower()->IsNoTextFrame() &&
            !GetDrawView()->GetMarkedObjectList().GetMarkCount() )
        {
            return false;
        }
        // edit in readonly sections
        else if ( pFrame && pFrame->IsInSct() &&
            nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) &&
            pSection->IsEditInReadonlyFlag() )
        {
            return false;
        }
        else if ( !IsMultiSelection() && CursorInsideInputField() )
        {
            return false;
        }
 
        return true;
    }
    return false;
}
 
/// is the cursor allowed to enter ReadOnly sections?
void SwCursorShell::SetReadOnlyAvailable( bool bFlag )
{
    // *never* switch in GlobalDoc
    if( (!GetDoc()->GetDocShell() ||
         dynamic_cast<const SwGlobalDocShell*>(GetDoc()->GetDocShell()) == nullptr ) &&
        bFlag != m_bSetCursorInReadOnly )
    {
        // If the flag is switched off then all selections need to be
        // invalidated. Otherwise we would trust that nothing protected is selected.
        if( !bFlag )
        {
            ClearMark();
        }
        m_bSetCursorInReadOnly = bFlag;
        UpdateCursor();
    }
}
 
bool SwCursorShell::HasReadonlySel(bool const isReplace) const
{
    // Treat selections that span over start or end of paragraph of an outline node
    // with folded outline content as read-only.
    if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
    {
        SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
        if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
            return true;
    }
    bool bRet = false;
    // If protected area is to be ignored, then selections are never read-only.
    if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() ||
        GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) &&
        !SwViewOption::IsIgnoreProtectedArea())
    {
        if ( m_pTableCursor != nullptr )
        {
            // TODO: handling when a table cell (cells) is selected
            bRet = m_pTableCursor->HasReadOnlyBoxSel()
                   || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace);
        }
        else
        {
            for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
            {
                if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace))
                {
                    bRet = true;
                    break;
                }
            }
        }
    }
    return bRet;
}
 
bool SwCursorShell::HasHiddenSections() const
{
    // Treat selections that span over start or end of paragraph of an outline node
    // with folded outline content as read-only.
    if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
    {
        SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
        if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
            return true;
    }
    bool bRet = false;
 
    if ( m_pTableCursor != nullptr )
    {
        bRet = m_pTableCursor->HasHiddenBoxSel()
               || m_pTableCursor->HasHiddenSections();
    }
    else
    {
        for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
        {
            if (rCursor.HasHiddenSections())
            {
                bRet = true;
                break;
            }
        }
    }
 
    return bRet;
}
 
bool SwCursorShell::IsSelFullPara() const
{
    bool bRet = false;
 
    if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
        m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() )
    {
        sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex();
        sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex();
        if( nStt > nEnd )
            std::swap( nStt, nEnd );
        const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode();
        bRet = pCNd && !nStt && nEnd == pCNd->Len();
    }
    return bRet;
}
 
SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const
{
    SwPosition aPos( *m_pCurrentCursor->GetPoint() );
    Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() );
    if( pPt )
    {
        SwCursorMoveState aTmpState( CursorMoveState::NONE );
        aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
 
        GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState );
    }
 
    return mxDoc->GetTextDirection( aPos, &aPt );
}
 
bool SwCursorShell::IsInVerticalText( const Point* pPt ) const
{
    const SvxFrameDirection nDir = GetTextDirection( pPt );
    return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir
           || nDir == SvxFrameDirection::Vertical_LR_BT;
}
 
bool SwCursorShell::IsInRightToLeftText() const
{
    const SvxFrameDirection nDir = GetTextDirection();
    // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in
    // vertical environment
    return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir;
}
 
/// If the current cursor position is inside a hidden range true is returned. If bSelect is
/// true, the hidden range is selected. If bSelect is false, the hidden range is not selected.
bool SwCursorShell::IsInHiddenRange(const bool bSelect)
{
    bool bRet = false;
    if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() )
    {
        SwPosition& rPt = *m_pCurrentCursor->GetPoint();
        const SwTextNode* pNode = rPt.GetNode().GetTextNode();
        if ( pNode )
        {
            const sal_Int32 nPos = rPt.GetContentIndex();
 
            // check if nPos is in hidden range
            sal_Int32 nHiddenStart;
            sal_Int32 nHiddenEnd;
            SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd );
            if ( COMPLETE_STRING != nHiddenStart )
            {
                if (bSelect)
                {
                    // make selection:
                    m_pCurrentCursor->SetMark();
                    m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd);
                }
                bRet = true;
            }
        }
    }
 
    return bRet;
}
 
sal_Int32 SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt,
                             bool bSearchInNotes,
                             SwDocPositions eStart, SwDocPositions eEnd,
                             bool& bCancel,
                             FindRanges eRng,
                             bool bReplace )
{
    if( m_pTableCursor )
        GetCursor();
    delete m_pTableCursor;
    m_pTableCursor = nullptr;
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    sal_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd,
                                     bCancel, eRng, bReplace, GetLayout());
    if( nRet || bCancel )
        UpdateCursor();
    return nRet;
}
 
sal_Int32 SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl,
                             SwDocPositions eStart, SwDocPositions eEnd,
                             bool& bCancel,
                             FindRanges eRng,
                             const SwTextFormatColl* pReplFormat )
{
    if( m_pTableCursor )
        GetCursor();
    delete m_pTableCursor;
    m_pTableCursor = nullptr;
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    sal_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng,
                                     pReplFormat );
    if( nRet )
        UpdateCursor();
    return nRet;
}
 
sal_Int32 SwCursorShell::FindAttrs( const SfxItemSet& rSet,
                             bool bNoCollections,
                             SwDocPositions eStart, SwDocPositions eEnd,
                             bool& bCancel,
                             FindRanges eRng,
                             const i18nutil::SearchOptions2* pSearchOpt,
                             const SfxItemSet* rReplSet )
{
    if( m_pTableCursor )
        GetCursor();
    delete m_pTableCursor;
    m_pTableCursor = nullptr;
    SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
    sal_Int32 nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd,
                         bCancel, eRng, pSearchOpt, rReplSet, GetLayout());
    if( nRet )
        UpdateCursor();
    return nRet;
}
 
void SwCursorShell::SetSelection( const SwPaM& rCursor )
{
    StartAction();
    SwCursor* pCursor = GetCursor();
    *pCursor->GetPoint() = *rCursor.GetPoint();
    if(rCursor.GetNext() != &rCursor)
    {
        const SwPaM *_pStartCursor = rCursor.GetNext();
        do
        {
            SwPaM* pCurrentCursor = CreateCursor();
            *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint();
            if(_pStartCursor->HasMark())
            {
                pCurrentCursor->SetMark();
                *pCurrentCursor->GetMark() = *_pStartCursor->GetMark();
            }
        } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor );
    }
    // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current
    // cursor; therefore set current cursor's mark only after creating all other cursors
    if (rCursor.HasMark())
    {
        pCursor->SetMark();
        *pCursor->GetMark() = *rCursor.GetMark();
    }
    EndAction();
}
 
static const SwStartNode* lcl_NodeContext( const SwNode& rNode )
{
    const SwStartNode *pRet = rNode.StartOfSectionNode();
    while( pRet->IsSectionNode() || pRet->IsTableNode() ||
        pRet->GetStartNodeType() == SwTableBoxStartNode )
    {
        pRet = pRet->StartOfSectionNode();
    }
    return pRet;
}
 
/**
   Checks if a position is valid. To be valid the position's node must
   be a content node and the content must not be unregistered.
 
   @param aPos the position to check.
*/
static bool sw_PosOk(const SwPosition & aPos)
{
    return nullptr != aPos.GetNode().GetContentNode() &&
           aPos.GetContentNode();
}
 
/**
   Checks if a PaM is valid. For a PaM to be valid its point must be
   valid. Additionally if the PaM has a mark this has to be valid, too.
 
   @param aPam the PaM to check
*/
static bool lcl_CursorOk(SwPaM & aPam)
{
    return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark()
        || sw_PosOk(*aPam.GetMark()));
}
 
void SwCursorShell::ClearUpCursors()
{
    // start of the ring
    SwPaM * pStartCursor = GetCursor();
    // start loop with second entry of the ring
    SwPaM * pCursor = pStartCursor->GetNext();
    SwPaM * pTmpCursor;
    bool bChanged = false;
 
    // For all entries in the ring except the start entry delete the entry if
    // it is invalid.
    while (pCursor != pStartCursor)
    {
        pTmpCursor = pCursor->GetNext();
        if ( ! lcl_CursorOk(*pCursor))
        {
            delete pCursor;
            bChanged = true;
        }
        pCursor = pTmpCursor;
    }
 
    if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) )
    {
        pStartCursor->DeleteMark();
        bChanged = true;
    }
    if (pStartCursor->GetPoint()->GetNode().IsTableNode())
    {
        // tdf#106959: When cursor points to start of a table, the proper content
        // node is the first one inside the table, not the previous one
        SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode());
        if (SwNode* pNode = SwNodes::GoNext(&aIdx))
        {
            SwPaM aTmpPam(*pNode);
            *pStartCursor = aTmpPam;
            bChanged = true;
        }
    }
    if( !sw_PosOk( *pStartCursor->GetPoint() ) )
    {
        SwNodes & aNodes = GetDoc()->GetNodes();
        const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->GetNode() );
        SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() );
        SwNode * pNode = SwNodes::GoPrevious(&aIdx);
        if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
        {
            pNode = SwNodes::GoNext(&aIdx);
            if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
            {
                // If the start entry of the ring is invalid replace it with a
                // cursor pointing to the beginning of the first content node in the
                // document.
                aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode());
                pNode = SwNodes::GoNext(&aIdx);
            }
        }
        bool bFound = (pNode != nullptr);
 
        assert(bFound);
 
        if (bFound)
        {
            SwPaM aTmpPam(*pNode);
            *pStartCursor = aTmpPam;
        }
 
        bChanged = true;
    }
 
    // If at least one of the cursors in the ring have been deleted or replaced,
    // remove the table cursor.
    if (m_pTableCursor != nullptr && bChanged)
        TableCursorToCursor();
}
 
OUString SwCursorShell::GetCursorDescr() const
{
    OUString aResult;
 
    if (IsMultiSelection())
        aResult += SwResId(STR_MULTISEL);
    else
        aResult = SwDoc::GetPaMDescr(*GetCursor());
 
    return aResult;
}
 
void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell"));
 
    SwViewShell::dumpAsXml(pWriter);
 
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor"));
    for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer())
        rPaM.dumpAsXml(pWriter);
    (void)xmlTextWriterEndElement(pWriter);
 
    (void)xmlTextWriterEndElement(pWriter);
}
 
static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes,
                             uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
                             const SwWrongList& rSmartTagList, sal_Int32 nCurrent )
{
    // Insert smart tag information
    std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps;
 
    for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i )
    {
        const sal_Int32 nSTPos = rSmartTagList.Pos( i );
        const sal_Int32 nSTLen = rSmartTagList.Len( i );
 
        if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen )
        {
            const SwWrongArea* pArea = rSmartTagList.GetElement( i );
            if ( pArea )
            {
                rSmartTagTypes.push_back( pArea->maType );
                aStringKeyMaps.push_back( pArea->mxPropertyBag );
            }
        }
    }
 
    if ( !rSmartTagTypes.empty() )
    {
        rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps);
    }
}
 
static void lcl_FillTextRange( uno::Reference<text::XTextRange>& rRange,
                   SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen )
{
    // create SwPosition for nStartIndex
    SwPosition aStartPos( rNode, nBegin );
    // create SwPosition for nEndIndex
    SwPosition aEndPos( rNode, nBegin + nLen );
 
    const rtl::Reference<SwXTextRange> xRange =
        SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos);
 
    rRange = xRange;
}
 
void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes,
                                   uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
                                   uno::Reference< text::XTextRange>& rRange ) const
{
    if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() )
        return;
 
    SwPaM* pCursor = GetCursor();
    SwPosition aPos(*pCursor->Start());
    SwTextNode *pNode = aPos.GetNode().GetTextNode();
    if ( !pNode || pNode->IsInProtectSect() )
        return;
 
    const SwWrongList *pSmartTagList = pNode->GetSmartTags();
    if ( !pSmartTagList )
        return;
 
    sal_Int32 nCurrent = aPos.GetContentIndex();
    sal_Int32 nBegin = nCurrent;
    sal_Int32 nLen = 1;
 
    if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
        return;
 
    const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin );
    const SwWrongList* pSubList = pSmartTagList->SubList( nIndex );
    if ( pSubList )
    {
        pSmartTagList = pSubList;
        nCurrent = 0;
    }
 
    lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent );
    lcl_FillTextRange( rRange, *pNode, nBegin, nLen );
}
 
// see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect )
{
    SwPaM* pCursor = GetCursor();
    SwPosition aPos( *pCursor->GetPoint() );
    Point aPt( rPt );
    SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
    eTmpState.m_bPosMatchesBounds = true; // treat last half of character same as first half
    SwSpecialPos aSpecialPos;
    eTmpState.m_pSpecialPos = &aSpecialPos;
    SwTextNode *pNode;
    const SwWrongList *pSmartTagList;
 
    if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) )
        return;
    pNode = aPos.GetNode().GetTextNode();
    if( !pNode )
        return;
    pSmartTagList = pNode->GetSmartTags();
    if( !pSmartTagList )
        return;
    if( pNode->IsInProtectSect() )
        return;
 
    sal_Int32 nBegin = aPos.GetContentIndex();
    sal_Int32 nLen = 1;
 
    if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
        return;
 
    // get smarttag word
    OUString aText( pNode->GetText().copy(nBegin, nLen) );
 
    //save the start and end positions of the line and the starting point
    Push();
    LeftMargin();
    const sal_Int32 nLineStart = GetCursor()->GetPoint()->GetContentIndex();
    RightMargin();
    const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex();
    Pop(PopMode::DeleteCurrent);
 
    // make sure the selection build later from the data below does not
    // include "in word" character to the left and right in order to
    // preserve those. Therefore count those "in words" in order to
    // modify the selection accordingly.
    const sal_Unicode* pChar = aText.getStr();
    sal_Int32 nLeft = 0;
    while (*pChar++ == CH_TXTATR_INWORD)
        ++nLeft;
    pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
    sal_Int32 nRight = 0;
    while (pChar && *pChar-- == CH_TXTATR_INWORD)
        ++nRight;
 
    aPos.SetContent( nBegin + nLeft );
    pCursor = GetCursor();
    *pCursor->GetPoint() = std::move(aPos);
    pCursor->SetMark();
    ExtendSelection( true, nLen - nLeft - nRight );
    // do not determine the rectangle in the current line
    const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
    // take one less than the line end - otherwise the next line would
    // be calculated
    const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd);
    Push();
    pCursor->DeleteMark();
    SwPosition& rPos = *GetCursor()->GetPoint();
    rPos.SetContent( nWordStart );
    SwRect aStartRect;
    SwCursorMoveState aState;
    aState.m_bRealWidth = true;
    SwContentNode* pContentNode = pCursor->GetPointContentNode();
    std::pair<Point, bool> const tmp(rPt, false);
    SwContentFrame *pContentFrame = pContentNode->getLayoutFrame(
            GetLayout(), pCursor->GetPoint(), &tmp);
 
    pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
    rPos.SetContent( nWordEnd - 1 );
    SwRect aEndRect;
    pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
    rSelectRect = aStartRect.Union( aEndRect );
    Pop(PopMode::DeleteCurrent);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V773 Visibility scope of the 'pNew' pointer was exited without releasing the memory. A memory leak is possible.

V773 Visibility scope of the 'pNew' pointer was exited without releasing the memory. A memory leak is possible.

V773 Visibility scope of the 'pCurrentCursor' pointer was exited without releasing the memory. A memory leak is possible.

V547 Expression '!"Did not remove table selection!"' is always false.

V773 The return value of function 'CreateCursor' is required to be utilized. A memory leak is possible.

V1051 Consider checking for misprints. It's possible that the 'pTstCursor' should be checked here.

V1051 Consider checking for misprints. It's possible that the 'pTmpCursor' should be used inside 'lcl_CursorOk' function.