/* -*- 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 <fmtfld.hxx>

#include <libxml/xmlwriter.h>

#include <fldbas.hxx>
#include <txtfld.hxx>
#include <txtannotationfld.hxx>
#include <docfld.hxx>
#include <docufld.hxx>
#include <doc.hxx>
#include <unofield.hxx>

#include <pam.hxx>
#include <reffld.hxx>
#include <ddefld.hxx>
#include <usrfld.hxx>
#include <expfld.hxx>
#include <ndtxt.hxx>
#include <hints.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <fieldhint.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <docsh.hxx>
#include <wrtsh.hxx>


// constructor for default item in attribute-pool
SwFormatField::SwFormatField( sal_uInt16 nWhich )
    : SfxPoolItem( nWhich )
    , SfxBroadcaster()
    , mpTextField( nullptr )
{
    setNonShareable();
}

SwFormatField::SwFormatField( const SwField &rField )
    : SfxPoolItem( RES_TXTATR_FIELD )
    , SfxBroadcaster()
    , mpField( rField.CopyField() )
    , mpTextField( nullptr )
{
    setNonShareable();
    rField.GetTyp()->Add(*this);
    if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
    {
        // input field in-place editing
        SetWhich( RES_TXTATR_INPUTFIELD );
        static_cast<SwInputField*>(mpField.get())->SetFormatField( *this );
    }
    else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
    {
        // see SwWrtShell::StartInputFieldDlg
        SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get()));
        if (pSetField->GetInputFlag()
            // only for string fields for now - inline editing of number fields
            // tends to produce error messages...
            && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType()
                & SwGetSetExpType::String))
        {
            SetWhich( RES_TXTATR_INPUTFIELD );
        }
        pSetField->SetFormatField(*this);
    }
    else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit )
    {
        // text annotation field
        SetWhich( RES_TXTATR_ANNOTATION );
    }
}

// #i24434#
// Since Items are used in ItemPool and in default constructed ItemSets with
// full pool range, all items need to be clonable. Thus, this one needed to be
// corrected
SwFormatField::SwFormatField( const SwFormatField& rAttr )
    : SfxPoolItem( rAttr )
    , SfxBroadcaster()
    , mpTextField( nullptr )
{
    setNonShareable();
    if ( !rAttr.mpField )
        return;

    rAttr.mpField->GetTyp()->Add(*this);
    mpField = rAttr.mpField->CopyField();
    if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
    {
        // input field in-place editing
        SetWhich( RES_TXTATR_INPUTFIELD );
        SwInputField *pField = dynamic_cast<SwInputField*>(mpField.get());
        assert(pField);
        if (pField)
            pField->SetFormatField( *this );
    }
    else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
    {
        SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get()));
        if (pSetField->GetInputFlag()
            && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType()
                & SwGetSetExpType::String))
        {
            SetWhich( RES_TXTATR_INPUTFIELD );
        }
        // see SwWrtShell::StartInputFieldDlg
        pSetField->SetFormatField(*this);
    }
    else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit )
    {
        // text annotation field
        SetWhich( RES_TXTATR_ANNOTATION );
    }
}

SwFormatField::~SwFormatField()
{
    Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::REMOVED ) );

    SwFieldType* pType = mpField ? mpField->GetTyp() : nullptr;
    if (pType && pType->Which() == SwFieldIds::Database)
        pType = nullptr;  // DB field types destroy themselves

    mpField.reset();

    // some fields need to delete their field type
    if( !(pType && pType->HasOnlyOneListener()) )
        return;

    bool bDel = false;
    switch( pType->Which() )
    {
    case SwFieldIds::User:
        bDel = static_cast<SwUserFieldType*>(pType)->IsDeleted();
        break;

    case SwFieldIds::SetExp:
        bDel = static_cast<SwSetExpFieldType*>(pType)->IsDeleted();
        break;

    case SwFieldIds::Dde:
        bDel = static_cast<SwDDEFieldType*>(pType)->IsDeleted();
        break;
    default: break;
    }

    if( bDel )
    {
        // unregister before deleting
        pType->Remove(*this);
        delete pType;
    }
}

void SwFormatField::RegisterToFieldType( SwFieldType& rType )
{
    rType.Add(*this);
}

void SwFormatField::SetField(std::unique_ptr<SwField> _pField)
{
    mpField = std::move(_pField);
    if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
    {
        static_cast<SwInputField* >(mpField.get())->SetFormatField( *this );
    }
    else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
    {
        // see SwWrtShell::StartInputFieldDlg
        static_cast<SwSetExpField *>(mpField.get())->SetFormatField(*this);
    }
    Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::CHANGED ) );
}

void SwFormatField::SetTextField( SwTextField& rTextField )
{
    mpTextField = &rTextField;
}

void SwFormatField::ClearTextField()
{
    mpTextField = nullptr;
}

bool SwFormatField::operator==( const SfxPoolItem& rAttr ) const
{
    assert(SfxPoolItem::operator==(rAttr));
    return ( mpField
             && static_cast<const SwFormatField&>(rAttr).mpField
             && mpField->GetTyp() == static_cast<const SwFormatField&>(rAttr).mpField->GetTyp()
             && mpField->GetUntypedFormat() == static_cast<const SwFormatField&>(rAttr).mpField->GetUntypedFormat() )
           ||
           ( !mpField && !static_cast<const SwFormatField&>(rAttr).mpField );
}

SwFormatField* SwFormatField::Clone( SfxItemPool* ) const
{
    return new SwFormatField( *this );
}

void SwFormatField::InvalidateField()
{
    if (auto xUnoField = m_wXTextField.get())
    {
        xUnoField->OnFormatFieldDelete();
        m_wXTextField.clear();
    }
}

void SwFormatField::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
{
    SwClient::SwClientNotify(rModify, rHint);
    if (rHint.GetId() == SfxHintId::SwAutoFormatUsedHint) {
        if(mpTextField)
            static_cast<const sw::AutoFormatUsedHint&>(rHint).CheckNode(mpTextField->GetpTextNode());
        return;
    }
    else if (rHint.GetId() == SfxHintId::SwField)
    {
        const auto pFieldHint = static_cast<const SwFieldHint*>( &rHint );
        // replace field content by text
        SwPaM* pPaM = pFieldHint->m_pPaM;
        pPaM->DeleteMark(); // TODO: this is really hackish

        if( !mpTextField )
            return;

        SwDoc& rDoc = pPaM->GetDoc();
        const SwTextNode& rTextNode = mpTextField->GetTextNode();
        pPaM->GetPoint()->Assign(rTextNode, mpTextField->GetStart());

        OUString const aEntry(mpField->ExpandField(rDoc.IsClipBoard(), pFieldHint->m_pLayout));
        pPaM->SetMark();
        pPaM->Move( fnMoveForward );
        rDoc.getIDocumentContentOperations().DeleteRange( *pPaM );
        rDoc.getIDocumentContentOperations().InsertString( *pPaM, aEntry );
    }
    else if (rHint.GetId() == SfxHintId::SwLegacyModify
            || rHint.GetId() == SfxHintId::SwFormatChange
            || rHint.GetId() == SfxHintId::SwAttrSetChange
            || rHint.GetId() == SfxHintId::SwObjectDying
            || rHint.GetId() == SfxHintId::SwUpdateAttr)
    {
        if(!mpTextField)
            return;
        UpdateTextNode(rHint);
    }
    else if (rHint.GetId() == SfxHintId::SwFindFormatForField)
    {
        const auto pFindForFieldHint = static_cast<const sw::FindFormatForFieldHint*>( &rHint );
        if(pFindForFieldHint->m_rpFormat == nullptr && pFindForFieldHint->m_pField == GetField())
            pFindForFieldHint->m_rpFormat = this;
    }
    else if (rHint.GetId() == SfxHintId::SwFindFormatForPostItId)
    {
        const auto pFindForPostItIdHint = static_cast<const sw::FindFormatForPostItIdHint*>( &rHint );
        auto pPostItField = dynamic_cast<SwPostItField*>(mpField.get());
        if(pPostItField && pFindForPostItIdHint->m_rpFormat == nullptr && pFindForPostItIdHint->m_nPostItId == pPostItField->GetPostItId())
            pFindForPostItIdHint->m_rpFormat = this;
    }
    else if (rHint.GetId() == SfxHintId::SwCollectPostIts)
    {
        const auto pCollectPostItsHint = static_cast<const sw::CollectPostItsHint*>( &rHint );
        if(GetTextField() && IsFieldInDoc() && (!pCollectPostItsHint->m_bHideRedlines || !sw::IsFieldDeletedInModel(pCollectPostItsHint->m_rIDRA, *GetTextField())))
            pCollectPostItsHint->m_rvFormatFields.push_back(this);
    }
    else if (rHint.GetId() == SfxHintId::SwHasHiddenInformationNotes)
    {
        const auto pHasHiddenInfoHint = static_cast<const sw::HasHiddenInformationNotesHint*>( &rHint );
        if(!pHasHiddenInfoHint->m_rbHasHiddenInformationNotes && GetTextField() && IsFieldInDoc())
            pHasHiddenInfoHint->m_rbHasHiddenInformationNotes = true;
    }
    else if (rHint.GetId() == SfxHintId::SwGatherNodeIndex)
    {
        const auto pGatherNodeIndexHint = static_cast<const sw::GatherNodeIndexHint*>( &rHint );
        if(auto pTextField = GetTextField())
            pGatherNodeIndexHint->m_rvNodeIndex.push_back(pTextField->GetTextNode().GetIndex());
    }
    else if (rHint.GetId() == SfxHintId::SwGatherRefFields)
    {
        const auto pGatherRefFieldsHint = static_cast<const sw::GatherRefFieldsHint*>( &rHint );
        if(!GetTextField())
            return;
        if(GetField()->GetTyp()->Which() != SwFieldIds::GetRef)
            return;
        auto pGetRefField = static_cast<const SwGetRefField*>(GetField());
        if (pGatherRefFieldsHint->m_nType != pGetRefField->GetSubType())
            return;
        SwTextNode* pNd = GetTextField()->GetpTextNode();
        if(pNd && pNd->GetNodes().IsDocNodes())
            pGatherRefFieldsHint->m_rvRFields.push_back(static_cast<SwGetRefField*>(GetField()));
    }
    else if (rHint.GetId() == SfxHintId::SwGatherFields)
    {
        const auto pGatherFieldsHint = static_cast<const sw::GatherFieldsHint*>( &rHint );
        if(pGatherFieldsHint->m_bCollectOnlyInDocNodes)
        {
            if(!GetTextField())
                return;
            SwTextNode* pNd = GetTextField()->GetpTextNode();
            if(!pNd || !pNd->GetNodes().IsDocNodes())
                return;
        }
        pGatherFieldsHint->m_rvFields.push_back(this);
    }
    else if (rHint.GetId() == SfxHintId::SwDocPosUpdate)
    {
        UpdateDocPos(static_cast<const sw::DocPosUpdate*>(&rHint)->m_nDocPos);
    }
}

namespace
{
    bool lcl_ExpandField(const SwFieldIds eId)
    {
        switch(eId)
        {
            case SwFieldIds::DbSetNumber:
            case SwFieldIds::DbNumSet:
            case SwFieldIds::DbNextSet:
            case SwFieldIds::DatabaseName:
                return false;
            default:
                return true;
        }

    };
    bool lcl_TriggerNode(const SwFieldIds eId)
    {
        switch(eId)
        {
            case SwFieldIds::HiddenPara:
            case SwFieldIds::DbSetNumber:
            case SwFieldIds::DbNumSet:
            case SwFieldIds::DbNextSet:
            case SwFieldIds::DatabaseName:
                return true;
            default:
                return false;
        }
    }
    void lcl_EnsureUserFieldValid(SwFieldType& rType)
    {
        if(rType.Which() != SwFieldIds::User)
            return;
        static_cast<SwUserFieldType*>(&rType)->EnsureValid();
    }
    bool lcl_NeedsForcedUpdate(const SwField& rField)
    {
        if (rField.GetTyp()->Which() == SwFieldIds::DocInfo)
        {
            auto pDocInfoField = static_cast<const SwDocInfoField*>(&rField);
            SwDocInfoSubType nSubType = pDocInfoField->GetSubType();
            // Do not consider extended SubTypes.
            nSubType &= SwDocInfoSubType::LowerMask;
            switch (nSubType)
            {
                case SwDocInfoSubType::Title:
                case SwDocInfoSubType::Subject:
                case SwDocInfoSubType::Change:
                case SwDocInfoSubType::Custom:
                    return false;
                default: break;
            }
        }
        return true;
    }
}

void SwFormatField::ForceUpdateTextNode()
{
    if (!IsFieldInDoc())
        return;

    SwTextNode* pTextNd = &mpTextField->GetTextNode();
    OSL_ENSURE(pTextNd, "Where is my Node?");

    auto pType = mpField->GetTyp();
    lcl_EnsureUserFieldValid(*pType);
    if(lcl_TriggerNode(pType->Which()))
        pTextNd->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, nullptr));
    if(!lcl_ExpandField(pType->Which()))
        return;

    // Force notify was added for conditional text fields,
    // at least the below fields need no forced notify.
    bool bNeedForced = lcl_NeedsForcedUpdate(*mpTextField->GetFormatField().GetField());
    if (!bNeedForced)
    {
        // Check if this is an idle update after any doc stat change or an explicit UI update.
        SwDocShell* pDocShell = pTextNd->GetDoc().GetDocShell();
        SwWrtShell* pWrtShell = pDocShell ? pDocShell->GetWrtShell() : nullptr;
        if (pWrtShell && pWrtShell->IsEnteringStdMode())
        {
            // The UI invoked the update, update even doc info fields.
            bNeedForced = true;
        }
    }
    mpTextField->ExpandTextField(bNeedForced);
}
void SwFormatField::UpdateDocPos(const SwTwips nDocPos)
{
    if (!IsFieldInDoc())
        return;
    auto pTextNd = &mpTextField->GetTextNode();

    pTextNd->UpdateDocPos(nDocPos, mpTextField->GetStart());
}
void SwFormatField::UpdateTextNode(const SfxHint& rHint)
{
    if(SfxHintId::SwHiddenParaPrint == rHint.GetId())
    {
        if (!IsFieldInDoc())
            return;
        auto pType = mpField->GetTyp();
        lcl_EnsureUserFieldValid(*pType);
        bool bTriggerNode = lcl_TriggerNode(pType->Which());
        bool bExpand = lcl_ExpandField(pType->Which());
        if(bTriggerNode)
        {
            SwTextNode* pTextNd = &mpTextField->GetTextNode();
            OSL_ENSURE(pTextNd, "Where is my Node?");
            pTextNd->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, nullptr));
        }
        if(bExpand)
            mpTextField->ExpandTextField(false);
    }
    else if(SfxHintId::SwRemoveUnoObject == rHint.GetId())
    {   // invalidate cached UNO object
        m_wXTextField.clear();
        // ??? why does this Modify method not already do this?
        CallSwClientNotify(rHint);
    }
    else if (rHint.GetId() == SfxHintId::SwFormatChange)
    {
        auto pChangeHint = static_cast<const SwFormatChangeHint*>(&rHint);
        if (pChangeHint->m_pOldFormat == nullptr && pChangeHint->m_pNewFormat == nullptr)
        {
            ForceUpdateTextNode();
            return;
        }
        if (!IsFieldInDoc())
            return;

        SwTextNode* pTextNd = &mpTextField->GetTextNode();
        OSL_ENSURE(pTextNd, "Where is my Node?");

        bool bTriggerNode = pChangeHint->m_pNewFormat != nullptr;
        if(bTriggerNode)
            pTextNd->TriggerNodeUpdate(*pChangeHint);
    }
    else if (rHint.GetId() == SfxHintId::SwAttrSetChange)
    {
        auto pChangeHint = static_cast<const sw::AttrSetChangeHint*>(&rHint);
        auto pOld = pChangeHint->m_pOld;
        auto pNew = pChangeHint->m_pNew;
        if (pOld == nullptr && pNew == nullptr)
        {
            ForceUpdateTextNode();
            return;
        }

        if (!IsFieldInDoc())
            return;

        SwTextNode* pTextNd = &mpTextField->GetTextNode();
        OSL_ENSURE(pTextNd, "Where is my Node?");

        bool bTriggerNode = pNew != nullptr;
        if(bTriggerNode)
            pTextNd->TriggerNodeUpdate(sw::AttrSetChangeHint(pOld, pNew));
    }
    else if(SfxHintId::SwObjectDying == rHint.GetId())
    {
        assert(false && "do not expect this, might need to restore some code");
    }
    else if(SfxHintId::SwUpdateAttr == rHint.GetId())
    {
        auto pUpdateHint = static_cast<const sw::UpdateAttrHint*>(&rHint);
        auto pOld = pUpdateHint->m_pOld;
        auto pNew = pUpdateHint->m_pNew;
        if (pOld == nullptr && pNew == nullptr)
        {
            ForceUpdateTextNode();
            return;
        }

        if (!IsFieldInDoc())
            return;

        SwTextNode* pTextNd = &mpTextField->GetTextNode();
        OSL_ENSURE(pTextNd, "Where is my Node?");

        bool bTriggerNode = pNew != nullptr;
        bool bExpand = false;
        if(pNew)
        {
            auto pType = mpField->GetTyp();
            lcl_EnsureUserFieldValid(*pType);
            bTriggerNode = lcl_TriggerNode(pType->Which());
            bExpand = lcl_ExpandField(pType->Which());
            pOld = nullptr;
        }
        if(bTriggerNode)
            pTextNd->TriggerNodeUpdate(sw::UpdateAttrHint(pOld, pNew));
        if(bExpand)
            mpTextField->ExpandTextField(false);
    }
    else if(SfxHintId::SwLegacyModify == rHint.GetId())
    {
        auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
        auto pOld = pLegacy->m_pOld;
        auto pNew = pLegacy->m_pNew;
        if (pOld == nullptr && pNew == nullptr)
        {
            ForceUpdateTextNode();
            return;
        }

        if (!IsFieldInDoc())
            return;

        SwTextNode* pTextNd = &mpTextField->GetTextNode();
        OSL_ENSURE(pTextNd, "Where is my Node?");

        bool bTriggerNode = pNew != nullptr;
        bool bExpand = false;
        if(pNew)
        {
            auto pType = mpField->GetTyp();
            lcl_EnsureUserFieldValid(*pType);
            bTriggerNode = lcl_TriggerNode(pType->Which());
            bExpand = lcl_ExpandField(pType->Which());
            pOld = nullptr;
        }
        if(bTriggerNode)
            pTextNd->TriggerNodeUpdate(sw::LegacyModifyHint(pOld, pNew));
        if(bExpand)
            mpTextField->ExpandTextField(false);
    }
}

bool SwFormatField::IsFieldInDoc() const
{
    return mpTextField != nullptr
           && mpTextField->IsFieldInDoc();
}

bool SwFormatField::IsProtect() const
{
    return mpTextField != nullptr
           && mpTextField->GetpTextNode() != nullptr
           && mpTextField->GetpTextNode()->IsProtect();
}

void SwFormatField::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatField"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mpTextField"), "%p", mpTextField);

    SfxPoolItem::dumpAsXml(pWriter);
    if (mpField) // pool default doesn't have one
    {
        mpField->dumpAsXml(pWriter);
    }

    (void)xmlTextWriterEndElement(pWriter);
}

// class SwTextField ////////////////////////////////////////////////////

SwTextField::SwTextField(
    const SfxPoolItemHolder& rAttr,
    sal_Int32 const nStartPos,
    bool const bInClipboard)
    : SwTextAttr( rAttr, nStartPos )
// fdo#39694 the ExpandField here may not give the correct result in all cases,
// but is better than nothing
    , m_aExpand()
    , m_pTextNode( nullptr )
{
    SwFormatField& rSwFormatField(static_cast<SwFormatField&>(GetAttr()));
    m_aExpand = rSwFormatField.GetField()->ExpandField(bInClipboard, nullptr);
    rSwFormatField.SetTextField( *this );
    SetHasDummyChar(true);
}

SwTextField::~SwTextField( )
{
    SwFormatField & rFormatField( static_cast<SwFormatField &>(GetAttr()) );
    if ( this == rFormatField.GetTextField() )
    {
        rFormatField.ClearTextField();
    }
}

bool SwTextField::IsFieldInDoc() const
{
    return GetpTextNode() != nullptr
           && GetpTextNode()->GetNodes().IsDocNodes();
}

void SwTextField::ExpandTextField(const bool bForceNotify) const
{
    OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" );

    const SwField* pField = GetFormatField().GetField();
    const OUString aNewExpand( pField->ExpandField(m_pTextNode->GetDoc().IsClipBoard(),
        // can't do any better than this here...
        m_pTextNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout()) );

    const SwFieldIds nWhich = pField->GetTyp()->Which();
    const bool bSameExpandSimpleNotification
        = SwFieldIds::Chapter != nWhich && SwFieldIds::PageNumber != nWhich
          && SwFieldIds::RefPageGet != nWhich
          // Page count fields to not use aExpand during formatting,
          // therefore an invalidation of the text frame has to be triggered even if aNewExpand == aExpand:
          && (SwFieldIds::DocStat != nWhich
              || SwDocStatSubType::Page != static_cast<const SwDocStatField*>(pField)->GetSubType())
          && (SwFieldIds::GetExp != nWhich
              || static_cast<const SwGetExpField*>(pField)->IsInBodyText());

    bool bHiddenParaChanged = false;
    if (aNewExpand != m_aExpand || bSameExpandSimpleNotification)
        bHiddenParaChanged = m_pTextNode->CalcHiddenParaField();

    if (aNewExpand == m_aExpand)
    {
        if ( bSameExpandSimpleNotification )
        {
            if( bHiddenParaChanged )
                m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, nullptr));
            if ( !bForceNotify )
            {
                // done, if no further notification forced.
                return;
            }
        }
    }
    else
        m_aExpand = aNewExpand;

    const_cast<SwTextField*>(this)->NotifyContentChange( const_cast<SwFormatField&>(GetFormatField()) );
}

void SwTextField::CopyTextField( SwTextField *pDest ) const
{
    OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" );
    OSL_ENSURE( pDest->m_pTextNode, "SwTextField: where is pDest's TextNode?" );

    IDocumentFieldsAccess* pIDFA = &m_pTextNode->getIDocumentFieldsAccess();
    IDocumentFieldsAccess* pDestIDFA = &pDest->m_pTextNode->getIDocumentFieldsAccess();

    SwFormatField& rDestFormatField = const_cast<SwFormatField&>(pDest->GetFormatField());
    const SwFieldIds nFieldWhich = rDestFormatField.GetField()->GetTyp()->Which();

    if( pIDFA != pDestIDFA )
    {
        // different documents, e.g. clipboard:
        // register field type in target document
        SwFieldType* pFieldType;
        if( nFieldWhich != SwFieldIds::Database
            && nFieldWhich != SwFieldIds::User
            && nFieldWhich != SwFieldIds::SetExp
            && nFieldWhich != SwFieldIds::Dde
            && SwFieldIds::TableOfAuthorities != nFieldWhich )
        {
            pFieldType = pDestIDFA->GetSysFieldType( nFieldWhich );
        }
        else
        {
            pFieldType = pDestIDFA->InsertFieldType( *rDestFormatField.GetField()->GetTyp() );
        }

        // DDE fields need special treatment
        if( SwFieldIds::Dde == nFieldWhich )
        {
            if( rDestFormatField.GetTextField() )
            {
                static_cast<SwDDEFieldType*>(rDestFormatField.GetField()->GetTyp())->DecRefCnt();
            }
            static_cast<SwDDEFieldType*>(pFieldType)->IncRefCnt();
        }

        OSL_ENSURE( pFieldType, "unknown FieldType" );
        pFieldType->Add(rDestFormatField); // register at the field type
        rDestFormatField.GetField()->ChgTyp( pFieldType );
    }

    // update expression fields
    if( nFieldWhich == SwFieldIds::SetExp
        || nFieldWhich == SwFieldIds::GetExp
        || nFieldWhich == SwFieldIds::HiddenText )
    {
        SwTextField* pField = const_cast<SwTextField*>(this);
        pDestIDFA->UpdateExpFields( pField, true );
    }
    // table fields: external display
    else if( SwFieldIds::Table == nFieldWhich
             && static_cast<SwTableField*>(rDestFormatField.GetField())->IsIntrnlName() )
    {
        // convert internal (core) to external (UI) formula
        const SwTableNode* pTableNd = m_pTextNode->FindTableNode();
        if( pTableNd )        // in a table?
            static_cast<SwTableField*>(rDestFormatField.GetField())->PtrToBoxNm( &pTableNd->GetTable() );
    }
}

void SwTextField::NotifyContentChange(SwFormatField& rFormatField)
{
    //if not in undo section notify the change
    if (m_pTextNode && m_pTextNode->GetNodes().IsDocNodes())
        m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, &rFormatField));
}

/*static*/
void SwTextField::GetPamForTextField(
    const SwTextField& rTextField,
    std::shared_ptr< SwPaM >& rPamForTextField )
{
    if (rTextField.GetpTextNode() == nullptr)
    {
        SAL_WARN("sw.core", "<SwTextField::GetPamForField> - missing <SwTextNode>");
        return;
    }

    const SwTextNode& rTextNode = rTextField.GetTextNode();

    rPamForTextField = std::make_shared<SwPaM>( rTextNode,
                                    (rTextField.End() != nullptr) ? *(rTextField.End()) : ( rTextField.GetStart() + 1 ),
                                    rTextNode,
                                    rTextField.GetStart() );

}

