/* -*- 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 <memory>
#include <fielduno.hxx>
#include <textuno.hxx>
#include <miscuno.hxx>
#include <docsh.hxx>
#include <hints.hxx>
#include <editsrc.hxx>
#include <unonames.hxx>
#include <editutil.hxx>
 
#include <svl/hint.hxx>
#include <utility>
#include <vcl/svapp.hxx>
 
#include <editeng/eeitem.hxx>
 
#include <editeng/editeng.hxx>
#include <editeng/editobj.hxx>
#include <editeng/flditem.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/supportsservice.hxx>
 
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/FilenameDisplayFormat.hpp>
#include <com/sun/star/text/textfield/Type.hpp>
 
using namespace com::sun::star;
 
namespace {
 
//  no Which-ID here, map only for PropertySetInfo
 
const SfxItemPropertySet* getDateTimePropertySet()
{
    static const SfxItemPropertyMapEntry aMapContent[] =
    {
        {  SC_UNONAME_DATETIME, 0, cppu::UnoType<util::DateTime>::get(), 0, 0 },
        {  SC_UNONAME_ISFIXED,  0, cppu::UnoType<bool>::get(),                  0, 0 },
        {  SC_UNONAME_ISDATE,   0, cppu::UnoType<bool>::get(),                  0, 0 },
        {  SC_UNONAME_NUMFMT,   0, cppu::UnoType<sal_Int32>::get(),      0, 0 },
    };
    static SfxItemPropertySet aMap(aMapContent);
    return &aMap;
}
 
const SfxItemPropertySet* getEmptyPropertySet()
{
    static SfxItemPropertySet aMap({});
    return &aMap;
}
 
const SfxItemPropertySet* lcl_GetURLPropertySet()
{
    static const SfxItemPropertyMapEntry aURLPropertyMap_Impl[] =
    {
        { SC_UNONAME_ANCTYPE,  0,  cppu::UnoType<text::TextContentAnchorType>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_ANCTYPES, 0,  cppu::UnoType<uno::Sequence<text::TextContentAnchorType>>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_REPR,     0,  cppu::UnoType<OUString>::get(),    0, 0},
        { SC_UNONAME_TARGET,   0,  cppu::UnoType<OUString>::get(),    0, 0},
        { SC_UNONAME_TEXTWRAP, 0,  cppu::UnoType<text::WrapTextMode>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_URL,      0,  cppu::UnoType<OUString>::get(),    0, 0},
    };
    static SfxItemPropertySet aURLPropertySet_Impl( aURLPropertyMap_Impl );
    return &aURLPropertySet_Impl;
}
 
const SfxItemPropertySet* lcl_GetHeaderFieldPropertySet()
{
    static const SfxItemPropertyMapEntry aHeaderFieldPropertyMap_Impl[] =
    {
        { SC_UNONAME_ANCTYPE,  0,  cppu::UnoType<text::TextContentAnchorType>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_ANCTYPES, 0,  cppu::UnoType<uno::Sequence<text::TextContentAnchorType>>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_TEXTWRAP, 0,  cppu::UnoType<text::WrapTextMode>::get(), beans::PropertyAttribute::READONLY, 0 },
    };
    static SfxItemPropertySet aHeaderFieldPropertySet_Impl( aHeaderFieldPropertyMap_Impl );
    return &aHeaderFieldPropertySet_Impl;
}
 
const SfxItemPropertySet* lcl_GetFileFieldPropertySet()
{
    static const SfxItemPropertyMapEntry aFileFieldPropertyMap_Impl[] =
    {
        { SC_UNONAME_ANCTYPE,  0,  cppu::UnoType<text::TextContentAnchorType>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_ANCTYPES, 0,  cppu::UnoType<uno::Sequence<text::TextContentAnchorType>>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_FILEFORM, 0,  cppu::UnoType<sal_Int16>::get(),        0, 0 },
        { SC_UNONAME_TEXTWRAP, 0,  cppu::UnoType<text::WrapTextMode>::get(), beans::PropertyAttribute::READONLY, 0 },
    };
    static SfxItemPropertySet aFileFieldPropertySet_Impl( aFileFieldPropertyMap_Impl );
    return &aFileFieldPropertySet_Impl;
}
 
SvxFileFormat lcl_UnoToSvxFileFormat( sal_Int16 nUnoValue )
{
    switch( nUnoValue )
    {
        case text::FilenameDisplayFormat::FULL: return SvxFileFormat::PathFull;
        case text::FilenameDisplayFormat::PATH: return SvxFileFormat::PathOnly;
        case text::FilenameDisplayFormat::NAME: return SvxFileFormat::NameOnly;
        default:
            return SvxFileFormat::NameAndExt;
    }
}
 
sal_Int16 lcl_SvxToUnoFileFormat( SvxFileFormat nSvxValue )
{
    switch( nSvxValue )
    {
        case SvxFileFormat::NameAndExt:    return text::FilenameDisplayFormat::NAME_AND_EXT;
        case SvxFileFormat::PathFull:    return text::FilenameDisplayFormat::FULL;
        case SvxFileFormat::PathOnly:        return text::FilenameDisplayFormat::PATH;
        default:
            return text::FilenameDisplayFormat::NAME;
    }
}
 
}
 
SC_SIMPLE_SERVICE_INFO( ScCellFieldsObj, u"ScCellFieldsObj"_ustr, u"com.sun.star.text.TextFields"_ustr )
SC_SIMPLE_SERVICE_INFO( ScHeaderFieldsObj, u"ScHeaderFieldsObj"_ustr, u"com.sun.star.text.TextFields"_ustr )
 
namespace {
 
enum ScUnoCollectMode
{
    SC_UNO_COLLECT_NONE,
    SC_UNO_COLLECT_COUNT,
    SC_UNO_COLLECT_FINDINDEX,
    SC_UNO_COLLECT_FINDPOS
};
 
/**
 * This class exists solely to allow searching through field items.  TODO:
 * Look into providing the same functionality directly in EditEngine, to
 * avoid having this class altogether.
 */
