/* -*- 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 <fmprop.hxx>
#include <fmtextcontroldialogs.hxx>
#include <fmtextcontrolfeature.hxx>
#include <fmtextcontrolshell.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/scriptspaceitem.hxx>
#include <svx/svxids.hrc>
#include <editeng/udlnitem.hxx>
 
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/frame/XDispatchProvider.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/awt/XFocusListener.hpp>
#include <com/sun/star/awt/XMouseListener.hpp>
#include <com/sun/star/awt/XWindow.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
 
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <cppuhelper/implbase.hxx>
#include <sfx2/app.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/msgpool.hxx>
#include <sfx2/msg.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/request.hxx>
#include <sfx2/sfxuno.hxx>
#include <sfx2/viewfrm.hxx>
#include <svl/eitem.hxx>
#include <svl/itempool.hxx>
#include <svl/ctloptions.hxx>
#include <svtools/stringtransfer.hxx>
#include <svl/whiter.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
 
#include <memory>
 
 
namespace svx
{
 
 
    using namespace ::com::sun::star;
    using namespace ::com::sun::star::uno;
    using namespace ::com::sun::star::form;
    using namespace ::com::sun::star::form::runtime;
    using namespace ::com::sun::star::lang;
    using namespace ::com::sun::star::frame;
    using namespace ::com::sun::star::util;
    using namespace ::com::sun::star::beans;
 
 
    typedef sal_uInt16 WhichId;
 
 
    static SfxSlotId pTextControlSlots[] =
    {
        SID_CLIPBOARD_FORMAT_ITEMS,
        SID_CUT,
        SID_COPY,
        SID_PASTE,
        SID_SELECTALL,
//        SID_ATTR_TABSTOP,           /* 2 */
        SID_ATTR_CHAR_FONT,
        SID_ATTR_CHAR_POSTURE,
        SID_ATTR_CHAR_WEIGHT,
        SID_ATTR_CHAR_SHADOWED,
        SID_ATTR_CHAR_WORDLINEMODE,
        SID_ATTR_CHAR_CONTOUR,
        SID_ATTR_CHAR_STRIKEOUT,
        SID_ATTR_CHAR_UNDERLINE,
        SID_ATTR_CHAR_FONTHEIGHT,
        SID_ATTR_CHAR_COLOR,
        SID_ATTR_CHAR_KERNING,
        SID_ATTR_CHAR_LANGUAGE,     /* 20 */
        SID_ATTR_CHAR_ESCAPEMENT,
        SID_ATTR_PARA_ADJUST,       /* 28 */
        SID_ATTR_PARA_ADJUST_LEFT,
        SID_ATTR_PARA_ADJUST_RIGHT,
        SID_ATTR_PARA_ADJUST_CENTER,
        SID_ATTR_PARA_ADJUST_BLOCK,
        SID_ATTR_PARA_LINESPACE,    /* 33 */
        SID_ATTR_PARA_LINESPACE_10,
        SID_ATTR_PARA_LINESPACE_15,
        SID_ATTR_PARA_LINESPACE_20,
        SID_ATTR_LRSPACE,           /* 48 */
        SID_ATTR_ULSPACE,           /* 49 */
        SID_ATTR_CHAR_AUTOKERN,
        SID_SET_SUPER_SCRIPT,
        SID_SET_SUB_SCRIPT,
        SID_CHAR_DLG,
        SID_PARA_DLG,
