/* -*- 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 <tools/gen.hxx>
#include <hintids.hxx>
#include <editeng/protitem.hxx>
#include <cntfrm.hxx>
#include <pagefrm.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <docary.hxx>
#include <pam.hxx>
#include <pamtyp.hxx>
#include <txtfrm.hxx>
#include <fmtcntnt.hxx>
#include <frmatr.hxx>
#include <swtable.hxx>
#include <flyfrm.hxx>
#include <fmteiro.hxx>
#include <section.hxx>
#include <sectfrm.hxx>
#include <ndtxt.hxx>
#include <swcrsr.hxx>
 
#include <IMark.hxx>
#include <DocumentSettingManager.hxx>
#include <hints.hxx>
#include <xmloff/odffields.hxx>
 
#include <editsh.hxx>
 
// for the dump "MSC-" compiler
inline sal_Int32 GetSttOrEnd( bool bCondition, const SwContentNode& rNd )
{
    return bCondition ? 0 : rNd.Len();
}
 
SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, const SwIndex & rContent )
    : nNode( rNodeIndex ), nContent( rContent )
{
}
 
SwPosition::SwPosition( const SwNodeIndex & rNodeIndex )
    : nNode( rNodeIndex ), nContent( nNode.GetNode().GetContentNode() )
{
}
 
SwPosition::SwPosition( const SwNode& rNode )
    : nNode( rNode ), nContent( nNode.GetNode().GetContentNode() )
{
}
 
SwPosition::SwPosition( SwContentNode & rNode, const sal_Int32 nOffset )
    : nNode( rNode ), nContent( &rNode, nOffset )
{
}
 
bool SwPosition::operator<(const SwPosition &rPos) const
{
    if( nNode < rPos.nNode )
        return true;
    if( nNode == rPos.nNode )
    {
        // note that positions with text node but no SwIndex registered are
        // created for text frames anchored at para (see SwXFrame::getAnchor())
        SwIndexReg const*const pThisReg(nContent.GetIdxReg());
        SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg());
        if (pThisReg && pOtherReg)
        {
            return (nContent < rPos.nContent);
        }
        else // by convention position with no index is smaller
        {
            return pOtherReg != nullptr;
        }
    }
    return false;
}
 
bool SwPosition::operator>(const SwPosition &rPos) const
{
    if(nNode > rPos.nNode )
        return true;
    if( nNode == rPos.nNode )
    {
        // note that positions with text node but no SwIndex registered are
        // created for text frames anchored at para (see SwXFrame::getAnchor())
        SwIndexReg const*const pThisReg(nContent.GetIdxReg());
        SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg());
        if (pThisReg && pOtherReg)
        {
            return (nContent > rPos.nContent);
        }
        else // by convention position with no index is smaller
        {
            return pThisReg != nullptr;
        }
    }
    return false;
}
 
bool SwPosition::operator<=(const SwPosition &rPos) const
{
    if(nNode < rPos.nNode )
        return true;
    if( nNode == rPos.nNode )
    {
        // note that positions with text node but no SwIndex registered are
        // created for text frames anchored at para (see SwXFrame::getAnchor())
        SwIndexReg const*const pThisReg(nContent.GetIdxReg());
        SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg());
        if (pThisReg && pOtherReg)
        {
            return (nContent <= rPos.nContent);
        }
        else // by convention position with no index is smaller
        {
            return pThisReg == nullptr;
        }
    }
    return false;
}
 
bool SwPosition::operator>=(const SwPosition &rPos) const
{
    if(nNode > rPos.nNode )
        return true;
    if( nNode == rPos.nNode )
    {
        // note that positions with text node but no SwIndex registered are
        // created for text frames anchored at para (see SwXFrame::getAnchor())
        SwIndexReg const*const pThisReg(nContent.GetIdxReg());
        SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg());
        if (pThisReg && pOtherReg)
        {
            return (nContent >= rPos.nContent);
        }
        else // by convention position with no index is smaller
        {
            return pOtherReg == nullptr;
        }
    }
    return false;
}
 
bool SwPosition::operator==(const SwPosition &rPos) const
{
    return (nNode == rPos.nNode)
        && (nContent == rPos.nContent);
}
 
bool SwPosition::operator!=(const SwPosition &rPos) const
{
    return (nNode != rPos.nNode)
        || (nContent != rPos.nContent);
}
 
SwDoc * SwPosition::GetDoc() const
{
    return nNode.GetNode().GetDoc();
}
 
void SwPosition::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    xmlTextWriterStartElement(pWriter, BAD_CAST("SwPosition"));
    xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nNode"), BAD_CAST(OString::number(nNode.GetIndex()).getStr()));
    xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nContent"), BAD_CAST(OString::number(nContent.GetIndex()).getStr()));
    xmlTextWriterEndElement(pWriter);
}
 
std::ostream &operator <<(std::ostream& s, const SwPosition& position)
{
    return s << "SwPosition (node " << position.nNode.GetIndex() << ", offset " << position.nContent.GetIndex() << ")";
}
 
enum CHKSECTION { Chk_Both, Chk_One, Chk_None };
 
static CHKSECTION lcl_TstIdx( sal_uLong nSttIdx, sal_uLong nEndIdx, const SwNode& rEndNd )
{
    sal_uLong nStt = rEndNd.StartOfSectionIndex(), nEnd = rEndNd.GetIndex();
    CHKSECTION eSec = nStt < nSttIdx && nEnd >= nSttIdx ? Chk_One : Chk_None;
    if( nStt < nEndIdx && nEnd >= nEndIdx )
        return( eSec == Chk_One ? Chk_Both : Chk_One );
    return eSec;
}
 
static bool lcl_ChkOneRange( CHKSECTION eSec, bool bChkSections,
                    const SwNode& rBaseEnd, sal_uLong nStt, sal_uLong nEnd )
{
    if( eSec != Chk_Both )
        return false;
 
    if( !bChkSections )
        return true;
 
    // search the surrounding section
    const SwNodes& rNds = rBaseEnd.GetNodes();
    const SwNode *pTmp, *pNd = rNds[ nStt ];
    if( !pNd->IsStartNode() )
        pNd = pNd->StartOfSectionNode();
 
    if( pNd == rNds[ nEnd ]->StartOfSectionNode() )
        return true; // same StartNode, same section
 
    // already on a base node => error
    if( !pNd->StartOfSectionIndex() )
        return false;
 
    while( ( pTmp = pNd->StartOfSectionNode())->EndOfSectionNode() !=
            &rBaseEnd )
        pNd = pTmp;
 
    sal_uLong nSttIdx = pNd->GetIndex(), nEndIdx = pNd->EndOfSectionIndex();
    return nSttIdx <= nStt && nStt <= nEndIdx &&
           nSttIdx <= nEnd && nEnd <= nEndIdx;
}
 
bool CheckNodesRange( const SwNodeIndex& rStt,
                      const SwNodeIndex& rEnd, bool bChkSection )
{
    const SwNodes& rNds = rStt.GetNodes();
    sal_uLong nStt = rStt.GetIndex(), nEnd = rEnd.GetIndex();
    CHKSECTION eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfContent() );
    if( Chk_None != eSec )
        return eSec == Chk_Both;
 
    eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfAutotext() );
    if( Chk_None != eSec )
        return lcl_ChkOneRange( eSec, bChkSection,
                            rNds.GetEndOfAutotext(), nStt, nEnd );
 
    eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfPostIts() );
    if( Chk_None != eSec )
        return lcl_ChkOneRange( eSec, bChkSection,
                            rNds.GetEndOfPostIts(), nStt, nEnd );
 
    eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfInserts() );
    if( Chk_None != eSec )
        return lcl_ChkOneRange( eSec, bChkSection,
                            rNds.GetEndOfInserts(), nStt, nEnd );
 
    eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfRedlines() );
    if( Chk_None != eSec )
        return lcl_ChkOneRange( eSec, bChkSection,
                            rNds.GetEndOfRedlines(), nStt, nEnd );
 
    return false; // somewhere in between => error
}
 
bool GoNext(SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode )
{
    if( pNd->IsContentNode() )
        return static_cast<SwContentNode*>(pNd)->GoNext( pIdx, nMode );
    return false;
}
 
bool GoPrevious( SwNode* pNd, SwIndex * pIdx, sal_uInt16 nMode )
{
    if( pNd->IsContentNode() )
        return static_cast<SwContentNode*>(pNd)->GoPrevious( pIdx, nMode );
    return false;
}
 
SwContentNode* GoNextNds( SwNodeIndex* pIdx, bool bChk )
{
    SwNodeIndex aIdx( *pIdx );
    SwContentNode* pNd = aIdx.GetNodes().GoNext( &aIdx );
    if( pNd )
    {
        if( bChk && 1 != aIdx.GetIndex() - pIdx->GetIndex() &&
            !CheckNodesRange( *pIdx, aIdx, true ) )
                pNd = nullptr;
        else
            *pIdx = aIdx;
    }
    return pNd;
}
 
SwContentNode* GoPreviousNds( SwNodeIndex * pIdx, bool bChk )
{
    SwNodeIndex aIdx( *pIdx );
    SwContentNode* pNd = SwNodes::GoPrevious( &aIdx );
    if( pNd )
    {
        if( bChk && 1 != pIdx->GetIndex() - aIdx.GetIndex() &&
            !CheckNodesRange( *pIdx, aIdx, true ) )
                pNd = nullptr;
        else
            *pIdx = aIdx;
    }
    return pNd;
}
 
SwPaM::SwPaM( const SwPosition& rPos, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rPos )
    , m_Bound2( rPos.nNode.GetNode().GetNodes() ) // default initialize
    , m_pPoint( &m_Bound1 )
    , m_pMark( m_pPoint )
    , m_bIsInFrontOfLabel( false )
{
}
 
SwPaM::SwPaM( const SwPosition& rMark, const SwPosition& rPoint, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rMark )
    , m_Bound2( rPoint )
    , m_pPoint( &m_Bound2 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
}
 
SwPaM::SwPaM( const SwNodeIndex& rMark, const SwNodeIndex& rPoint,
              long nMarkOffset, long nPointOffset, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rMark )
    , m_Bound2( rPoint )
    , m_pPoint( &m_Bound2 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    if ( nMarkOffset )
    {
        m_pMark->nNode += nMarkOffset;
    }
    if ( nPointOffset )
    {
        m_pPoint->nNode += nPointOffset;
    }
    m_Bound1.nContent.Assign( m_Bound1.nNode.GetNode().GetContentNode(), 0 );
    m_Bound2.nContent.Assign( m_Bound2.nNode.GetNode().GetContentNode(), 0 );
}
 
SwPaM::SwPaM( const SwNode& rMark, const SwNode& rPoint,
              long nMarkOffset, long nPointOffset, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rMark )
    , m_Bound2( rPoint )
    , m_pPoint( &m_Bound2 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    if ( nMarkOffset )
    {
        m_pMark->nNode += nMarkOffset;
    }
    if ( nPointOffset )
    {
        m_pPoint->nNode += nPointOffset;
    }
    m_Bound1.nContent.Assign( m_Bound1.nNode.GetNode().GetContentNode(), 0 );
    m_Bound2.nContent.Assign( m_Bound2.nNode.GetNode().GetContentNode(), 0 );
}
 
SwPaM::SwPaM( const SwNodeIndex& rMark, sal_Int32 nMarkContent,
              const SwNodeIndex& rPoint, sal_Int32 nPointContent, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rMark )
    , m_Bound2( rPoint )
    , m_pPoint( &m_Bound2 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    m_pPoint->nContent.Assign( rPoint.GetNode().GetContentNode(), nPointContent);
    m_pMark ->nContent.Assign( rMark .GetNode().GetContentNode(), nMarkContent );
}
 
SwPaM::SwPaM( const SwNode& rMark, sal_Int32 nMarkContent,
              const SwNode& rPoint, sal_Int32 nPointContent, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rMark )
    , m_Bound2( rPoint )
    , m_pPoint( &m_Bound2 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    m_pPoint->nContent.Assign( m_pPoint->nNode.GetNode().GetContentNode(),
        nPointContent);
    m_pMark ->nContent.Assign( m_pMark ->nNode.GetNode().GetContentNode(),
        nMarkContent );
}
 
SwPaM::SwPaM( const SwNode& rNode, sal_Int32 nContent, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rNode )
    , m_Bound2( m_Bound1.nNode.GetNode().GetNodes() ) // default initialize
    , m_pPoint( &m_Bound1 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    m_pPoint->nContent.Assign( m_pPoint->nNode.GetNode().GetContentNode(),
        nContent );
}
 
SwPaM::SwPaM( const SwNodeIndex& rNodeIdx, sal_Int32 nContent, SwPaM* pRing )
    : Ring( pRing )
    , m_Bound1( rNodeIdx )
    , m_Bound2( rNodeIdx.GetNode().GetNodes() ) // default initialize
    , m_pPoint( &m_Bound1 )
    , m_pMark( &m_Bound1 )
    , m_bIsInFrontOfLabel( false )
{
    m_pPoint->nContent.Assign( rNodeIdx.GetNode().GetContentNode(), nContent );
}
 
SwPaM::~SwPaM() {}
 
SwPaM::SwPaM(SwPaM const& rPam, SwPaM *const pRing)
    : Ring(pRing)
    , m_Bound1( *(rPam.m_pPoint) )
    , m_Bound2( *(rPam.m_pMark)  )
    , m_pPoint( &m_Bound1 ), m_pMark( rPam.HasMark() ? &m_Bound2 : m_pPoint )
    , m_bIsInFrontOfLabel( false )
{
}
 
// @@@ semantic: no copy assignment for super class Ring.
SwPaM &SwPaM::operator=( const SwPaM &rPam )
{
    *m_pPoint = *( rPam.m_pPoint );
    if ( rPam.HasMark() )
    {
        SetMark();
        *m_pMark = *( rPam.m_pMark );
    }
    else
    {
        DeleteMark();
    }
    return *this;
}
 
void SwPaM::SetMark()
{
    if (m_pPoint == &m_Bound1)
    {
        m_pMark = &m_Bound2;
    }
    else
    {
        m_pMark = &m_Bound1;
    }
    (*m_pMark) = *m_pPoint;
}
 
#ifdef DBG_UTIL
void SwPaM::Exchange()
{
    if (m_pPoint != m_pMark)
    {
        SwPosition *pTmp = m_pPoint;
        m_pPoint = m_pMark;
        m_pMark = pTmp;
    }
}
#endif
 
/// movement of cursor
bool SwPaM::Move( SwMoveFnCollection const & fnMove, SwGoInDoc fnGo )
{
    const bool bRet = (*fnGo)( *this, fnMove );
 
    m_bIsInFrontOfLabel = false;
    return bRet;
}
 
/** make a new region
 
    Sets the first SwPaM onto the given SwPaM, or to the beginning or end of a
    document. SPoint stays at its position, GetMark will be changed respectively.
 
    @param fnMove  Contains information if beginning or end of document.
    @param pOrigRg The given region.
 
    @return Newly created range, in Ring with parameter pOrigRg.
*/
std::unique_ptr<SwPaM> SwPaM::MakeRegion( SwMoveFnCollection const & fnMove, const SwPaM * pOrigRg )
{
    std::unique_ptr<SwPaM> pPam;
    if( pOrigRg == nullptr )
    {
        pPam.reset(new SwPaM( *m_pPoint ));
        pPam->SetMark(); // set beginning
        pPam->Move( fnMove, GoInSection); // to beginning or end of a node
 
        // set SPoint onto its old position; set GetMark to the "end"
        pPam->Exchange();
    }
    else
    {
        pPam.reset(new SwPaM(*pOrigRg, const_cast<SwPaM*>(pOrigRg))); // given search range
        // make sure that SPoint is on the "real" start position
        // FORWARD: SPoint always smaller than GetMark
        // BACKWARD: SPoint always bigger than GetMark
        if( (pPam->GetMark()->*fnMove.fnCmpOp)( *pPam->GetPoint() ) )
            pPam->Exchange();
    }
    return pPam;
}
 