class ScUnoEditEngine : public ScEditEngineDefaulter
{
    ScUnoCollectMode    eMode;
    sal_uInt16          nFieldCount;
    sal_Int32           mnFieldType;
    std::unique_ptr<SvxFieldData>
                        pFound;         // local copy
    sal_Int32           nFieldPar;
    sal_Int32           nFieldPos;
    sal_uInt16          nFieldIndex;
 
public:
    explicit ScUnoEditEngine(ScEditEngineDefaulter* pSource);
 
    virtual OUString  CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos,
                                   std::optional<Color>& rTxtColor, std::optional<Color>& rFldColor, std::optional<FontLineStyle>& rFldLineStyle ) override;
 
    sal_uInt16 CountFields();
    SvxFieldData* FindByIndex(sal_uInt16 nIndex);
    SvxFieldData* FindByPos(sal_Int32 nPar, sal_Int32 nPos, sal_Int32 nType);
 
    sal_Int32       GetFieldPar() const     { return nFieldPar; }
    sal_Int32       GetFieldPos() const     { return nFieldPos; }
};
 
}
 
ScUnoEditEngine::ScUnoEditEngine(ScEditEngineDefaulter* pSource)
    : ScEditEngineDefaulter(*pSource)
    , eMode(SC_UNO_COLLECT_NONE)
    , nFieldCount(0)
    , mnFieldType(text::textfield::Type::UNSPECIFIED)
    , nFieldPar(0)
    , nFieldPos(0)
    , nFieldIndex(0)
{
    std::unique_ptr<EditTextObject> pData = pSource->CreateTextObject();
    SetTextCurrentDefaults( *pData );
}
 
OUString ScUnoEditEngine::CalcFieldValue( const SvxFieldItem& rField,
            sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rTxtColor, std::optional<Color>& rFldColor, std::optional<FontLineStyle>& rFldLineStyle )
{
    OUString aRet(EditEngine::CalcFieldValue( rField, nPara, nPos, rTxtColor, rFldColor, rFldLineStyle ));
    if (eMode != SC_UNO_COLLECT_NONE)
    {
        const SvxFieldData* pFieldData = rField.GetField();
        if ( pFieldData )
        {
            if (mnFieldType == text::textfield::Type::UNSPECIFIED || pFieldData->GetClassId() == mnFieldType)
            {
                if ( eMode == SC_UNO_COLLECT_FINDINDEX && !pFound && nFieldCount == nFieldIndex )
                {
                    pFound = pFieldData->Clone();
                    nFieldPar = nPara;
                    nFieldPos = nPos;
                }
                if ( eMode == SC_UNO_COLLECT_FINDPOS && !pFound &&
                        nPara == nFieldPar && nPos == nFieldPos )
                {
                    pFound = pFieldData->Clone();
                    nFieldIndex = nFieldCount;
                }
                ++nFieldCount;
            }
        }
    }
    return aRet;
}
 
sal_uInt16 ScUnoEditEngine::CountFields()
{
    eMode = SC_UNO_COLLECT_COUNT;
    mnFieldType = text::textfield::Type::UNSPECIFIED;
    nFieldCount = 0;
    UpdateFields();
    eMode = SC_UNO_COLLECT_NONE;
 
    return nFieldCount;
}
 
SvxFieldData* ScUnoEditEngine::FindByIndex(sal_uInt16 nIndex)
{
    eMode = SC_UNO_COLLECT_FINDINDEX;
    nFieldIndex = nIndex;
    mnFieldType = text::textfield::Type::UNSPECIFIED;
    nFieldCount = 0;
    UpdateFields();
    eMode = SC_UNO_COLLECT_NONE;
 
    return pFound.get();
}
 
SvxFieldData* ScUnoEditEngine::FindByPos(sal_Int32 nPar, sal_Int32 nPos, sal_Int32 nType)
{
    eMode = SC_UNO_COLLECT_FINDPOS;
    nFieldPar = nPar;
    nFieldPos = nPos;
    mnFieldType = nType;
    nFieldCount = 0;
    UpdateFields();
    mnFieldType = text::textfield::Type::UNSPECIFIED;
    eMode = SC_UNO_COLLECT_NONE;
 
    return pFound.get();
}
 
ScCellFieldsObj::ScCellFieldsObj(
    uno::Reference<text::XTextRange> xContent,
    ScDocShell* pDocSh, const ScAddress& rPos) :
    mxContent(std::move(xContent)),
    pDocShell( pDocSh ),
    aCellPos( rPos )
{
    pDocShell->GetDocument().AddUnoObject(*this);
 
    mpEditSource.reset( new ScCellEditSource( pDocShell, aCellPos ) );
}
 