/*static*/
void SwTextField::DeleteTextField( const SwTextField& rTextField )
{
    if (rTextField.GetpTextNode() != nullptr)
    {
        std::shared_ptr< SwPaM > pPamForTextField;
        GetPamForTextField(rTextField, pPamForTextField);
        if (pPamForTextField != nullptr)
        {
            rTextField.GetTextNode().GetDoc().getIDocumentContentOperations().DeleteAndJoin(*pPamForTextField);
        }
    }
}

// class SwTextInputField ///////////////////////////////////////////////

// input field in-place editing
SwTextInputField::SwTextInputField(
    const SfxPoolItemHolder& rAttr,
    sal_Int32 const nStart,
    sal_Int32 const nEnd,
    bool const bInClipboard )

    : SwTextAttr( rAttr, nStart )
    , SwTextAttrNesting( rAttr, nStart, nEnd )
    , SwTextField( rAttr, nStart, bInClipboard )
    , m_bLockNotifyContentChange( false )
{
    SetHasDummyChar( false );
    SetHasContent( true );
}

SwTextInputField::~SwTextInputField()
{
}

bool SwTextInputField::LockNotifyContentChange()
{
    if (m_bLockNotifyContentChange)
    {
        return false;
    }
    m_bLockNotifyContentChange = true;
    return true;
}