void SwPaM::Normalize(bool bPointFirst)
{
    if (HasMark())
        if ( ( bPointFirst && *m_pPoint > *m_pMark) ||
             (!bPointFirst && *m_pPoint < *m_pMark) )
        {
            Exchange();
        }
}
 
/// return page number at cursor (for reader and page bound frames)
sal_uInt16 SwPaM::GetPageNum( bool bAtPoint, const Point* pLayPos )
{
    const SwContentFrame* pCFrame;
    const SwPageFrame *pPg;
    const SwContentNode *pNd ;
    const SwPosition* pPos = bAtPoint ? m_pPoint : m_pMark;
 
    if( nullptr != ( pNd = pPos->nNode.GetNode().GetContentNode() ) &&
        nullptr != ( pCFrame = pNd->getLayoutFrame( pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), pLayPos, pPos, false )) &&
        nullptr != ( pPg = pCFrame->FindPageFrame() ))
        return pPg->GetPhyPageNum();
    return 0;
}
 
// form view - see also SwCursorShell::IsCursorReadonly()
static const SwFrame* lcl_FindEditInReadonlyFrame( const SwFrame& rFrame )
{
    const SwFrame* pRet = nullptr;
 
    const SwFlyFrame* pFly;
    const SwSectionFrame* pSectionFrame;
 
    if( rFrame.IsInFly() &&
       (pFly = rFrame.FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
        pFly->Lower() &&
       !pFly->Lower()->IsNoTextFrame() )
    {
       pRet = pFly;
    }
    else if ( rFrame.IsInSct() &&
              nullptr != ( pSectionFrame = rFrame.FindSctFrame() )->GetSection() &&
              pSectionFrame->GetSection()->IsEditInReadonlyFlag() )
    {
        pRet = pSectionFrame;
    }
 
    return pRet;
}
 
/// is in protected section or selection surrounds something protected
bool SwPaM::HasReadonlySel( bool bFormView ) const
{
    bool bRet = false;
 
    const SwContentNode* pNd = GetPoint()->nNode.GetNode().GetContentNode();
    const SwContentFrame *pFrame = nullptr;
    if ( pNd != nullptr )
    {
        Point aTmpPt;
        pFrame = pNd->getLayoutFrame( pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), &aTmpPt, GetPoint(), false );
    }
 
    // Will be set if point are inside edit-in-readonly environment
    const SwFrame* pPointEditInReadonlyFrame = nullptr;
    if ( pFrame != nullptr
         && ( pFrame->IsProtected()
              || ( bFormView
                   && nullptr == ( pPointEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) )
    {
        bRet = true;
    }
    else if( pNd != nullptr )
    {
        const SwSectionNode* pSNd = pNd->GetSectionNode();
        if ( pSNd != nullptr
             && ( pSNd->GetSection().IsProtectFlag()
                  || ( bFormView
                       && !pSNd->GetSection().IsEditInReadonlyFlag()) ) )
        {
            bRet = true;
        }
    }
 
    if ( !bRet
         && HasMark()
         && GetPoint()->nNode != GetMark()->nNode )
    {
        pNd = GetMark()->nNode.GetNode().GetContentNode();
        pFrame = nullptr;
        if ( pNd != nullptr )
        {
            Point aTmpPt;
            pFrame = pNd->getLayoutFrame( pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), &aTmpPt, GetMark(), false );
        }
 
        const SwFrame* pMarkEditInReadonlyFrame = nullptr;
        if ( pFrame != nullptr
             && ( pFrame->IsProtected()
                  || ( bFormView
                       && nullptr == ( pMarkEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) )
        {
            bRet = true;
        }
        else if( pNd != nullptr )
        {
            const SwSectionNode* pSNd = pNd->GetSectionNode();
            if ( pSNd != nullptr
                 && ( pSNd->GetSection().IsProtectFlag()
                      || ( bFormView
                           && !pSNd->GetSection().IsEditInReadonlyFlag()) ) )
            {
                bRet = true;
            }
        }
 
        if ( !bRet && bFormView )
        {
           // Check if start and end frame are inside the _same_
           // edit-in-readonly-environment. Otherwise we better return 'true'
           if ( pPointEditInReadonlyFrame != pMarkEditInReadonlyFrame )
                bRet = true;
        }
 
        // check for protected section inside the selection
        if( !bRet )
        {
            sal_uLong nSttIdx = GetMark()->nNode.GetIndex(),
                    nEndIdx = GetPoint()->nNode.GetIndex();
            if( nEndIdx <= nSttIdx )
            {
                sal_uLong nTmp = nSttIdx;
                nSttIdx = nEndIdx;
                nEndIdx = nTmp;
            }
 
            // If a protected section should be between nodes, then the
            // selection needs to contain already x nodes.
            // (TextNd, SectNd, TextNd, EndNd, TextNd )
            if( nSttIdx + 3 < nEndIdx )
            {
                const SwSectionFormats& rFormats = GetDoc()->GetSections();
                for( SwSectionFormats::size_type n = rFormats.size(); n;  )
                {
                    const SwSectionFormat* pFormat = rFormats[ --n ];
                    if( pFormat->GetProtect().IsContentProtected() )
                    {
                        const SwFormatContent& rContent = pFormat->GetContent(false);
                        OSL_ENSURE( rContent.GetContentIdx(), "where is the SectionNode?" );
                        sal_uLong nIdx = rContent.GetContentIdx()->GetIndex();
                        if( nSttIdx <= nIdx && nEndIdx >= nIdx &&
                            rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes() )
                        {
                            bRet = true;
                            break;
                        }
                    }
                }
            }
        }
    }
 
    const SwDoc *pDoc = GetDoc();
    const IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess();
    sw::mark::IFieldmark* pA = GetPoint() ? pMarksAccess->getFieldmarkFor( *GetPoint( ) ) : nullptr;
    sw::mark::IFieldmark* pB = GetMark()  ? pMarksAccess->getFieldmarkFor( *GetMark( ) ) : pA;
 
    if (!bRet)
    {
        bool bUnhandledMark = pA && pA->GetFieldname( ) == ODF_UNHANDLED;
        // Unhandled fieldmarks case shouldn't be edited manually to avoid breaking anything
        if ( ( pA == pB ) && bUnhandledMark )
            bRet = true;
        else
        {
            bool bAtStartA = (pA != nullptr) && (pA->GetMarkStart() == *GetPoint());
            bool bAtStartB = (pB != nullptr) && (pB->GetMarkStart() == *GetMark());
 
            if ((pA == pB) && (bAtStartA != bAtStartB))
                bRet = true;
            else if (pA != pB)
            {
                // If both points are either outside or at marks edges (i.e. selection either
                // touches fields, or fully encloses it), then don't disable editing
                bRet = !( ( !pA || bAtStartA ) && ( !pB || bAtStartB ) );
            }
            if( !bRet && pDoc->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM ) )
            {
                // Form protection case
                bRet = ( pA == nullptr ) || ( pB == nullptr ) || bAtStartA || bAtStartB;
            }
        }
    }
    else
    {
        bRet = !( pA == pB && pA != nullptr );
    }
 
    if (!bRet)
    {
        // Paragraph Signatures and Classification fields are read-only.
        if (pDoc && pDoc->GetEditShell())
            bRet = pDoc->GetEditShell()->IsCursorInParagraphMetadataField();
    }
 
    return bRet;
}
 
/// This function returns the next node in direction of search. If there is no
/// left or the next is out of the area, then a null-pointer is returned.
/// @param rbFirst If <true> than first time request. If so than the position of
///        the PaM must not be changed!
SwContentNode* GetNode( SwPaM & rPam, bool& rbFirst, SwMoveFnCollection const & fnMove,
                      bool bInReadOnly )
{
    SwContentNode * pNd = nullptr;
    if( ((*rPam.GetPoint()).*fnMove.fnCmpOp)( *rPam.GetMark() ) ||
        ( *rPam.GetPoint() == *rPam.GetMark() && rbFirst ) )
    {
        SwContentFrame* pFrame;
        if( rbFirst )
        {
            rbFirst = false;
            pNd = rPam.GetContentNode();
            if( pNd )
            {
                if(
                    (
                        nullptr == ( pFrame = pNd->getLayoutFrame( pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) ) ||
                        ( !bInReadOnly && pFrame->IsProtected() ) ||
                        (pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow())
                    ) ||
                    ( !bInReadOnly && pNd->FindSectionNode() &&
                        pNd->FindSectionNode()->GetSection().IsProtect()
                    )
                  )
                    {
                        pNd = nullptr;
                    }
            }
        }
 
        if( !pNd ) // is the cursor not on a ContentNode?
        {
            SwPosition aPos( *rPam.GetPoint() );
            bool bSrchForward = &fnMove == &fnMoveForward;
            SwNodes& rNodes = aPos.nNode.GetNodes();
 
            // go to next/previous ContentNode
            while( true )
            {
                pNd = bSrchForward
                        ? rNodes.GoNextSection( &aPos.nNode, true, !bInReadOnly )
                        : SwNodes::GoPrevSection( &aPos.nNode, true, !bInReadOnly );
                if( pNd )
                {
                    aPos.nContent.Assign( pNd, ::GetSttOrEnd( bSrchForward,*pNd ));
                    // is the position still in the area
                    if( (aPos.*fnMove.fnCmpOp)( *rPam.GetMark() ) )
                    {
                        // only in AutoTextSection can be nodes that are hidden
                        if( nullptr == ( pFrame = pNd->getLayoutFrame( pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) ) ||
                            ( !bInReadOnly && pFrame->IsProtected() ) ||
                            ( pFrame->IsTextFrame() &&
                                static_cast<SwTextFrame*>(pFrame)->IsHiddenNow() ) )
                        {
                            pNd = nullptr;
                            continue;
                        }
                        *rPam.GetPoint() = aPos;
                    }
                    else
                        pNd = nullptr; // no valid node
                    break;
                }
                break;
            }
        }
    }
    return pNd;
}
 
void GoStartDoc( SwPosition * pPos )
{
    SwNodes& rNodes = pPos->nNode.GetNodes();
    pPos->nNode = *rNodes.GetEndOfContent().StartOfSectionNode();
    // we always need to find a ContentNode!
    SwContentNode* pCNd = rNodes.GoNext( &pPos->nNode );
    if( pCNd )
        pCNd->MakeStartIndex( &pPos->nContent );
}
 
void GoEndDoc( SwPosition * pPos )
{
    SwNodes& rNodes = pPos->nNode.GetNodes();
    pPos->nNode = rNodes.GetEndOfContent();
    SwContentNode* pCNd = GoPreviousNds( &pPos->nNode, true );
    if( pCNd )
        pCNd->MakeEndIndex( &pPos->nContent );
}
 
void GoStartSection( SwPosition * pPos )
{
    // jump to section's beginning
    SwNodes& rNodes = pPos->nNode.GetNodes();
    sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode );
    if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() )
        nLevel--;
    do { SwNodes::GoStartOfSection( &pPos->nNode ); } while( nLevel-- );
 
    // already on a ContentNode
    pPos->nNode.GetNode().GetContentNode()->MakeStartIndex( &pPos->nContent );
}
 