ScCellFieldsObj::~ScCellFieldsObj()
{
    {
        SolarMutexGuard g;
 
        if (pDocShell)
            pDocShell->GetDocument().RemoveUnoObject(*this);
 
        mpEditSource.reset();
    }
 
    // increment refcount to prevent double call off dtor
    osl_atomic_increment( &m_refCount );
 
    std::unique_lock g(aMutex);
    if (maRefreshListeners.getLength(g))
    {
        lang::EventObject aEvent;
        aEvent.Source.set(getXWeak());
        maRefreshListeners.disposeAndClear(g, aEvent);
    }
}
 
void ScCellFieldsObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    if ( rHint.GetId() == SfxHintId::ScUpdateRef )
    {
        //! update of references
    }
    else if ( rHint.GetId() == SfxHintId::Dying )
    {
        pDocShell = nullptr;       // became invalid
    }
 
    //  EditSource registered itself as a listener
}
 
// XIndexAccess (via XTextFields)
 
uno::Reference<text::XTextField> ScCellFieldsObj::GetObjectByIndex_Impl(sal_Int32 Index) const
{
    //! Field functions have to be passed to the forwarder !!!
    ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
    ScUnoEditEngine aTempEngine(pEditEngine);
    SvxFieldData* pData = aTempEngine.FindByIndex(static_cast<sal_uInt16>(Index));
    if (!pData)
        return uno::Reference<text::XTextField>();
 
    sal_Int32 nPar = aTempEngine.GetFieldPar();
    sal_Int32 nPos = aTempEngine.GetFieldPos();
    ESelection aSelection( nPar, nPos, nPar, nPos+1 );      // Field size is 1 character
 
    sal_Int32 eType = pData->GetClassId();
    uno::Reference<text::XTextField> xRet(
        new ScEditFieldObj(mxContent, std::make_unique<ScCellEditSource>(pDocShell, aCellPos), eType, aSelection));
    return xRet;
}
 
sal_Int32 SAL_CALL ScCellFieldsObj::getCount()
{
    SolarMutexGuard aGuard;
 
    //! Field functions have to be passed to the forwarder !!!
    ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
    ScUnoEditEngine aTempEngine(pEditEngine);
 
    return aTempEngine.CountFields();       // count the fields, we don't care about their type in the cell
}
 
uno::Any SAL_CALL ScCellFieldsObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    uno::Reference<text::XTextField> xField(GetObjectByIndex_Impl(nIndex));
    if (!xField.is())
        throw lang::IndexOutOfBoundsException();
 
    return uno::Any(xField);
}
 
uno::Type SAL_CALL ScCellFieldsObj::getElementType()
{
    return cppu::UnoType<text::XTextField>::get();
}
 
sal_Bool SAL_CALL ScCellFieldsObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
uno::Reference<container::XEnumeration> SAL_CALL ScCellFieldsObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.text.TextFieldEnumeration"_ustr);
}
 
void SAL_CALL ScCellFieldsObj::addContainerListener(
                                const uno::Reference<container::XContainerListener>& /* xListener */ )
{
    OSL_FAIL("not implemented");
}
 
void SAL_CALL ScCellFieldsObj::removeContainerListener(
                                const uno::Reference<container::XContainerListener>& /* xListener */ )
{
    OSL_FAIL("not implemented");
}
 
// XRefreshable
void SAL_CALL ScCellFieldsObj::refresh(  )
{
    std::unique_lock g(aMutex);
    if (maRefreshListeners.getLength(g))
    {
        //  Call all listeners.
        lang::EventObject aEvent;
        aEvent.Source.set(uno::Reference< util::XRefreshable >(this));
        maRefreshListeners.notifyEach( g, &util::XRefreshListener::refreshed, aEvent );
    }
}
 
void SAL_CALL ScCellFieldsObj::addRefreshListener( const uno::Reference< util::XRefreshListener >& xListener )
{
    if (xListener.is())
    {
        std::unique_lock g(aMutex);
        maRefreshListeners.addInterface(g, xListener);
    }
}
 
void SAL_CALL ScCellFieldsObj::removeRefreshListener( const uno::Reference<util::XRefreshListener >& xListener )
{
    if (xListener.is())
    {
        std::unique_lock g(aMutex);
        maRefreshListeners.removeInterface(g, xListener);
    }
}
 
ScHeaderFieldsObj::ScHeaderFieldsObj(ScHeaderFooterTextData& rData) :
    mrData(rData)
{
    mpEditSource.reset( new ScHeaderFooterEditSource(rData) );
}
 
ScHeaderFieldsObj::~ScHeaderFieldsObj()
{
    mpEditSource.reset();
 
    // increment refcount to prevent double call of dtor
    osl_atomic_increment( &m_refCount );
 
    std::unique_lock g(aMutex);
    if (maRefreshListeners.getLength(g))
    {
        lang::EventObject aEvent;
        aEvent.Source = getXWeak();
        maRefreshListeners.disposeAndClear(g, aEvent);
    }
}
 
// XIndexAccess (via XTextFields)
 