void SwTextInputField::UnlockNotifyContentChange()
{
    m_bLockNotifyContentChange = false;
}

void SwTextInputField::NotifyContentChange( SwFormatField& rFormatField )
{
    if ( !m_bLockNotifyContentChange )
    {
        LockNotifyContentChange();

        SwTextField::NotifyContentChange( rFormatField );
        UpdateTextNodeContent( GetFieldContent() );

        UnlockNotifyContentChange();
    }
}

OUString SwTextInputField::GetFieldContent() const
{
    return GetFormatField().GetField()->ExpandField(false, nullptr/*ignored anyway*/);
}

void SwTextInputField::UpdateFieldContent()
{
    if ( !(IsFieldInDoc()
         && GetStart() != (*End())) )
        return;

    assert( (*End()) - GetStart() >= 2 &&
            "<SwTextInputField::UpdateFieldContent()> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" );
    // skip CH_TXT_ATR_INPUTFIELDSTART character
    const sal_Int32 nIdx = GetStart() + 1;
    // skip CH_TXT_ATR_INPUTFIELDEND character
    const sal_Int32 nLen = static_cast<sal_Int32>(std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) ));
    const OUString aNewFieldContent = GetTextNode().GetExpandText(nullptr, nIdx, nLen);

    const SwField* pField = GetFormatField().GetField();
    const SwInputField* pInputField = dynamic_cast<const SwInputField*>(pField);
    if (pInputField)
        const_cast<SwInputField*>(pInputField)->applyFieldContent( aNewFieldContent );

    const SwSetExpField* pExpField = dynamic_cast<const SwSetExpField*>(pField);
    if (pExpField)
    {
        assert(pExpField->GetInputFlag());
        const_cast<SwSetExpField*>(pExpField)->SetPar2(aNewFieldContent);
    }
    assert(pInputField || pExpField);

    // trigger update of fields for scenarios in which the Input Field's content is part of e.g. a table formula
    GetTextNode().GetDoc().getIDocumentFieldsAccess().GetUpdateFields().SetFieldsDirty(true);
}