/// go to the end of the current base section
void GoEndSection( SwPosition * pPos )
{
    // jump to section's beginning/end
    SwNodes& rNodes = pPos->nNode.GetNodes();
    sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode );
    if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() )
        nLevel--;
    do { SwNodes::GoEndOfSection( &pPos->nNode ); } while( nLevel-- );
 
    // now on a EndNode, thus to the previous ContentNode
    if( GoPreviousNds( &pPos->nNode, true ) )
        pPos->nNode.GetNode().GetContentNode()->MakeEndIndex( &pPos->nContent );
}
 
bool GoInDoc( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    (*fnMove.fnDoc)( rPam.GetPoint() );
    return true;
}
 
bool GoInSection( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    (*fnMove.fnSections)( rPam.GetPoint() );
    return true;
}
 
bool GoInNode( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    SwContentNode *pNd = (*fnMove.fnNds)( &rPam.GetPoint()->nNode, true );
    if( pNd )
        rPam.GetPoint()->nContent.Assign( pNd,
                        ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) );
    return pNd;
}
 
bool GoInContent( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(),
                        &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS ))
        return true;
    return GoInNode( rPam, fnMove );
}
 
bool GoInContentCells( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(),
                         &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS ))
        return true;
    return GoInNode( rPam, fnMove );
}
 