uno::Reference<text::XTextField> ScHeaderFieldsObj::GetObjectByIndex_Impl(sal_Int32 Index) const
{
    //! Field functions have to be passed to the forwarder !!!
    ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
    ScUnoEditEngine aTempEngine(pEditEngine);
 
    SvxFieldData* pData = aTempEngine.FindByIndex(static_cast<sal_uInt16>(Index));
    if (!pData)
        return nullptr;
 
    // Get the parent text range instance.
    uno::Reference<text::XTextRange> xTextRange;
    rtl::Reference<ScHeaderFooterContentObj> xContentObj = mrData.GetContentObj();
    if (!xContentObj.is())
        throw uno::RuntimeException(u""_ustr);
 
    uno::Reference<text::XText> xText;
 
    switch ( mrData.GetPart() )
    {
        case ScHeaderFooterPart::LEFT:
            xText = xContentObj->getLeftText();
        break;
        case ScHeaderFooterPart::CENTER:
            xText = xContentObj->getCenterText();
        break;
        case ScHeaderFooterPart::RIGHT:
            xText = xContentObj->getRightText();
        break;
    }
 
    xTextRange = xText;
 
    sal_Int32 nPar = aTempEngine.GetFieldPar();
    sal_Int32 nPos = aTempEngine.GetFieldPos();
    ESelection aSelection( nPar, nPos, nPar, nPos+1 );      // Field size is 1 character
 
    sal_Int32 eRealType = pData->GetClassId();
    uno::Reference<text::XTextField> xRet(
        new ScEditFieldObj(xTextRange, std::make_unique<ScHeaderFooterEditSource>(mrData), eRealType, aSelection));
    return xRet;
}
 
sal_Int32 SAL_CALL ScHeaderFieldsObj::getCount()
{
    SolarMutexGuard aGuard;
 
    //! Field functions have to be passed to the forwarder !!!
    ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
    ScUnoEditEngine aTempEngine(pEditEngine);
    return aTempEngine.CountFields();
}
 
uno::Any SAL_CALL ScHeaderFieldsObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    uno::Reference<text::XTextField> xField(GetObjectByIndex_Impl(nIndex));
    if (!xField.is())
        throw lang::IndexOutOfBoundsException();
 
    return uno::Any(xField);
}
 
uno::Type SAL_CALL ScHeaderFieldsObj::getElementType()
{
    return cppu::UnoType<text::XTextField>::get();
}
 
sal_Bool SAL_CALL ScHeaderFieldsObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
uno::Reference<container::XEnumeration> SAL_CALL ScHeaderFieldsObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.text.TextFieldEnumeration"_ustr);
}
 
void SAL_CALL ScHeaderFieldsObj::addContainerListener(
                                const uno::Reference<container::XContainerListener>& /* xListener */ )
{
    OSL_FAIL("not implemented");
}
 
void SAL_CALL ScHeaderFieldsObj::removeContainerListener(
                                const uno::Reference<container::XContainerListener>& /* xListener */ )
{
    OSL_FAIL("not implemented");
}
 
// XRefreshable
void SAL_CALL ScHeaderFieldsObj::refresh(  )
{
    std::unique_lock g(aMutex);
    if (maRefreshListeners.getLength(g))
    {
        //  Call all listeners.
        lang::EventObject aEvent;
        aEvent.Source.set(uno::Reference< util::XRefreshable >(this));
        maRefreshListeners.notifyEach( g, &util::XRefreshListener::refreshed, aEvent);
    }
}
 
void SAL_CALL ScHeaderFieldsObj::addRefreshListener( const uno::Reference< util::XRefreshListener >& xListener )
{
    if (xListener.is())
    {
        std::unique_lock g(aMutex);
        maRefreshListeners.addInterface(g, xListener);
    }
}
 
void SAL_CALL ScHeaderFieldsObj::removeRefreshListener( const uno::Reference<util::XRefreshListener >& xListener )
{
    if (xListener.is())
    {
        std::unique_lock g(aMutex);
        maRefreshListeners.removeInterface(g, xListener);
    }
}
 
SvxFieldData& ScEditFieldObj::getData()
{
    if (!mpData)
    {
        switch (meType)
        {
            case text::textfield::Type::DATE:
                mpData.reset(new SvxDateField);
            break;
            case text::textfield::Type::EXTENDED_FILE:
                mpData.reset(
                    new SvxExtFileField(OUString(), SvxFileType::Var, SvxFileFormat::NameAndExt));
            break;
            case text::textfield::Type::PAGE:
                mpData.reset(new SvxPageField);
            break;
            case text::textfield::Type::PAGES:
                mpData.reset(new SvxPagesField);
            break;
            case text::textfield::Type::TABLE:
                mpData.reset(new SvxTableField);
            break;
            case text::textfield::Type::TIME:
                mpData.reset(new SvxTimeField);
            break;
            case text::textfield::Type::EXTENDED_TIME:
            {
                if (mbIsDate)
                    mpData.reset(new SvxDateField);
                else
                    mpData.reset(new SvxExtTimeField);
            }
            break;
            case text::textfield::Type::DOCINFO_TITLE:
                mpData.reset(new SvxFileField);
            break;
            case text::textfield::Type::URL:
                mpData.reset(
                    new SvxURLField(OUString(), OUString(), SvxURLFormat::AppDefault));
            break;
            default:
                mpData.reset(new SvxFieldData);
        }
    }
    return *mpData;
}
 