//        SID_TEXTDIRECTION_LEFT_TO_RIGHT, /* 907 */
//        SID_TEXTDIRECTION_TOP_TO_BOTTOM,
        SID_ATTR_CHAR_SCALEWIDTH,       /* 911 */
        SID_ATTR_CHAR_RELIEF,
        SID_ATTR_PARA_LEFT_TO_RIGHT,    /* 950 */
        SID_ATTR_PARA_RIGHT_TO_LEFT,
        SID_ATTR_CHAR_OVERLINE,
        0
    };
 
    // slots which we are not responsible for on the SfxShell level, but
    // need to handle during the "paragraph attributes" and/or "character
    // attributes" dialogs
    static SfxSlotId pDialogSlots[] =
    {
        SID_ATTR_TABSTOP,
        SID_ATTR_PARA_HANGPUNCTUATION,
        SID_ATTR_PARA_FORBIDDEN_RULES,
        SID_ATTR_PARA_SCRIPTSPACE,
        SID_ATTR_CHAR_LATIN_LANGUAGE,
        SID_ATTR_CHAR_CJK_LANGUAGE,
        SID_ATTR_CHAR_CTL_LANGUAGE,
        SID_ATTR_CHAR_LATIN_FONT,
        SID_ATTR_CHAR_CJK_FONT,
        SID_ATTR_CHAR_CTL_FONT,
        SID_ATTR_CHAR_LATIN_FONTHEIGHT,
        SID_ATTR_CHAR_CJK_FONTHEIGHT,
        SID_ATTR_CHAR_CTL_FONTHEIGHT,
        SID_ATTR_CHAR_LATIN_WEIGHT,
        SID_ATTR_CHAR_CJK_WEIGHT,
        SID_ATTR_CHAR_CTL_WEIGHT,
        SID_ATTR_CHAR_LATIN_POSTURE,
        SID_ATTR_CHAR_CJK_POSTURE,
        SID_ATTR_CHAR_CTL_POSTURE,
        SID_ATTR_CHAR_EMPHASISMARK,
        0
    };
 
    typedef ::cppu::WeakImplHelper <   css::awt::XFocusListener
                                    >   FmFocusListenerAdapter_Base;
    class FmFocusListenerAdapter : public FmFocusListenerAdapter_Base
    {
    private:
        IFocusObserver*         m_pObserver;
        Reference< css::awt::XWindow >    m_xWindow;
 
    public:
        FmFocusListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IFocusObserver* _pObserver );
 
        // clean up the instance
        void    dispose();
 
    protected:
        virtual ~FmFocusListenerAdapter() override;
 
    protected:
        virtual void SAL_CALL focusGained( const css::awt::FocusEvent& e ) override;
        virtual void SAL_CALL focusLost( const css::awt::FocusEvent& e ) override;
        virtual void SAL_CALL disposing( const EventObject& Source ) override;
    };
 
 
    FmFocusListenerAdapter::FmFocusListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IFocusObserver* _pObserver )
        :m_pObserver( _pObserver )
        ,m_xWindow( _rxControl, UNO_QUERY )
    {
 
        DBG_ASSERT( m_xWindow.is(), "FmFocusListenerAdapter::FmFocusListenerAdapter: invalid control!" );
        osl_atomic_increment( &m_refCount );
        {
            try
            {
                if ( m_xWindow.is() )
                    m_xWindow->addFocusListener( this );
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
        }
        osl_atomic_decrement( &m_refCount );
    }
 
 
    FmFocusListenerAdapter::~FmFocusListenerAdapter()
    {
        acquire();
        dispose();
 
    }
 
 
    void FmFocusListenerAdapter::dispose()
    {
        if ( m_xWindow.is() )
        {
            m_xWindow->removeFocusListener( this );
            m_xWindow.clear();
        }
    }
 
 
    void SAL_CALL FmFocusListenerAdapter::focusGained( const css::awt::FocusEvent& e )
    {
        if ( m_pObserver )
            m_pObserver->focusGained( e );
    }
 
 
    void SAL_CALL FmFocusListenerAdapter::focusLost( const css::awt::FocusEvent& e )
    {
        if ( m_pObserver )
            m_pObserver->focusLost( e );
    }
 
 
    void SAL_CALL FmFocusListenerAdapter::disposing( const EventObject& Source )
    {
        DBG_ASSERT( Source.Source == m_xWindow, "FmFocusListenerAdapter::disposing: where did this come from?" );
        m_xWindow.clear();
    }
 
    typedef ::cppu::WeakImplHelper <   css::awt::XMouseListener
                                    >   FmMouseListenerAdapter_Base;
    class FmMouseListenerAdapter : public FmMouseListenerAdapter_Base
    {
    private:
        IContextRequestObserver*  m_pObserver;
        Reference< css::awt::XWindow >    m_xWindow;
 
    public:
        FmMouseListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IContextRequestObserver* _pObserver );
 
        // clean up the instance
        void    dispose();
 
    protected:
        virtual ~FmMouseListenerAdapter() override;
 
    protected:
        virtual void SAL_CALL mousePressed( const css::awt::MouseEvent& e ) override;
        virtual void SAL_CALL mouseReleased( const css::awt::MouseEvent& e ) override;
        virtual void SAL_CALL mouseEntered( const css::awt::MouseEvent& e ) override;
        virtual void SAL_CALL mouseExited( const css::awt::MouseEvent& e ) override;
        virtual void SAL_CALL disposing( const EventObject& Source ) override;
    };
 
    FmMouseListenerAdapter::FmMouseListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IContextRequestObserver* _pObserver )
        :m_pObserver( _pObserver )
        ,m_xWindow( _rxControl, UNO_QUERY )
    {
 
        DBG_ASSERT( m_xWindow.is(), "FmMouseListenerAdapter::FmMouseListenerAdapter: invalid control!" );
        osl_atomic_increment( &m_refCount );
        {
            try
            {
                if ( m_xWindow.is() )
                    m_xWindow->addMouseListener( this );
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
        }
        osl_atomic_decrement( &m_refCount );
    }
 
 
    FmMouseListenerAdapter::~FmMouseListenerAdapter()
    {
        acquire();
        dispose();
 
    }
 
 
    void FmMouseListenerAdapter::dispose()
    {
        if ( m_xWindow.is() )
        {
            m_xWindow->removeMouseListener( this );
            m_xWindow.clear();
        }
    }
 
 
    void SAL_CALL FmMouseListenerAdapter::mousePressed( const css::awt::MouseEvent& _rEvent )
    {
        SolarMutexGuard aGuard;
        // is this a request for a context menu?
        if ( _rEvent.PopupTrigger )
        {
            if ( m_pObserver )
                m_pObserver->contextMenuRequested();
        }
    }
 
 
    void SAL_CALL FmMouseListenerAdapter::mouseReleased( const css::awt::MouseEvent& /*e*/ )
    {
        // not interested in
    }
 
 
    void SAL_CALL FmMouseListenerAdapter::mouseEntered( const css::awt::MouseEvent& /*e*/ )
    {
        // not interested in
    }
 
 
    void SAL_CALL FmMouseListenerAdapter::mouseExited( const css::awt::MouseEvent& /*e*/ )
    {
        // not interested in
    }
 
 
    void SAL_CALL FmMouseListenerAdapter::disposing( const EventObject& Source )
    {
        DBG_ASSERT( Source.Source == m_xWindow, "FmMouseListenerAdapter::disposing: where did this come from?" );
        m_xWindow.clear();
    }
 
 
    //= FmTextControlShell
 
 
    namespace
    {
 
        void lcl_translateUnoStateToItem( SfxSlotId _nSlot, const Any& _rUnoState, SfxItemSet& _rSet )
        {
            WhichId nWhich = _rSet.GetPool()->GetWhichIDFromSlotID( _nSlot );
            if ( !_rUnoState.hasValue() )
            {
                if  ( ( _nSlot != SID_CUT )
                   && ( _nSlot != SID_COPY )
                   && ( _nSlot != SID_PASTE )
                    )
                {
                    _rSet.InvalidateItem( nWhich );
                }
            }
            else
            {
                switch ( _rUnoState.getValueTypeClass() )
                {
                case TypeClass_BOOLEAN:
                {
                    bool bState = false;
                    _rUnoState >>= bState;
                    if ( _nSlot == SID_ATTR_PARA_SCRIPTSPACE )
                        _rSet.Put( SvxScriptSpaceItem( bState, nWhich ) );
                    else
                        _rSet.Put( SfxBoolItem( nWhich, bState ) );
                }
                break;
 
                default:
                {
                    Sequence< PropertyValue > aComplexState;
                    if ( _rUnoState >>= aComplexState )
                    {
                        if ( !aComplexState.hasElements() )
                            _rSet.InvalidateItem( nWhich );
                        else
                        {
                            SfxAllItemSet aAllItems( _rSet );
                            TransformParameters( _nSlot, aComplexState, aAllItems );
                            const SfxPoolItem* pTransformed = aAllItems.GetItem( nWhich );
                            OSL_ENSURE( pTransformed, "lcl_translateUnoStateToItem: non-empty parameter sequence leading to empty item?" );
                            if ( pTransformed )
                                _rSet.Put( *pTransformed );
                            else
                                _rSet.InvalidateItem( nWhich );
                        }
                    }
                    else
                    {
                        OSL_FAIL( "lcl_translateUnoStateToItem: invalid state!" );
                    }
                }
                }
            }
        }
 
 
        OUString lcl_getUnoSlotName( SfxSlotId _nSlotId )
        {
            SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool();
            const SfxSlot* pSlot = rSlotPool.GetSlot( _nSlotId );
 
            OUString sUnoName;
            if ( pSlot )
            {
                sUnoName = pSlot->GetCommand();
            }
            else
            {
                // some hard-coded slots, which do not have a UNO name at SFX level, but which
                // we nevertheless need to transport via UNO mechanisms, so we need a name
                switch ( _nSlotId )
                {
                case SID_ATTR_PARA_HANGPUNCTUATION: sUnoName = ".uno:AllowHangingPunctuation"; break;
                case SID_ATTR_PARA_FORBIDDEN_RULES: sUnoName = ".uno:ApplyForbiddenCharacterRules"; break;
                case SID_ATTR_PARA_SCRIPTSPACE: sUnoName = ".uno:UseScriptSpacing"; break;
                }
            }
 
            if (sUnoName.isEmpty())
            {
                SAL_WARN( "svx", "lcl_getUnoSlotName: invalid slot id, or invalid slot, or no UNO name! "
                        "(slot id: " << _nSlotId << ")");
            }
            return sUnoName;
        }
 
 
        bool lcl_determineReadOnly( const Reference< css::awt::XControl >& _rxControl )
        {
            bool bIsReadOnlyModel = true;
            try
            {
                Reference< XPropertySet > xModelProps;
                if ( _rxControl.is() )
                    xModelProps.set(_rxControl->getModel(), css::uno::UNO_QUERY);
                Reference< XPropertySetInfo > xModelPropInfo;
                if ( xModelProps.is() )
                    xModelPropInfo = xModelProps->getPropertySetInfo();
 
                if ( !xModelPropInfo.is() || !xModelPropInfo->hasPropertyByName( FM_PROP_READONLY ) )
                    bIsReadOnlyModel = true;
                else
                {
                    bool bReadOnly = true;
                    xModelProps->getPropertyValue( FM_PROP_READONLY ) >>= bReadOnly;
                    bIsReadOnlyModel = bReadOnly;
                }
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
            return bIsReadOnlyModel;
        }
 
 
        vcl::Window* lcl_getWindow( const Reference< css::awt::XControl >& _rxControl )
        {
            vcl::Window* pWindow = nullptr;
            try
            {
                Reference< css::awt::XWindowPeer > xControlPeer;
                if ( _rxControl.is() )
                    xControlPeer = _rxControl->getPeer();
                if ( xControlPeer.is() )
                    pWindow = VCLUnoHelper::GetWindow( xControlPeer );
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
 
            return pWindow;
        }
 
 
        bool lcl_isRichText( const Reference< css::awt::XControl >& _rxControl )
        {
            if ( !_rxControl.is() )
                return false;
 
            bool bIsRichText = false;
            try
            {
                Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY );
                Reference< XPropertySetInfo > xPSI;
                if ( xModelProps.is() )
                    xPSI = xModelProps->getPropertySetInfo();
                OUString sRichTextPropertyName = u"RichText"_ustr;
                if ( xPSI.is() && xPSI->hasPropertyByName( sRichTextPropertyName ) )
                {
                    OSL_VERIFY( xModelProps->getPropertyValue( sRichTextPropertyName ) >>= bIsRichText );
                }
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
            return bIsRichText;
        }
    }
 
 
    FmTextControlShell::FmTextControlShell( SfxViewFrame* _pFrame )
        :m_bActiveControl( false )
        ,m_bActiveControlIsReadOnly( true )
        ,m_bActiveControlIsRichText( false )
        ,m_pViewFrame( _pFrame )
        ,m_rBindings( _pFrame->GetBindings() )
        ,m_aClipboardInvalidation("svx FmTextControlShell m_aClipboardInvalidation")
        ,m_bNeedClipboardInvalidation( true )
    {
        m_aClipboardInvalidation.SetInvokeHandler( LINK( this, FmTextControlShell, OnInvalidateClipboard ) );
        m_aClipboardInvalidation.SetTimeout( 200 );
    }
 
 
    FmTextControlShell::~FmTextControlShell()
    {
        dispose();
    }
 
 
    IMPL_LINK_NOARG( FmTextControlShell, OnInvalidateClipboard, Timer*, void )
    {
        if ( m_bNeedClipboardInvalidation )
        {
            SAL_INFO("svx.form", "invalidating clipboard slots" );
            m_rBindings.Invalidate( SID_CUT );
            m_rBindings.Invalidate( SID_COPY );
            m_rBindings.Invalidate( SID_PASTE );
            m_bNeedClipboardInvalidation = false;
        }
    }
 
 
    void FmTextControlShell::transferFeatureStatesToItemSet( ControlFeatures& _rDispatchers, SfxAllItemSet& _rSet, bool _bTranslateLatin )
    {
        SfxItemPool& rPool = *_rSet.GetPool();
 
        for (const auto& rFeature : _rDispatchers)
        {
            SfxSlotId nSlotId( rFeature.first );
#if OSL_DEBUG_LEVEL > 0
            OUString sUnoSlotName;
            if ( SfxGetpApp() )
                sUnoSlotName = lcl_getUnoSlotName( nSlotId );
            else
                sUnoSlotName = "<unknown>";
            OString sUnoSlotNameAscii = "\"" +
                OUStringToOString( sUnoSlotName, RTL_TEXTENCODING_ASCII_US ) +
                "\"";
#endif
 
            if ( _bTranslateLatin )
            {
                // A rich text control offers a dispatcher for the "Font" slot/feature.
                // Sadly, the semantics of the dispatches is that the feature "Font" depends
                // on the current cursor position: If it's on latin text, it's the "latin font"
                // which is set up at the control. If it's on CJK text, it's the "CJK font", and
                // equivalent for "CTL font".
                // The same holds for some other font related features/slots.
                // Thus, we have separate dispatches for "Latin Font", "Latin Font Size", etc,
                // which are only "virtual", in a sense that there exist no item with this id.
                // So when we encounter such a dispatcher for, say, "Latin Font", we need to
                // put an item into the set which has the "Font" id.
 
                switch ( nSlotId )
                {
                case SID_ATTR_CHAR_LATIN_FONT:      nSlotId = SID_ATTR_CHAR_FONT; break;
                case SID_ATTR_CHAR_LATIN_FONTHEIGHT:nSlotId = SID_ATTR_CHAR_FONTHEIGHT; break;
                case SID_ATTR_CHAR_LATIN_LANGUAGE:  nSlotId = SID_ATTR_CHAR_LANGUAGE; break;
                case SID_ATTR_CHAR_LATIN_POSTURE:   nSlotId = SID_ATTR_CHAR_POSTURE; break;
                case SID_ATTR_CHAR_LATIN_WEIGHT:    nSlotId = SID_ATTR_CHAR_WEIGHT; break;
                }
            }
 
            WhichId nWhich = rPool.GetWhichIDFromSlotID( nSlotId );
            bool bIsInPool = rPool.IsInRange( nWhich );
            if ( bIsInPool )
            {
#if OSL_DEBUG_LEVEL > 0
                bool bFeatureIsEnabled = rFeature.second->isFeatureEnabled();
                OString sMessage =  "found a feature state for "  + sUnoSlotNameAscii;
                if ( !bFeatureIsEnabled )
                    sMessage += " (disabled)";
                SAL_INFO("svx.form", sMessage );
#endif
 
                lcl_translateUnoStateToItem( nSlotId, rFeature.second->getFeatureState(), _rSet );
            }
#if OSL_DEBUG_LEVEL > 0
            else
            {
                SAL_WARN("svx.form", "found a feature state for " << sUnoSlotNameAscii << ", but could not translate it into an item!" );
            }
#endif
        }
    }
 
 
    void FmTextControlShell::executeAttributeDialog( AttributeSet _eSet, SfxRequest& rReq )
    {
        const SvxFontListItem* pFontList = dynamic_cast<const SvxFontListItem*>( m_pViewFrame->GetObjectShell()->GetItem( SID_ATTR_CHAR_FONTLIST )  );
        DBG_ASSERT( pFontList, "FmTextControlShell::executeAttributeDialog: no font list item!" );
        if ( !pFontList )
            return;
 
        rtl::Reference<SfxItemPool> pPool(EditEngine::CreatePool());
        std::optional< SfxItemSet > xPureItems(( SfxItemSet( *pPool ) ));
 
        // put the current states of the items into the set
        std::optional<SfxAllItemSet> xCurrentItems(( SfxAllItemSet( *xPureItems ) ));
        transferFeatureStatesToItemSet( m_aControlFeatures, *xCurrentItems, false );
 
        // additional items, which we are not responsible for at the SfxShell level,
        // but which need to be forwarded to the dialog, anyway
        ControlFeatures aAdditionalFestures;
        fillFeatureDispatchers( m_xActiveControl, pDialogSlots, aAdditionalFestures );
        transferFeatureStatesToItemSet( aAdditionalFestures, *xCurrentItems, true );
 
        std::unique_ptr<SfxTabDialogController> xDialog;
        if (_eSet == eCharAttribs)
            xDialog = std::make_unique<TextControlCharAttribDialog>(rReq.GetFrameWeld(), *xCurrentItems, *pFontList);
        else
            xDialog = std::make_unique<TextControlParaAttribDialog>(rReq.GetFrameWeld(), *xCurrentItems);
        if ( RET_OK == xDialog->run() )
        {
            const SfxItemSet& rModifiedItems = *xDialog->GetOutputItemSet();
            for ( WhichId nWhich = pPool->GetFirstWhich(); nWhich <= pPool->GetLastWhich(); ++nWhich )
            {
                const SfxPoolItem* pModifiedItem = nullptr;
                if ( rModifiedItems.GetItemState( nWhich, true, &pModifiedItem ) == SfxItemState::SET )
                {
                    SfxSlotId nSlotForItemSet = pPool->GetSlotId( nWhich );
 
                    SfxSlotId nSlotForDispatcher = nSlotForItemSet;
                    switch ( nSlotForDispatcher )
                    {
                        case SID_ATTR_CHAR_FONT:      nSlotForDispatcher = SID_ATTR_CHAR_LATIN_FONT; break;
                        case SID_ATTR_CHAR_FONTHEIGHT:nSlotForDispatcher = SID_ATTR_CHAR_LATIN_FONTHEIGHT; break;
                        case SID_ATTR_CHAR_LANGUAGE:  nSlotForDispatcher = SID_ATTR_CHAR_LATIN_LANGUAGE; break;
                        case SID_ATTR_CHAR_POSTURE:   nSlotForDispatcher = SID_ATTR_CHAR_LATIN_POSTURE; break;
                        case SID_ATTR_CHAR_WEIGHT:    nSlotForDispatcher = SID_ATTR_CHAR_LATIN_WEIGHT; break;
                    }
 
                    // do we already have a dispatcher for this slot/feature?
                    ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlotForDispatcher );
                    bool bFound = aFeaturePos != m_aControlFeatures.end( );
 
                    if ( !bFound )
                    {
                        aFeaturePos = aAdditionalFestures.find( nSlotForDispatcher );
                        bFound = aFeaturePos != aAdditionalFestures.end( );
                    }
 
                    if ( bFound )
                    {
                        Sequence< PropertyValue > aArgs;
                        // temporarily put the modified item into a "clean" set,
                        // and let TransformItems calc the respective UNO parameters
                        xPureItems->Put( *pModifiedItem );
                        TransformItems( nSlotForItemSet, *xPureItems, aArgs );
                        xPureItems->ClearItem( nWhich );
 
                        if  (   ( nSlotForItemSet == SID_ATTR_PARA_HANGPUNCTUATION )
                            ||  ( nSlotForItemSet == SID_ATTR_PARA_FORBIDDEN_RULES )
                            ||  ( nSlotForItemSet == SID_ATTR_PARA_SCRIPTSPACE )
                            )
                        {
                            // these are no UNO slots, they need special handling since TransformItems cannot
                            // handle them
                            DBG_ASSERT( !aArgs.hasElements(), "FmTextControlShell::executeAttributeDialog: these are no UNO slots - are they?" );
 
                            const SfxBoolItem* pBoolItem = dynamic_cast<const SfxBoolItem*>( pModifiedItem  );
                            DBG_ASSERT( pBoolItem, "FmTextControlShell::executeAttributeDialog: no bool item?!" );
                            if ( pBoolItem )
                            {
                                aArgs = { comphelper::makePropertyValue(u"Enable"_ustr,
                                                                        pBoolItem->GetValue()) };
                            }
                        }
 
                        // dispatch this
                        aFeaturePos->second->dispatch( aArgs );
                    }
                #if OSL_DEBUG_LEVEL > 0
                    else
                    {
                        OUString sUnoSlotName = lcl_getUnoSlotName( nSlotForItemSet );
                        if ( sUnoSlotName.isEmpty() )
                            sUnoSlotName = "unknown (no SfxSlot)";
                        SAL_WARN( "svx", "FmTextControShell::executeAttributeDialog: Could not handle the following item:"
                                "\n  SlotID: " << nSlotForItemSet
                                << "\n  WhichID: " << nWhich
                                << "\n  UNO name: " << sUnoSlotName );
                    }
                #endif
                }
            }
            rReq.Done( rModifiedItems );
        }
 
        xDialog.reset();
        xCurrentItems.reset();
        xPureItems.reset();
        pPool.clear();
    }
 
 
    void FmTextControlShell::executeSelectAll( )
    {
        try
        {
            if ( m_xActiveTextComponent.is() )
            {
                sal_Int32 nTextLen = m_xActiveTextComponent->getText().getLength();
                m_xActiveTextComponent->setSelection( css::awt::Selection( 0, nTextLen ) );
            }
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
 
 
    void FmTextControlShell::executeClipboardSlot( SfxSlotId _nSlot )
    {
        try
        {
            if ( m_xActiveTextComponent.is() )
            {
                switch ( _nSlot )
                {
                case SID_COPY:
                case SID_CUT:
                {
                    OUString sSelectedText( m_xActiveTextComponent->getSelectedText() );
                    ::svt::OStringTransfer::CopyString( sSelectedText, lcl_getWindow( m_xActiveControl ) );
                    if ( SID_CUT == _nSlot )
                    {
                        css::awt::Selection aSelection( m_xActiveTextComponent->getSelection() );
                        m_xActiveTextComponent->insertText( aSelection, OUString() );
                    }
                }
                break;
                case SID_PASTE:
                {
                    OUString sClipboardContent;
                    OSL_VERIFY( ::svt::OStringTransfer::PasteString( sClipboardContent, lcl_getWindow( m_xActiveControl ) ) );
                    css::awt::Selection aSelection( m_xActiveTextComponent->getSelection() );
                    m_xActiveTextComponent->insertText( aSelection, sClipboardContent );
                }
                break;
                default:
                    OSL_FAIL( "FmTextControlShell::executeClipboardSlot: invalid slot!" );
                }
            }
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
 
 
    void FmTextControlShell::ExecuteTextAttribute( SfxRequest& _rReq )
    {
        SfxSlotId nSlot = _rReq.GetSlot();
 
        ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlot );
        if ( aFeaturePos == m_aControlFeatures.end() )
        {
            // special slots
            switch ( nSlot )
            {
            case SID_CHAR_DLG:
                executeAttributeDialog( eCharAttribs, _rReq );
                break;
 
            case SID_PARA_DLG:
                executeAttributeDialog( eParaAttribs, _rReq );
                break;
 
            case SID_SELECTALL:
                executeSelectAll();
                break;
 
            case SID_CUT:
            case SID_COPY:
            case SID_PASTE:
                executeClipboardSlot( nSlot );
                break;
 
            default:
                DBG_ASSERT( aFeaturePos != m_aControlFeatures.end(), "FmTextControShell::ExecuteTextAttribute: I have no such dispatcher, and cannot handle it at all!" );
                return;
            }
        }
        else
        {
            // slots which are dispatched to the control
 
            switch ( nSlot )
            {
            case SID_ATTR_CHAR_STRIKEOUT:
            case SID_ATTR_CHAR_UNDERLINE:
            case SID_ATTR_CHAR_OVERLINE:
            {
                SfxItemSet aToggled( *_rReq.GetArgs() );
 
                lcl_translateUnoStateToItem( nSlot, aFeaturePos->second->getFeatureState(), aToggled );
                WhichId nWhich = aToggled.GetPool()->GetWhichIDFromSlotID( nSlot );
                const SfxPoolItem* pItem = aToggled.GetItem( nWhich );
                if ( ( SID_ATTR_CHAR_UNDERLINE == nSlot ) || ( SID_ATTR_CHAR_OVERLINE == nSlot ) )
                {
                    const SvxTextLineItem* pTextLine = dynamic_cast<const SvxTextLineItem*>( pItem  );
                    DBG_ASSERT( pTextLine, "FmTextControlShell::ExecuteTextAttribute: ooops - no underline/overline item!" );
                    if ( pTextLine )
                    {
                        FontLineStyle eTL = pTextLine->GetLineStyle();
                        if ( SID_ATTR_CHAR_UNDERLINE == nSlot ) {
                            aToggled.Put( SvxUnderlineItem( eTL == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, nWhich ) );
                        } else {
                            aToggled.Put( SvxOverlineItem( eTL == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, nWhich ) );
                        }
                    }
                }
                else
                {
                    const SvxCrossedOutItem* pCrossedOut = dynamic_cast<const SvxCrossedOutItem*>( pItem  );
                    DBG_ASSERT( pCrossedOut, "FmTextControlShell::ExecuteTextAttribute: ooops - no CrossedOut item!" );
                    if ( pCrossedOut )
                    {
                        FontStrikeout eFS = pCrossedOut->GetStrikeout();
                        aToggled.Put( SvxCrossedOutItem( eFS == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, nWhich ) );
                    }
                }
 
                Sequence< PropertyValue > aArguments;
                TransformItems( nSlot, aToggled, aArguments );
                aFeaturePos->second->dispatch( aArguments );
            }
            break;
 
            case SID_ATTR_CHAR_FONTHEIGHT:
            case SID_ATTR_CHAR_FONT:
            case SID_ATTR_CHAR_POSTURE:
            case SID_ATTR_CHAR_WEIGHT:
            case SID_ATTR_CHAR_SHADOWED:
            case SID_ATTR_CHAR_CONTOUR:
            case SID_SET_SUPER_SCRIPT:
            case SID_SET_SUB_SCRIPT:
            {
                const SfxItemSet* pArgs = _rReq.GetArgs();
                Sequence< PropertyValue > aArgs;
                if ( pArgs )
                    TransformItems( nSlot, *pArgs, aArgs );
                aFeaturePos->second->dispatch( aArgs );
            }
            break;
 
            default:
                if ( aFeaturePos->second->isFeatureEnabled() )
                    aFeaturePos->second->dispatch();
                break;
            }
        }
        _rReq.Done();
    }
 
 
    void FmTextControlShell::GetTextAttributeState( SfxItemSet& _rSet )
    {
        SfxWhichIter aIter( _rSet );
        sal_uInt16 nSlot = aIter.FirstWhich();
        while ( nSlot )
        {
            if  (   ( nSlot == SID_ATTR_PARA_LEFT_TO_RIGHT )
                ||  ( nSlot == SID_ATTR_PARA_RIGHT_TO_LEFT )
                )
            {
                if ( !SvtCTLOptions::IsCTLFontEnabled() )
                {
                    _rSet.DisableItem( nSlot );
                    nSlot = aIter.NextWhich();
                    continue;
                }
            }
 
            ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlot );
            if ( aFeaturePos != m_aControlFeatures.end() )
            {
                if ( aFeaturePos->second->isFeatureEnabled() )
                    lcl_translateUnoStateToItem( nSlot, aFeaturePos->second->getFeatureState(), _rSet );
                else
                    _rSet.DisableItem( nSlot );
            }
            else
            {
                bool bDisable = false;
 
                bool bNeedWriteableControl = false;
                bool bNeedTextComponent = false;
                bool bNeedSelection = false;
 
                switch ( nSlot )
                {
                case SID_CHAR_DLG:
                case SID_PARA_DLG:
                    bDisable |= m_aControlFeatures.empty();
                    bNeedWriteableControl = true;
                    break;
 
                case SID_CUT:
                    bNeedSelection = true;
                    bNeedTextComponent = true;
                    bNeedWriteableControl = true;
                    SAL_INFO("svx.form", "need to invalidate again" );
                    m_bNeedClipboardInvalidation = true;
                    break;
 
                case SID_PASTE:
                {
                    vcl::Window* pActiveControlVCLWindow = lcl_getWindow( m_xActiveControl );
                    if ( pActiveControlVCLWindow )
                    {
                        TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pActiveControlVCLWindow) );
                        bDisable |= !aDataHelper.HasFormat( SotClipboardFormatId::STRING );
                    }
                    else
                        bDisable = true;
 
                    bNeedTextComponent = true;
                    bNeedWriteableControl = true;
                }
                break;
 
                case SID_COPY:
                    bNeedTextComponent = true;
                    bNeedSelection = true;
                    break;
 
                case SID_SELECTALL:
                    bNeedTextComponent = true;
                    break;
 
                default:
                    // slot is unknown at all
                    bDisable = true;
                    break;
                }
                SAL_WARN_IF( bNeedSelection && !bNeedTextComponent, "svx.form", "FmTextControlShell::GetTextAttributeState: bNeedSelection should imply bNeedTextComponent!" );
 
                if ( !bDisable && bNeedWriteableControl )
                    bDisable |= !IsActiveControl( ) || m_bActiveControlIsReadOnly;
 
                if ( !bDisable && bNeedTextComponent )
                    bDisable |= !m_xActiveTextComponent.is();
 
                if ( !bDisable && bNeedSelection )
                {
                    css::awt::Selection aSelection = m_xActiveTextComponent->getSelection();
                    bDisable |= aSelection.Min == aSelection.Max;
                }
 
                if ( bDisable )
                    _rSet.DisableItem( nSlot );
            }
 
            nSlot = aIter.NextWhich();
        }
    }
 
 
    bool FmTextControlShell::IsActiveControl( bool _bCountRichTextOnly ) const
    {
        if ( _bCountRichTextOnly && !m_bActiveControlIsRichText )
            return false;
 
        return m_bActiveControl;
    }
 
 
    void FmTextControlShell::dispose()
    {
        if ( IsActiveControl() )
            controlDeactivated();
        if ( isControllerListening() )
            stopControllerListening();
    }
 
 
    void FmTextControlShell::designModeChanged()
    {
        m_rBindings.Invalidate( pTextControlSlots );
    }
 
 
    void FmTextControlShell::formActivated( const Reference< runtime::XFormController >& _rxController )
    {
#if OSL_DEBUG_LEVEL > 0
        SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(_rxController.get()), 16 ));
#endif
 
        DBG_ASSERT( _rxController.is(), "FmTextControlShell::formActivated: invalid controller!" );
        if ( !_rxController.is() )
            return;
 
        // sometimes, a form controller notifies activations, even if it's already activated
        if ( m_xActiveController == _rxController )
            return;
 
        try
        {
            startControllerListening( _rxController );
            controlActivated( _rxController->getCurrentControl() );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
 
 
    void FmTextControlShell::formDeactivated( const Reference< runtime::XFormController >& _rxController )
    {
        SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(_rxController.get()), 16 ));
 
        if ( IsActiveControl() )
            controlDeactivated();
        if ( isControllerListening() )
            stopControllerListening();
    }
 
 
    void FmTextControlShell::startControllerListening( const Reference< runtime::XFormController >& _rxController )
    {
        OSL_PRECOND( _rxController.is(), "FmTextControlShell::startControllerListening: invalid controller!" );
        if ( !_rxController.is() )
            return;
 
        OSL_PRECOND( !isControllerListening(), "FmTextControlShell::startControllerListening: already listening!" );
        if ( isControllerListening() )
            stopControllerListening( );
        DBG_ASSERT( !isControllerListening(), "FmTextControlShell::startControllerListening: inconsistence!" );
 
        try
        {
            const Sequence< Reference< css::awt::XControl > > aControls( _rxController->getControls() );
            m_aControlObservers.resize( 0 );
            m_aControlObservers.reserve( aControls.getLength() );
 
            std::transform(aControls.begin(), aControls.end(), std::back_inserter(m_aControlObservers),
                [this](const Reference< css::awt::XControl >& rControl) -> FocusListenerAdapter {
                    return FocusListenerAdapter( new FmFocusListenerAdapter( rControl, this ) ); });
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
 
        m_xActiveController = _rxController;
    }
 
 
    void FmTextControlShell::stopControllerListening( )
    {
        OSL_PRECOND( isControllerListening(), "FmTextControlShell::stopControllerListening: inconsistence!" );
 
        // dispose all listeners associated with the controls of the active controller
        for (auto& rpObserver : m_aControlObservers)
        {
            rpObserver->dispose();
        }
 
        FocusListenerAdapters().swap(m_aControlObservers);
 
        m_xActiveController.clear();
    }
 
 
    void FmTextControlShell::implClearActiveControlRef()
    {
        // no more features for this control
        for (auto& rFeature : m_aControlFeatures)
        {
            rFeature.second->dispose();
        }
 
        ControlFeatures().swap(m_aControlFeatures);
 
        if ( m_aContextMenuObserver )
        {
            m_aContextMenuObserver->dispose();
            m_aContextMenuObserver = MouseListenerAdapter();
        }
 
        if ( m_xActiveTextComponent.is() )
        {
            SAL_INFO("svx.form", "stopping timer for clipboard invalidation" );
            m_aClipboardInvalidation.Stop();
        }
        // no more active control
        m_xActiveControl.clear();
        m_xActiveTextComponent.clear();
        m_bActiveControlIsReadOnly = true;
        m_bActiveControlIsRichText = false;
        m_bActiveControl = false;
    }
 
 
    void FmTextControlShell::controlDeactivated( )
    {
        DBG_ASSERT( IsActiveControl(), "FmTextControlShell::controlDeactivated: no active control!" );
 
        m_bActiveControl = false;
 
        m_rBindings.Invalidate( pTextControlSlots );
    }
 
 
    void FmTextControlShell::controlActivated( const Reference< css::awt::XControl >& _rxControl )
    {
        // ensure that all knittings with the previously active control are lost
        if ( m_xActiveControl.is() )
            implClearActiveControlRef();
        DBG_ASSERT( m_aControlFeatures.empty(), "FmTextControlShell::controlActivated: should have no dispatchers when I'm here!" );
 
#if OSL_DEBUG_LEVEL > 0
        {
            Sequence< Reference< css::awt::XControl > > aActiveControls;
            if ( m_xActiveController.is() )
                aActiveControls = m_xActiveController->getControls();
 
            bool bFoundThisControl = false;
 
            const Reference< css::awt::XControl >* pControls = aActiveControls.getConstArray();
            const Reference< css::awt::XControl >* pControlsEnd = pControls + aActiveControls.getLength();
            for ( ; ( pControls != pControlsEnd ) && !bFoundThisControl; ++pControls )
            {
                if ( *pControls == _rxControl )
                    bFoundThisControl = true;
            }
            DBG_ASSERT( bFoundThisControl, "FmTextControlShell::controlActivated: only controls which belong to the active controller can be activated!" );
        }
#endif
        // ask the control for dispatchers for our text-related slots
        fillFeatureDispatchers( _rxControl, pTextControlSlots, m_aControlFeatures );
 
        // remember this control
        m_xActiveControl = _rxControl;
        m_xActiveTextComponent.set(_rxControl, css::uno::UNO_QUERY);
        m_bActiveControlIsReadOnly = lcl_determineReadOnly( m_xActiveControl );
        m_bActiveControlIsRichText = lcl_isRichText( m_xActiveControl );
 
        // if we found a rich text control, we need context menu support
        if ( m_bActiveControlIsRichText )
        {
            DBG_ASSERT( !m_aContextMenuObserver, "FmTextControlShell::controlActivated: already have an observer!" );
            m_aContextMenuObserver = MouseListenerAdapter( new FmMouseListenerAdapter( _rxControl, this ) );
        }
 
        if ( m_xActiveTextComponent.is() )
        {
            SAL_INFO("svx.form", "starting timer for clipboard invalidation" );
            m_aClipboardInvalidation.Start();
        }
 
        m_bActiveControl = true;
 
        m_rBindings.Invalidate( pTextControlSlots );
 
        if ( m_pViewFrame )
            m_pViewFrame->UIFeatureChanged();
 
        // don't call the activation handler if we don't have any slots we can serve
        // The activation handler is used to put the shell on the top of the dispatcher stack,
        // so it's preferred when slots are distributed.
        // Note that this is a slight hack, to prevent that we grab slots from the SfxDispatcher
        // which should be served by other shells (e.g. Cut/Copy/Paste).
        // A real solution would be a forwarding-mechanism for slots: We should be on the top
        // if we're active, but if we cannot handle the slot, then we need to tell the dispatcher
        // to skip our shell, and pass the slot to the next one. However, this mechanism is not
        // not in place in SFX.
        // Another possibility would be to have dedicated shells for the slots which we might
        // or might not be able to serve. However, this could probably increase the number of
        // shells too much (In theory, nearly every slot could have an own shell then).
 
        // #i51621# / 2005-08-19 / frank.schoenheit@sun.com
        // bool bHaveAnyServeableSlots = m_xActiveTextComponent.is() || !m_aControlFeatures.empty();
        // LEM: not calling m_aControlActivatonHandler causes fdo#63695, so disable this hack for now.
        m_aControlActivationHandler.Call( nullptr );
 
        m_bNeedClipboardInvalidation = true;
    }
 
 
    void FmTextControlShell::fillFeatureDispatchers(const Reference< css::awt::XControl >& _rxControl, SfxSlotId* _pZeroTerminatedSlots,
            ControlFeatures& _rDispatchers)
    {
        Reference< XDispatchProvider > xProvider( _rxControl, UNO_QUERY );
        SfxApplication* pApplication = SfxGetpApp();
        DBG_ASSERT( pApplication, "FmTextControlShell::fillFeatureDispatchers: no SfxApplication!" );
        if ( xProvider.is() && pApplication )
        {
            SfxSlotId* pSlots = _pZeroTerminatedSlots;
            while ( *pSlots )
            {
                rtl::Reference<FmTextControlFeature> pDispatcher = implGetFeatureDispatcher( xProvider, pApplication, *pSlots );
                if ( pDispatcher )
                    _rDispatchers.emplace( *pSlots, pDispatcher );
 
                ++pSlots;
            }
        }
    }
 
 
    rtl::Reference<FmTextControlFeature> FmTextControlShell::implGetFeatureDispatcher( const Reference< XDispatchProvider >& _rxProvider, SfxApplication const * _pApplication, SfxSlotId _nSlot )
    {
        OSL_PRECOND( _rxProvider.is() && _pApplication, "FmTextControlShell::implGetFeatureDispatcher: invalid arg(s)!" );
        URL aFeatureURL;
        aFeatureURL.Complete = lcl_getUnoSlotName( _nSlot );
        try
        {
            if ( !m_xURLTransformer.is() )
            {
                m_xURLTransformer = util::URLTransformer::create( ::comphelper::getProcessComponentContext() );
            }
            if ( m_xURLTransformer.is() )
                m_xURLTransformer->parseStrict( aFeatureURL );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
        Reference< XDispatch > xDispatcher = _rxProvider->queryDispatch( aFeatureURL, OUString(), 0xFF );
        if ( xDispatcher.is() )
            return new FmTextControlFeature( xDispatcher, std::move(aFeatureURL), _nSlot, this );
        return nullptr;
    }
 
 
    void FmTextControlShell::Invalidate( SfxSlotId _nSlot )
    {
        m_rBindings.Invalidate( _nSlot );
        // despite this method being called "Invalidate", we also update here - this gives more immediate
        // feedback in the UI
        m_rBindings.Update( _nSlot );
    }
 
 
    void FmTextControlShell::focusGained( const css::awt::FocusEvent& _rEvent )
    {
        Reference< css::awt::XControl > xControl( _rEvent.Source, UNO_QUERY );
 
#if OSL_DEBUG_LEVEL > 0
        SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(xControl.get()), 16 ));
#endif
 
        DBG_ASSERT( xControl.is(), "FmTextControlShell::focusGained: suspicious focus event!" );
        if ( xControl.is() )
            controlActivated( xControl );
    }
 
 
    void FmTextControlShell::focusLost( const css::awt::FocusEvent& _rEvent )
    {
        Reference< css::awt::XControl > xControl( _rEvent.Source, UNO_QUERY );
 
#if OSL_DEBUG_LEVEL > 0
        SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(xControl.get()), 16 ));
#endif
 
        m_bActiveControl = false;
    }
 
 
    void FmTextControlShell::ForgetActiveControl()
    {
        implClearActiveControlRef();
    }
 
 
    void FmTextControlShell::contextMenuRequested()
    {
        m_rBindings.GetDispatcher()->ExecutePopup( u"formrichtext"_ustr );
    }
 
 
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1053 Calling the 'acquire' virtual function in the destructor may lead to unexpected result at runtime.

V1053 Calling the 'acquire' virtual function in the destructor may lead to unexpected result at runtime.

V1048 The 'bIsReadOnlyModel' variable was assigned the same value.

V1048 The 'bIsReadOnlyModel' variable was assigned the same value.