bool GoInContentSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(),
                        &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS | CRSR_SKIP_HIDDEN ) )
        return true;
    return GoInNode( rPam, fnMove );
}
 
bool GoInContentCellsSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(),
                         &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS | CRSR_SKIP_HIDDEN ) )
        return true;
    return GoInNode( rPam, fnMove );
}
 
bool GoPrevPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
{
    if( rPam.Move( fnMoveBackward, GoInNode ) )
    {
        // always on a ContentNode
        SwPosition& rPos = *rPam.GetPoint();
        SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode();
        rPos.nContent.Assign( pNd,
                            ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) );
        return true;
    }
    return false;
}
 
bool GoCurrPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
{
    SwPosition& rPos = *rPam.GetPoint();
    SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode();
    if( pNd )
    {
        const sal_Int32 nOld = rPos.nContent.GetIndex();
        const sal_Int32 nNew = &aPosPara == &fnMoveForward ? 0 : pNd->Len();
        // if already at beginning/end then to the next/previous
        if( nOld != nNew )
        {
            rPos.nContent.Assign( pNd, nNew );
            return true;
        }
    }
    // move node to next/previous ContentNode
    if( ( &aPosPara==&fnParaStart && nullptr != ( pNd =
            GoPreviousNds( &rPos.nNode, true ))) ||
        ( &aPosPara==&fnParaEnd && nullptr != ( pNd =
            GoNextNds( &rPos.nNode, true ))) )
    {
        rPos.nContent.Assign( pNd,
                        ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ));
        return true;
    }
    return false;
}
 