void ScEditFieldObj::setPropertyValueURL(const OUString& rName, const css::uno::Any& rVal)
{
    OUString aStrVal;
    if (mpEditSource)
    {
        // Edit engine instance already exists for this field item.  Use it.
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
 
        //  don't care about the type (only URLs can be found in the cells)
        SvxFieldData* pField = aTempEngine.FindByPos(
            aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::UNSPECIFIED);
        OSL_ENSURE(pField,"setPropertyValue: Field not found");
        if (!pField)
            return;
 
        if (pField->GetClassId() != text::textfield::Type::URL)
            // Make sure this is indeed a URL field.
            return;
 
        SvxURLField* pURL = static_cast<SvxURLField*>(pField);
 
        if (rName == SC_UNONAME_URL)
        {
            if (rVal >>= aStrVal)
                pURL->SetURL(aStrVal);
        }
        else if (rName == SC_UNONAME_REPR)
        {
            if (rVal >>= aStrVal)
                pURL->SetRepresentation(aStrVal);
        }
        else if (rName == SC_UNONAME_TARGET)
        {
            if (rVal >>= aStrVal)
                pURL->SetTargetFrame(aStrVal);
        }
        else
            throw beans::UnknownPropertyException(rName);
 
        pEditEngine->QuickInsertField( SvxFieldItem(*pField, EE_FEATURE_FIELD), aSelection );
        mpEditSource->UpdateData();
        return;
    }
 
    // Edit engine instance not yet present.  Store the item data for later use.
    SvxURLField& rData = static_cast<SvxURLField&>(getData());
    if (rName == SC_UNONAME_URL)
    {
        if (rVal >>= aStrVal)
            rData.SetURL(aStrVal);
    }
    else if (rName == SC_UNONAME_REPR)
    {
        if (rVal >>= aStrVal)
            rData.SetRepresentation(aStrVal);
    }
    else if (rName == SC_UNONAME_TARGET)
    {
        if (rVal >>= aStrVal)
            rData.SetTargetFrame(aStrVal);
    }
    else
        throw beans::UnknownPropertyException(rName);
}
 
uno::Any ScEditFieldObj::getPropertyValueURL(const OUString& rName)
{
    uno::Any aRet;
 
    // anchor type is always "as character", text wrap always "none"
 
    if (mpEditSource)
    {
        //! Field functions have to be passed to the forwarder !!!
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
 
        //  don't care about the type (only URLs can be found in the cells)
        const SvxFieldData* pField = aTempEngine.FindByPos(
            aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::UNSPECIFIED);
        OSL_ENSURE(pField,"getPropertyValue: Field not found");
        if (!pField)
            throw uno::RuntimeException();
 
        if (pField->GetClassId() != text::textfield::Type::URL)
            throw uno::RuntimeException();
 
        const SvxURLField* pURL = static_cast<const SvxURLField*>(pField);
 
        if (rName == SC_UNONAME_URL)
            aRet <<= pURL->GetURL();
        else if (rName == SC_UNONAME_REPR)
            aRet <<= pURL->GetRepresentation();
        else if (rName == SC_UNONAME_TARGET)
            aRet <<= pURL->GetTargetFrame();
        else
            throw beans::UnknownPropertyException(rName);
    }
    else        // not inserted yet
    {
        const SvxURLField& rURL = static_cast<const SvxURLField&>(getData());
 
        if (rName == SC_UNONAME_URL)
            aRet <<= rURL.GetURL();
        else if (rName == SC_UNONAME_REPR)
            aRet <<= rURL.GetRepresentation();
        else if (rName == SC_UNONAME_TARGET)
            aRet <<= rURL.GetTargetFrame();
        else
            throw beans::UnknownPropertyException(rName);
    }
    return aRet;
}
 
void ScEditFieldObj::setPropertyValueFile(const OUString& rName, const uno::Any& rVal)
{
    if (rName != SC_UNONAME_FILEFORM)
        throw beans::UnknownPropertyException(rName);
 
    sal_Int16 nIntVal = 0;
    if (!(rVal >>= nIntVal))
        return;
 
    SvxFileFormat eFormat = lcl_UnoToSvxFileFormat(nIntVal);
    if (mpEditSource)
    {
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
        SvxFieldData* pField = aTempEngine.FindByPos(
                aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::EXTENDED_FILE);
        OSL_ENSURE(pField, "setPropertyValueFile: Field not found");
        if (pField)
        {
            SvxExtFileField* pExtFile = static_cast<SvxExtFileField*>(pField);   // local to the ScUnoEditEngine
            pExtFile->SetFormat(eFormat);
            pEditEngine->QuickInsertField(SvxFieldItem(*pField, EE_FEATURE_FIELD), aSelection);
            mpEditSource->UpdateData();
        }
    }
    else
    {
        SvxExtFileField& rExtFile = static_cast<SvxExtFileField&>(getData());
        rExtFile.SetFormat(eFormat);
    }
 
}
 
uno::Any ScEditFieldObj::getPropertyValueFile(const OUString& rName)
{
    uno::Any aRet;
    if (rName != SC_UNONAME_FILEFORM)
        throw beans::UnknownPropertyException(rName);
 
    SvxFileFormat eFormat = SvxFileFormat::NameAndExt;
    const SvxFieldData* pField = nullptr;
    if (mpEditSource)
    {
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
        pField = aTempEngine.FindByPos(
            aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::EXTENDED_FILE);
    }
    else
        pField = &getData();
 
    OSL_ENSURE(pField, "setPropertyValueFile: Field not found");
    if (!pField)
        throw uno::RuntimeException();
 
    const SvxExtFileField* pExtFile = static_cast<const SvxExtFileField*>(pField);
    eFormat = pExtFile->GetFormat();
    sal_Int16 nIntVal = lcl_SvxToUnoFileFormat(eFormat);
    aRet <<= nIntVal;
 
    return aRet;
}
 