void SwTextInputField::UpdateTextNodeContent( const OUString& rNewContent )
{
    assert(IsFieldInDoc() &&
        "<SwTextInputField::UpdateTextNodeContent(..)> - misusage as Input Field is not in document content.");

    assert( (*End()) - GetStart() >= 2 &&
            "<SwTextInputField::UpdateTextNodeContent(..)> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" );
    // skip CH_TXT_ATR_INPUTFIELDSTART character
    const sal_Int32 nIdx = GetStart() + 1;
    // skip CH_TXT_ATR_INPUTFIELDEND character
    const sal_Int32 nDelLen = std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) );
    SwContentIndex aIdx( &GetTextNode(), nIdx );
    GetTextNode().ReplaceText( aIdx, nDelLen, rNewContent );
}

// class SwTextAnnotationField //////////////////////////////////////////

// text annotation field
SwTextAnnotationField::SwTextAnnotationField(
    const SfxPoolItemHolder& rAttr,
    sal_Int32 const nStart,
    bool const bInClipboard )
    : SwTextAttr( rAttr, nStart )
    , SwTextField( rAttr, nStart, bInClipboard )
{
}

SwTextAnnotationField::~SwTextAnnotationField()
{
}

::sw::mark::AnnotationMark* SwTextAnnotationField::GetAnnotationMark() const
{
    auto pPostItField = dynamic_cast<const SwPostItField*>(GetFormatField().GetField());
    assert(pPostItField);

    SwDoc& rDoc = static_cast<const SwPostItFieldType*>(pPostItField->GetTyp())->GetDoc();

    IDocumentMarkAccess* pMarksAccess = rDoc.getIDocumentMarkAccess();
    auto pMark = pMarksAccess->findAnnotationMark( pPostItField->GetName() );
    return pMark != pMarksAccess->getAnnotationMarksEnd()
           ? *pMark
           : nullptr;
}

void SwFormatField::SetXTextField(rtl::Reference<SwXTextField> const& xTextField)
{ m_wXTextField = xTextField.get(); }

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