bool GoNextPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
{
    if( rPam.Move( fnMoveForward, GoInNode ) )
    {
        // always on a ContentNode
        SwPosition& rPos = *rPam.GetPoint();
        SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode();
        rPos.nContent.Assign( pNd,
                        ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) );
        return true;
    }
    return false;
}
 
bool GoCurrSection( SwPaM & rPam, SwMoveFnCollection const & fnMove )
{
    SwPosition& rPos = *rPam.GetPoint();
    SwPosition aSavePos( rPos ); // position for comparison
    (fnMove.fnSection)( &rPos.nNode );
    SwContentNode *pNd;
    if( nullptr == ( pNd = rPos.nNode.GetNode().GetContentNode()) &&
        nullptr == ( pNd = (*fnMove.fnNds)( &rPos.nNode, true )) )
    {
        rPos = aSavePos; // do not change cursor
        return false;
    }
 
    rPos.nContent.Assign( pNd,
                        ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) );
    return aSavePos != rPos;
}
 
OUString SwPaM::GetText() const
{
    OUStringBuffer aResult;
 
    SwNodeIndex aNodeIndex = Start()->nNode;
 
    // The first node can be already the end node.
    // Use a "forever" loop with an exit condition in the middle
    // of its body, in order to correctly handle all cases.
    bool bIsStartNode = true;
    for (;;)
    {
        const bool bIsEndNode = aNodeIndex == End()->nNode;
        SwTextNode * pTextNode = aNodeIndex.GetNode().GetTextNode();
 
        if (pTextNode != nullptr)
        {
            const OUString aTmpStr = pTextNode->GetText();
 
            if (bIsStartNode || bIsEndNode)
            {
                // Handle corner cases of start/end node(s)
                const sal_Int32 nStart = bIsStartNode
                    ? Start()->nContent.GetIndex()
                    : 0;
                const sal_Int32 nEnd = bIsEndNode
                    ? End()->nContent.GetIndex()
                    : aTmpStr.getLength();
 
                aResult.appendCopy(aTmpStr, nStart, nEnd-nStart);
            }
            else
            {
                aResult.append(aTmpStr);
            }
        }
 
        if (bIsEndNode)
        {
            break;
        }
 
        ++aNodeIndex;
        bIsStartNode = false;
    }
 
    return aResult.makeStringAndClear();
}
 