void ScEditFieldObj::setPropertyValueDateTime(const OUString& rName, const uno::Any& rVal)
{
    if (mpEditSource)
    {
        // Field already inserted.
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
        SvxFieldData* pField = aTempEngine.FindByPos(aSelection.nStartPara, aSelection.nStartPos, meType);
        if (!pField)
            return;
 
        switch (meType)
        {
            case text::textfield::Type::DATE:
            {
                SvxDateField* p = static_cast<SvxDateField*>(pField);
                if (rName == SC_UNONAME_ISDATE)
                {
                    // Do nothing for now.
                }
                else if (rName == SC_UNONAME_ISFIXED)
                {
                    SvxDateType eType = rVal.get<bool>() ? SvxDateType::Fix : SvxDateType::Var;
                    p->SetType(eType);
                }
                else if (rName == SC_UNONAME_DATETIME)
                {
                    maDateTime = rVal.get<util::DateTime>();
                    Date aDate(maDateTime.Day, maDateTime.Month, maDateTime.Year);
                    p->SetFixDate(aDate);
                }
                else if (rName == SC_UNONAME_NUMFMT)
                {
                    mnNumFormat = rVal.get<sal_Int32>();
                    p->SetFormat(static_cast<SvxDateFormat>(mnNumFormat));
                }
                else
                    throw beans::UnknownPropertyException(rName);
            }
            break;
            case text::textfield::Type::TIME:
            {
                // SvxTimeField doesn't have any attributes.
                if (rName != SC_UNONAME_ISDATE && rName != SC_UNONAME_ISFIXED &&
                    rName != SC_UNONAME_DATETIME && rName != SC_UNONAME_NUMFMT)
                    throw beans::UnknownPropertyException(rName);
            }
            break;
            case text::textfield::Type::EXTENDED_TIME:
            {
                SvxExtTimeField* p = static_cast<SvxExtTimeField*>(pField);
                if (rName == SC_UNONAME_ISDATE)
                {
                    // Do nothing for now.
                }
                else if (rName == SC_UNONAME_ISFIXED)
                {
                    SvxTimeType eType = rVal.get<bool>() ? SvxTimeType::Fix : SvxTimeType::Var;
                    p->SetType(eType);
                }
                else if (rName == SC_UNONAME_DATETIME)
                {
                    maDateTime = rVal.get<util::DateTime>();
                    tools::Time aTime(maDateTime);
                    p->SetFixTime(aTime);
                }
                else if (rName == SC_UNONAME_NUMFMT)
                {
                    mnNumFormat = rVal.get<sal_Int32>();
                    p->SetFormat(static_cast<SvxTimeFormat>(mnNumFormat));
                }
                else
                    throw beans::UnknownPropertyException(rName);
            }
            break;
            default:
                throw beans::UnknownPropertyException(rName);
        }
    }
    else
    {
        if (rName == SC_UNONAME_ISDATE)
            mbIsDate = rVal.get<bool>();
        else if (rName == SC_UNONAME_ISFIXED)
            mbIsFixed = rVal.get<bool>();
        else if (rName == SC_UNONAME_DATETIME)
            maDateTime = rVal.get<util::DateTime>();
        else if (rName == SC_UNONAME_NUMFMT)
            mnNumFormat = rVal.get<sal_Int32>();
        else
            throw beans::UnknownPropertyException(rName);
    }
}
 
uno::Any ScEditFieldObj::getPropertyValueDateTime(const OUString& rName)
{
    if (mpEditSource)
    {
        // Field already inserted.
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
        SvxFieldData* pField = aTempEngine.FindByPos(aSelection.nStartPara, aSelection.nStartPos, meType);
        if (!pField)
            throw uno::RuntimeException();
 
        switch (meType)
        {
            case text::textfield::Type::DATE:
            {
                SvxDateField* p = static_cast<SvxDateField*>(pField);
                if (rName == SC_UNONAME_ISDATE)
                    return uno::Any(true);
 
                if (rName == SC_UNONAME_ISFIXED)
                    return uno::Any(p->GetType() == SvxDateType::Fix);
 
                if (rName == SC_UNONAME_DATETIME)
                {
                    Date aD(p->GetFixDate());
                    maDateTime.Year = aD.GetYear();
                    maDateTime.Month = aD.GetMonth();
                    maDateTime.Day = aD.GetDay();
                    maDateTime.Hours = 0;
                    maDateTime.Minutes = 0;
                    maDateTime.Seconds = 0;
                    maDateTime.NanoSeconds = 0;
                    return uno::Any(maDateTime);
                }
 
                if (rName == SC_UNONAME_NUMFMT)
                    return uno::Any(static_cast<sal_Int32>(p->GetFormat()));
            }
            break;
            case text::textfield::Type::TIME:
            {
                // SvxTimeField doesn't have any attributes.
                if (rName == SC_UNONAME_ISDATE)
                    return uno::Any(false);
 
                if (rName == SC_UNONAME_ISFIXED)
                    return uno::Any(false);
 
                if (rName == SC_UNONAME_DATETIME)
                    // This is the best we can do.
                    return uno::Any(maDateTime);
 
                if (rName == SC_UNONAME_NUMFMT)
                    // Same as above.
                    return uno::Any(sal_Int32(0));
            }
            break;
            case text::textfield::Type::EXTENDED_TIME:
            {
                SvxExtTimeField* p = static_cast<SvxExtTimeField*>(pField);
                if (rName == SC_UNONAME_ISDATE)
                    return uno::Any(false);
 
                if (rName == SC_UNONAME_ISFIXED)
                    return uno::Any(p->GetType() == SvxTimeType::Fix);
 
                if (rName == SC_UNONAME_DATETIME)
                {
                    tools::Time aT(tools::Time::fromEncodedTime(p->GetFixTime()));
                    maDateTime.Year = 0;
                    maDateTime.Month = 0;
                    maDateTime.Day = 0;
                    maDateTime.Hours = aT.GetHour();
                    maDateTime.Minutes = aT.GetMin();
                    maDateTime.Seconds = aT.GetSec();
                    maDateTime.NanoSeconds = aT.GetNanoSec();
                    return uno::Any(maDateTime);
                }
 
                if (rName == SC_UNONAME_NUMFMT)
                    return uno::Any(static_cast<sal_Int32>(p->GetFormat()));
            }
            break;
            default:
                ;
        }
    }
    else
    {
        if (rName == SC_UNONAME_ISDATE)
            return uno::Any(mbIsDate);
 
        if (rName == SC_UNONAME_ISFIXED)
            return uno::Any(mbIsFixed);
 
        if (rName == SC_UNONAME_DATETIME)
            return uno::Any(maDateTime);
 
        if (rName == SC_UNONAME_NUMFMT)
            return uno::Any(mnNumFormat);
    }
 
    throw beans::UnknownPropertyException(rName);
}
 
void ScEditFieldObj::setPropertyValueSheet(const OUString& rName, const uno::Any& rVal)
{
    if (mpEditSource)
    {
        // Edit engine instance already exists for this field item.  Use it.
        ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
        ScUnoEditEngine aTempEngine(pEditEngine);
 
        //  don't care about the type (only URLs can be found in the cells)
        SvxFieldData* pField = aTempEngine.FindByPos(
            aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::UNSPECIFIED);
        OSL_ENSURE(pField,"setPropertyValue: Field not found");
        if (!pField)
            return;
 
        if (pField->GetClassId() != text::textfield::Type::TABLE)
            // Make sure this is indeed a URL field.
            return;
 
        SvxTableField* p = static_cast<SvxTableField*>(pField);
 
        if (rName != SC_UNONAME_TABLEPOS)
            throw beans::UnknownPropertyException(rName);
 
        sal_Int32 nTab = rVal.get<sal_Int32>();
        p->SetTab(nTab);
 
 
        pEditEngine->QuickInsertField(SvxFieldItem(*pField, EE_FEATURE_FIELD), aSelection);
        mpEditSource->UpdateData();
        return;
    }
 
    // Edit engine instance not yet present.  Store the item data for later use.
    SvxTableField& r = static_cast<SvxTableField&>(getData());
    if (rName != SC_UNONAME_TABLEPOS)
        throw beans::UnknownPropertyException(rName);
 
    sal_Int32 nTab = rVal.get<sal_Int32>();
    r.SetTab(nTab);
}
 
ScEditFieldObj::ScEditFieldObj(
    uno::Reference<text::XTextRange> xContent,
    std::unique_ptr<ScEditSource> pEditSrc, sal_Int32 eType, const ESelection& rSel) :
    pPropSet(nullptr),
    mpEditSource(std::move(pEditSrc)),
    aSelection(rSel),
    meType(eType), mpContent(std::move(xContent)), mnNumFormat(0), mbIsDate(false), mbIsFixed(false)
{
    switch (meType)
    {
        case text::textfield::Type::DOCINFO_TITLE:
            pPropSet = getEmptyPropertySet();
        break;
        case text::textfield::Type::EXTENDED_FILE:
            pPropSet = lcl_GetFileFieldPropertySet();
        break;
        case text::textfield::Type::URL:
            pPropSet = lcl_GetURLPropertySet();
        break;
        case text::textfield::Type::DATE:
        case text::textfield::Type::TIME:
        case text::textfield::Type::EXTENDED_TIME:
            pPropSet = getDateTimePropertySet();
        break;
        default:
            pPropSet = lcl_GetHeaderFieldPropertySet();
    }
 
    if (meType == text::textfield::Type::DATE)
        mbIsDate = true;
}
 
void ScEditFieldObj::InitDoc(
    const uno::Reference<text::XTextRange>& rContent, std::unique_ptr<ScEditSource> pEditSrc, const ESelection& rSel)
{
    if (!mpEditSource)
    {
        mpContent = rContent;
        mpData.reset();
 
        aSelection = rSel;
        mpEditSource = std::move( pEditSrc );
    }
}
 
ScEditFieldObj::~ScEditFieldObj()
{
}
 
SvxFieldItem ScEditFieldObj::CreateFieldItem()
{
    OSL_ENSURE( !mpEditSource, "CreateFieldItem with inserted field" );
    return SvxFieldItem(getData(), EE_FEATURE_FIELD);
}
 
void ScEditFieldObj::DeleteField()
{
    if (mpEditSource)
    {
        SvxTextForwarder* pForwarder = mpEditSource->GetTextForwarder();
        pForwarder->QuickInsertText( OUString(), aSelection );
        mpEditSource->UpdateData();
 
        aSelection.nEndPara = aSelection.nStartPara;
        aSelection.nEndPos  = aSelection.nStartPos;
 
        //! Broadcast in order to adjust selection in other objects
        //! (also for other actions)
    }
}
 
bool ScEditFieldObj::IsInserted() const
{
    return mpEditSource != nullptr;
}
 
// XTextField
 