void SwPaM::InvalidatePaM()
{
    const SwNode &_pNd = GetNode();
    const SwTextNode *_pTextNd = _pNd.GetTextNode();
    if (_pTextNd != nullptr)
    {
        // pretend that the PaM marks inserted text to recalc the portion...
        SwInsText aHint( Start()->nContent.GetIndex(),
                        End()->nContent.GetIndex() - Start()->nContent.GetIndex() + 1 );
        SwModify *_pModify=const_cast<SwModify*>(static_cast<SwModify const *>(_pTextNd));
        _pModify->ModifyNotification( nullptr, &aHint);
    }
}
 
void SwPaM::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    xmlTextWriterStartElement(pWriter, BAD_CAST("SwPaM"));
 
    xmlTextWriterStartElement(pWriter, BAD_CAST("point"));
    GetPoint()->dumpAsXml(pWriter);
    xmlTextWriterEndElement(pWriter);
 
    if (HasMark())
    {
        xmlTextWriterStartElement(pWriter, BAD_CAST("mark"));
        GetMark()->dumpAsXml(pWriter);
        xmlTextWriterEndElement(pWriter);
    }
 
    xmlTextWriterEndElement(pWriter);
}
 
std::ostream &operator <<(std::ostream& s, const SwPaM& pam)
{
    if( pam.HasMark())
        return s << "SwPaM (point " << *pam.GetPoint() << ", mark " << *pam.GetMark() << ")";
    else
        return s << "SwPaM (point " << *pam.GetPoint() << ")";
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V595 The 'pDoc' pointer was utilized before it was verified against nullptr. Check lines: 704, 719.