OUString SAL_CALL ScEditFieldObj::getPresentation( sal_Bool bShowCommand )
{
    SolarMutexGuard aGuard;
 
    if (!mpEditSource)
        return OUString();
 
    //! Field functions have to be passed to the forwarder !!!
    ScEditEngineDefaulter* pEditEngine = mpEditSource->GetEditEngine();
    ScUnoEditEngine aTempEngine(pEditEngine);
 
    //  don't care about the type (only URLs can be found in the cells)
    const SvxFieldData* pField = aTempEngine.FindByPos(
        aSelection.nStartPara, aSelection.nStartPos, text::textfield::Type::UNSPECIFIED);
    OSL_ENSURE(pField,"getPresentation: Field not found");
    if (!pField)
        return OUString();
 
    switch (meType)
    {
        case text::textfield::Type::URL:
        {
            if (pField->GetClassId() != text::textfield::Type::URL)
                // Not a URL field, but URL is expected.
                throw uno::RuntimeException();
 
            const SvxURLField* pURL = static_cast<const SvxURLField*>(pField);
            return bShowCommand ? pURL->GetURL() : pURL->GetRepresentation();
        }
        break;
        default:
            ;
    }
    return OUString();
}
 
// XTextContent
 
void SAL_CALL ScEditFieldObj::attach( const uno::Reference<text::XTextRange>& xTextRange )
{
    SolarMutexGuard aGuard;
    if (xTextRange.is())
    {
        uno::Reference<text::XText> xText(xTextRange->getText());
        if (xText.is())
        {
            xText->insertTextContent( xTextRange, this, true );
        }
    }
}
 
uno::Reference<text::XTextRange> SAL_CALL ScEditFieldObj::getAnchor()
{
    SolarMutexGuard aGuard;
    return mpContent;
}
 
// XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScEditFieldObj::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    uno::Reference<beans::XPropertySetInfo> aRef = pPropSet->getPropertySetInfo();
    return aRef;
}
 
void SAL_CALL ScEditFieldObj::setPropertyValue(
                        const OUString& aPropertyName, const uno::Any& aValue )
{
    SolarMutexGuard aGuard;
    if (aPropertyName == SC_UNONAME_ANCHOR)
    {
        aValue >>= mpContent;
        return;
    }
 
    switch (meType)
    {
        case text::textfield::Type::URL:
            setPropertyValueURL(aPropertyName, aValue);
        break;
        case text::textfield::Type::EXTENDED_FILE:
            setPropertyValueFile(aPropertyName, aValue);
        break;
        case text::textfield::Type::DATE:
        case text::textfield::Type::TIME:
        case text::textfield::Type::EXTENDED_TIME:
            setPropertyValueDateTime(aPropertyName, aValue);
        break;
        case text::textfield::Type::TABLE:
            setPropertyValueSheet(aPropertyName, aValue);
        break;
        case text::textfield::Type::DOCINFO_TITLE:
        default:
            throw beans::UnknownPropertyException(OUString::number(meType));
    }
}
 
uno::Any SAL_CALL ScEditFieldObj::getPropertyValue( const OUString& aPropertyName )
{
    SolarMutexGuard aGuard;
    if (aPropertyName == SC_UNONAME_TEXTFIELD_TYPE)
        return uno::Any(meType);
 
    if (aPropertyName == SC_UNONAME_ANCHOR)
        return uno::Any(mpContent);
 
    if (aPropertyName == SC_UNONAME_ANCTYPE)
    {
        uno::Any aRet;
        aRet <<= text::TextContentAnchorType_AS_CHARACTER;
        return aRet;
    }
    if (aPropertyName == SC_UNONAME_ANCTYPES)
    {
        uno::Any aRet;
        uno::Sequence<text::TextContentAnchorType> aSeq { text::TextContentAnchorType_AS_CHARACTER };
        aRet <<= aSeq;
        return aRet;
    }
    if (aPropertyName == SC_UNONAME_TEXTWRAP)
    {
        uno::Any aRet;
        aRet <<= text::WrapTextMode_NONE;
        return aRet;
    }
 
    switch (meType)
    {
        case text::textfield::Type::URL:
            return getPropertyValueURL(aPropertyName);
        case text::textfield::Type::EXTENDED_FILE:
            return getPropertyValueFile(aPropertyName);
        case text::textfield::Type::DATE:
        case text::textfield::Type::TIME:
        case text::textfield::Type::EXTENDED_TIME:
            return getPropertyValueDateTime(aPropertyName);
        case text::textfield::Type::DOCINFO_TITLE:
        default:
            throw beans::UnknownPropertyException(OUString::number(meType));
    }
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScEditFieldObj )
 
// XServiceInfo
 
OUString SAL_CALL ScEditFieldObj::getImplementationName()
{
    return u"ScEditFieldObj"_ustr;
}
 
sal_Bool SAL_CALL ScEditFieldObj::supportsService( const OUString& rServiceName )
{
    return cppu::supportsService(this, rServiceName);
}
 
uno::Sequence<OUString> SAL_CALL ScEditFieldObj::getSupportedServiceNames()
{
    return {u"com.sun.star.text.TextField"_ustr,
            u"com.sun.star.text.TextContent"_ustr};
}
 
uno::Sequence<uno::Type> SAL_CALL ScEditFieldObj::getTypes()
{
    return comphelper::concatSequences(
        ScEditFieldObj_Base::getTypes(),
        uno::Sequence<uno::Type>
        {
            cppu::UnoType<text::XTextField>::get(),
            cppu::UnoType<beans::XPropertySet>::get(),
            cppu::UnoType<lang::XUnoTunnel>::get(),
            cppu::UnoType<lang::XServiceInfo>::get()
        } );
}
 
uno::Sequence<sal_Int8> SAL_CALL ScEditFieldObj::getImplementationId()
{
    return css::uno::Sequence<sal_Int8>();
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.

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