/* -*- 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 <svx/svdmodel.hxx>
#include <cassert>
#include <sal/log.hxx>
#include <rtl/ustrbuf.hxx>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <unotools/configmgr.hxx>
#include <unotools/pathoptions.hxx>
#include <svl/whiter.hxx>
#include <svl/asiancfg.hxx>
#include <svx/compatflags.hxx>
#include <svx/xbtmpit.hxx>
#include <svx/xlndsit.hxx>
#include <svx/xlnedit.hxx>
#include <svx/xflgrit.hxx>
#include <svx/xflftrit.hxx>
#include <svx/xflhtit.hxx>
#include <svx/xlnstit.hxx>
#include <editeng/editeng.hxx>
#include <svx/xtable.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdlayer.hxx>
#include <svx/svdundo.hxx>
#include <svx/svdpool.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdotext.hxx>
#include <svx/unoshape.hxx>
#include <textchain.hxx>
#include <svx/svdetc.hxx>
#include <svx/svdoutl.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <svx/theme/IThemeColorChanger.hxx>
#include <svdoutlinercache.hxx>
#include <svx/sdasitm.hxx>
#include <officecfg/Office/Common.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/fhgtitem.hxx>
#include <svl/style.hxx>
#include <editeng/forbiddencharacterstable.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/storagehelper.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/syslocale.hxx>
#include <editeng/eeitem.hxx>
#include <svl/itemset.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <memory>
#include <libxml/xmlwriter.h>
#include <sfx2/viewsh.hxx>
#include <o3tl/enumrange.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/UnitConversion.hxx>
#include <docmodel/theme/Theme.hxx>
#include <svx/ColorSets.hxx>
#include <svx/svditer.hxx>
#include <svx/svdoashp.hxx>
 
 
using namespace ::com::sun::star;
 
struct SdrModelImpl
{
    SfxUndoManager* mpUndoManager;
    SdrUndoFactory* mpUndoFactory;
    bool mbAnchoredTextOverflowLegacy; // tdf#99729 compatibility flag
    bool mbLegacyFontwork;             // tdf#148000 compatibility flag
    bool mbConnectorUseSnapRect;       // tdf#149756 compatibility flag
    bool mbIgnoreBreakAfterMultilineField; ///< tdf#148966 compatibility flag
    std::shared_ptr<model::Theme> mpTheme;
    std::shared_ptr<svx::IThemeColorChanger> mpThemeColorChanger;
 
    SdrModelImpl()
        : mpUndoManager(nullptr)
        , mpUndoFactory(nullptr)
        , mbAnchoredTextOverflowLegacy(false)
        , mbLegacyFontwork(false)
        , mbConnectorUseSnapRect(false)
        , mbIgnoreBreakAfterMultilineField(false)
        , mpTheme(new model::Theme(u"Office"_ustr))
    {}
 
    void initTheme()
    {
        auto const* pColorSet = svx::ColorSets::get().getColorSet(u"LibreOffice");
        if (pColorSet)
        {
            std::shared_ptr<model::ColorSet> pDefaultColorSet(new model::ColorSet(*pColorSet));
            mpTheme->setColorSet(pDefaultColorSet);
        }
    }
};
 
 
SdrModel::SdrModel(SfxItemPool* pPool, comphelper::IEmbeddedHelper* pEmbeddedHelper, bool bDisablePropertyFiles)
    : m_eObjUnit(SdrEngineDefaults::GetMapUnit())
    , m_eUIUnit(FieldUnit::MM)
    , m_aUIScale(Fraction(1,1))
    , m_nUIUnitDecimalMark(0)
    , m_pLayerAdmin(new SdrLayerAdmin)
    , m_pItemPool(pPool)
    , m_pEmbeddedHelper(pEmbeddedHelper)
    , mnDefTextHgt(SdrEngineDefaults::GetFontHeight())
    , m_pRefOutDev(nullptr)
    , m_pDefaultStyleSheet(nullptr)
    , mpDefaultStyleSheetForSdrGrafObjAndSdrOle2Obj(nullptr)
    , m_pLinkManager(nullptr)
    , m_nUndoLevel(0)
    , m_bIsWriter(true)
    , m_bThemedControls(true)
    , mbUndoEnabled(true)
    , mbChanged(false)
    , m_bTransportContainer(false)
    , m_bReadOnly(false)
    , m_bTransparentTextFrames(false)
    , m_bSwapGraphics(false)
    , m_bPasteResize(false)
    , m_bStarDrawPreviewMode(false)
    , mbDisableTextEditUsesCommonUndoManager(false)
    , mbVOCInvalidationIsReliable(false)
    , m_bIsPDFDocument(false)
    , m_nDefaultTabulator(0)
    , m_nMaxUndoCount(16)
    , m_pTextChain(new TextChain)
    , mpImpl(new SdrModelImpl)
    , mnCharCompressType(CharCompressType::NONE)
    , mnHandoutPageCount(0)
    , mbModelLocked(false)
    , mbKernAsianPunctuation(false)
    , mbAddExtLeading(false)
    , mbInDestruction(false)
{
    if (!comphelper::IsFuzzing())
    {
        mnCharCompressType = static_cast<CharCompressType>(
            officecfg::Office::Common::AsianLayout::CompressCharacterDistance::get());
    }
 
    if (m_pItemPool == nullptr)
    {
        m_pItemPool = new SdrItemPool(nullptr);
        // Outliner doesn't have its own Pool, so use the EditEngine's
        rtl::Reference<SfxItemPool> pOutlPool=EditEngine::CreatePool();
        // OutlinerPool as SecondaryPool of SdrPool
        m_pItemPool->SetSecondaryPool(pOutlPool.get());
        // remember that I created both pools myself
        m_bIsWriter = false;
    }
    m_pItemPool->SetDefaultMetric(m_eObjUnit);
 
// using static SdrEngineDefaults only if default SvxFontHeight item is not available
    const SfxPoolItem* pPoolItem = m_pItemPool->GetUserDefaultItem( EE_CHAR_FONTHEIGHT );
    if (pPoolItem)
        mnDefTextHgt = static_cast<const SvxFontHeightItem*>(pPoolItem)->GetHeight();
 
    m_pItemPool->SetUserDefaultItem( makeSdrTextWordWrapItem( false ) );
 
    SetTextDefaults();
    m_pLayerAdmin->SetModel(this);
    ImpSetUIUnit();
 
    // can't create DrawOutliner OnDemand, because I can't get the Pool,
    // then (only from 302 onwards!)
    m_pDrawOutliner = SdrMakeOutliner(OutlinerMode::TextObject, *this);
    ImpSetOutlinerDefaults(m_pDrawOutliner.get(), true);
 
    m_pHitTestOutliner = SdrMakeOutliner(OutlinerMode::TextObject, *this);
    ImpSetOutlinerDefaults(m_pHitTestOutliner.get(), true);
 
    /* Start Text Chaining related code */
    // Initialize Chaining Outliner
    m_pChainingOutliner = SdrMakeOutliner( OutlinerMode::TextObject, *this );
    ImpSetOutlinerDefaults(m_pChainingOutliner.get(), true);
 
    ImpCreateTables(bDisablePropertyFiles || comphelper::IsFuzzing());
 
    mpImpl->initTheme();
}
 
void SdrModel::implDtorClearModel()
{
    mbInDestruction = true;
 
    Broadcast(SdrHint(SdrHintKind::ModelCleared));
 
    mpOutlinerCache.reset();
 
    ClearUndoBuffer();
#ifdef DBG_UTIL
    SAL_WARN_IF(m_pCurrentUndoGroup, "svx", "In the Dtor of the SdrModel there is an open Undo left: \""
                    << m_pCurrentUndoGroup->GetComment() << '\"');
#endif
    m_pCurrentUndoGroup.reset();
 
    ClearModel(true);
}
 
SdrModel::~SdrModel()
{
    implDtorClearModel();
 
#ifdef DBG_UTIL
    if(!maAllIncarnatedObjects.empty())
    {
        SAL_WARN("svx",
            "SdrModel::~SdrModel: Not all incarnations of SdrObjects deleted, possible memory leak");
        for (const auto & pObj : maAllIncarnatedObjects)
            SAL_WARN("svx", "leaked instance of " << typeid(*pObj).name());
    }
#endif
 
    m_pLayerAdmin.reset();
 
    m_pTextChain.reset();
    // Delete DrawOutliner only after deleting ItemPool, because ItemPool
    // references Items of the DrawOutliner!
    m_pChainingOutliner.reset();
    m_pHitTestOutliner.reset();
    m_pDrawOutliner.reset();
 
    // delete StyleSheetPool, derived classes should not do this since
    // the DrawingEngine may need it in its destructor
    if( mxStyleSheetPool.is() )
    {
        uno::Reference<lang::XComponent> xComponent( getXWeak( mxStyleSheetPool.get() ), uno::UNO_QUERY );
        if( xComponent.is() ) try
        {
            xComponent->dispose();
        }
        catch (uno::RuntimeException&)
        {
        }
        mxStyleSheetPool.clear();
    }
 
    mpForbiddenCharactersTable.reset();
 
    delete mpImpl->mpUndoFactory;
}
 
void SdrModel::SetSwapGraphics()
{
    m_bSwapGraphics = true;
}
 
bool SdrModel::IsReadOnly() const
{
    return m_bReadOnly;
}
 
void SdrModel::SetReadOnly(bool bYes)
{
    m_bReadOnly=bYes;
}
 
 
void SdrModel::SetMaxUndoActionCount(sal_uInt32 nCount)
{
    if (nCount<1) nCount=1;
    m_nMaxUndoCount=nCount;
    while (m_aUndoStack.size()>m_nMaxUndoCount)
        m_aUndoStack.pop_back();
}
 
void SdrModel::ClearUndoBuffer()
{
    m_aUndoStack.clear();
    m_aRedoStack.clear();
}
 
bool SdrModel::HasUndoActions() const
{
    return !m_aUndoStack.empty();
}
 
bool SdrModel::HasRedoActions() const
{
    return !m_aRedoStack.empty();
}
 
void SdrModel::Undo()
{
    if( mpImpl->mpUndoManager )
    {
        OSL_FAIL("svx::SdrModel::Undo(), method not supported with application undo manager!");
    }
    else
    {
        if(HasUndoActions())
        {
            SfxUndoAction* pDo = m_aUndoStack.front().get();
            const bool bWasUndoEnabled = mbUndoEnabled;
            mbUndoEnabled = false;
            pDo->Undo();
            std::unique_ptr<SfxUndoAction> p = std::move(m_aUndoStack.front());
            m_aUndoStack.pop_front();
            m_aRedoStack.emplace_front(std::move(p));
            mbUndoEnabled = bWasUndoEnabled;
        }
    }
}
 
void SdrModel::Redo()
{
    if( mpImpl->mpUndoManager )
    {
        OSL_FAIL("svx::SdrModel::Redo(), method not supported with application undo manager!");
    }
    else
    {
        if(HasRedoActions())
        {
            SfxUndoAction* pDo = m_aRedoStack.front().get();
            const bool bWasUndoEnabled = mbUndoEnabled;
            mbUndoEnabled = false;
            pDo->Redo();
            std::unique_ptr<SfxUndoAction> p = std::move(m_aRedoStack.front());
            m_aRedoStack.pop_front();
            m_aUndoStack.emplace_front(std::move(p));
            mbUndoEnabled = bWasUndoEnabled;
        }
    }
}
 
void SdrModel::Repeat(SfxRepeatTarget& rView)
{
    if( mpImpl->mpUndoManager )
    {
        OSL_FAIL("svx::SdrModel::Redo(), method not supported with application undo manager!");
    }
    else
    {
        if(HasUndoActions())
        {
            SfxUndoAction* pDo =  m_aUndoStack.front().get();
            if(pDo->CanRepeat(rView))
            {
                pDo->Repeat(rView);
            }
        }
    }
}
 
void SdrModel::ImpPostUndoAction(std::unique_ptr<SdrUndoAction> pUndo)
{
    DBG_ASSERT( mpImpl->mpUndoManager == nullptr, "svx::SdrModel::ImpPostUndoAction(), method not supported with application undo manager!" );
    if( !IsUndoEnabled() )
        return;
 
    if (m_aUndoLink)
    {
        m_aUndoLink(std::move(pUndo));
    }
    else
    {
        m_aUndoStack.emplace_front(std::move(pUndo));
        while (m_aUndoStack.size()>m_nMaxUndoCount)
        {
            m_aUndoStack.pop_back();
        }
        m_aRedoStack.clear();
    }
}
 
void SdrModel::BegUndo()
{
    if( mpImpl->mpUndoManager )
    {
        ViewShellId nViewShellId(-1);
        if (SfxViewShell* pViewShell = SfxViewShell::Current())
            nViewShellId = pViewShell->GetViewShellId();
        mpImpl->mpUndoManager->EnterListAction(u""_ustr,u""_ustr,0,nViewShellId);
        m_nUndoLevel++;
    }
    else if( IsUndoEnabled() )
    {
        if(!m_pCurrentUndoGroup)
        {
            m_pCurrentUndoGroup.reset(new SdrUndoGroup(*this));
            m_nUndoLevel=1;
        }
        else
        {
            m_nUndoLevel++;
        }
    }
}
 
void SdrModel::BegUndo(const OUString& rComment)
{
    if( mpImpl->mpUndoManager )
    {
        ViewShellId nViewShellId(-1);
        if (SfxViewShell* pViewShell = SfxViewShell::Current())
            nViewShellId = pViewShell->GetViewShellId();
        mpImpl->mpUndoManager->EnterListAction( rComment, u""_ustr, 0, nViewShellId );
        m_nUndoLevel++;
    }
    else if( IsUndoEnabled() )
    {
        BegUndo();
        if (m_nUndoLevel==1)
        {
            m_pCurrentUndoGroup->SetComment(rComment);
        }
    }
}
 
void SdrModel::BegUndo(const OUString& rComment, const OUString& rObjDescr, SdrRepeatFunc eFunc)
{
    if( mpImpl->mpUndoManager )
    {
        OUString aComment(rComment);
        if( !aComment.isEmpty() && !rObjDescr.isEmpty() )
        {
            aComment = aComment.replaceFirst("%1", rObjDescr);
        }
        ViewShellId nViewShellId(-1);
        if (SfxViewShell* pViewShell = SfxViewShell::Current())
            nViewShellId = pViewShell->GetViewShellId();
        mpImpl->mpUndoManager->EnterListAction( aComment,u""_ustr,0,nViewShellId );
        m_nUndoLevel++;
    }
    else if( IsUndoEnabled() )
    {
        BegUndo();
        if (m_nUndoLevel==1)
        {
            m_pCurrentUndoGroup->SetComment(rComment);
            m_pCurrentUndoGroup->SetObjDescription(rObjDescr);
            m_pCurrentUndoGroup->SetRepeatFunction(eFunc);
        }
    }
}
 
void SdrModel::EndUndo()
{
    DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::EndUndo(): UndoLevel is already 0!");
    if( mpImpl->mpUndoManager )
    {
        if( m_nUndoLevel )
        {
            m_nUndoLevel--;
            mpImpl->mpUndoManager->LeaveListAction();
        }
    }
    else
    {
        if(m_pCurrentUndoGroup!=nullptr && IsUndoEnabled())
        {
            m_nUndoLevel--;
            if(m_nUndoLevel==0)
            {
                if(m_pCurrentUndoGroup->GetActionCount()!=0)
                {
                    ImpPostUndoAction(std::move(m_pCurrentUndoGroup));
                }
                else
                {
                    // was empty
                    m_pCurrentUndoGroup.reset();
                }
            }
        }
    }
}
 
void SdrModel::SetUndoComment(const OUString& rComment)
{
    DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::SetUndoComment(): UndoLevel is already 0!");
 
    if( mpImpl->mpUndoManager )
    {
        OSL_FAIL("svx::SdrModel::SetUndoComment(), method not supported with application undo manager!" );
    }
    else if( IsUndoEnabled() && m_nUndoLevel==1)
    {
        m_pCurrentUndoGroup->SetComment(rComment);
    }
}
 
void SdrModel::SetUndoComment(const OUString& rComment, const OUString& rObjDescr)
{
    DBG_ASSERT(m_nUndoLevel!=0,"SdrModel::SetUndoComment(): UndoLevel is already 0!");
    if( mpImpl->mpUndoManager )
    {
        OSL_FAIL("svx::SdrModel::SetUndoComment(), method not supported with application undo manager!" );
    }
    else
    {
        if (m_nUndoLevel==1)
        {
            m_pCurrentUndoGroup->SetComment(rComment);
            m_pCurrentUndoGroup->SetObjDescription(rObjDescr);
        }
    }
}
 
void SdrModel::AddUndo(std::unique_ptr<SdrUndoAction> pUndo)
{
    if( mpImpl->mpUndoManager )
    {
        mpImpl->mpUndoManager->AddUndoAction( std::move(pUndo) );
    }
    else if( IsUndoEnabled() )
    {
        if (m_pCurrentUndoGroup)
        {
            m_pCurrentUndoGroup->AddAction(std::move(pUndo));
        }
        else
        {
            ImpPostUndoAction(std::move(pUndo));
        }
    }
}
 
void SdrModel::EnableUndo( bool bEnable )
{
    if( mpImpl->mpUndoManager )
    {
        mpImpl->mpUndoManager->EnableUndo( bEnable );
    }
    else
    {
        mbUndoEnabled = bEnable;
    }
}
 
bool SdrModel::IsUndoEnabled() const
{
    if( mpImpl->mpUndoManager )
    {
        return mpImpl->mpUndoManager->IsUndoEnabled();
    }
    else
    {
        return mbUndoEnabled;
    }
}
 
void SdrModel::ImpCreateTables(bool bDisablePropertyFiles)
{
    // use standard path for initial construction
    const OUString aTablePath(!bDisablePropertyFiles ? SvtPathOptions().GetPalettePath() : u""_ustr);
 
    for( auto i : o3tl::enumrange<XPropertyListType>() )
    {
        maProperties[i] = XPropertyList::CreatePropertyList(i, aTablePath, u""_ustr/*TODO?*/ );
    }
}
 
void SdrModel::ClearModel(bool bCalledFromDestructor)
{
    if(bCalledFromDestructor)
    {
        mbInDestruction = true;
    }
 
    // Disconnect all SvxShape's from their SdrObjects to prevent the SdrObjects
    // from hanging around and causing use-after-free.
    // Make a copy because it might modified during InvalidateSdrObject calls.
    std::vector<rtl::Reference<SdrObject>> allObjs(maAllIncarnatedObjects.begin(), maAllIncarnatedObjects.end());
    for (const auto & pSdrObj : allObjs)
    {
        uno::Reference<uno::XInterface> xShape = pSdrObj->getWeakUnoShape().get();
        rtl::Reference<SvxShape> pSvxShape = dynamic_cast<SvxShape*>(xShape.get());
        // calling getWeakUnoShape so we don't accidentally create new UNO shapes
        if (pSvxShape)
            pSvxShape->InvalidateSdrObject();
        else
        {
            // because some things like SwXShape don't subclass SvxShape
            uno::Reference<lang::XComponent> xComp(xShape, uno::UNO_QUERY);
            if (xComp)
                xComp->dispose();
        }
    }
    sal_Int32 i;
    // delete all drawing pages
    sal_Int32 nCount=GetPageCount();
    for (i=nCount-1; i>=0; i--)
    {
        DeletePage( static_cast<sal_uInt16>(i) );
    }
    maPages.clear();
    PageListChanged();
 
    // delete all Masterpages
    nCount=GetMasterPageCount();
    for(i=nCount-1; i>=0; i--)
    {
        DeleteMasterPage( static_cast<sal_uInt16>(i) );
    }
    maMasterPages.clear();
    MasterPageListChanged();
 
    m_pLayerAdmin->ClearLayers();
}
 
SdrModel* SdrModel::AllocModel() const
{
    SdrModel* pModel=new SdrModel();
    pModel->SetScaleUnit(m_eObjUnit);
    return pModel;
}
 
rtl::Reference<SdrPage> SdrModel::AllocPage(bool bMasterPage)
{
    return new SdrPage(*this,bMasterPage);
}
 
void SdrModel::SetTextDefaults() const
{
    SetTextDefaults( m_pItemPool.get(), mnDefTextHgt );
}
 
void SdrModel::SetTextDefaults( SfxItemPool* pItemPool, sal_Int32 nDefTextHgt )
{
    // set application-language specific dynamic pool language defaults
    SvxFontItem aSvxFontItem( EE_CHAR_FONTINFO) ;
    SvxFontItem aSvxFontItemCJK(EE_CHAR_FONTINFO_CJK);
    SvxFontItem aSvxFontItemCTL(EE_CHAR_FONTINFO_CTL);
    LanguageType nLanguage;
    if (!comphelper::IsFuzzing())
        nLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
    else
        nLanguage = LANGUAGE_ENGLISH_US;
 
    // get DEFAULTFONT_LATIN_TEXT and set at pool as dynamic default
    vcl::Font aFont(OutputDevice::GetDefaultFont(DefaultFontType::LATIN_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne));
    aSvxFontItem.SetFamily(aFont.GetFamilyType());
    aSvxFontItem.SetFamilyName(aFont.GetFamilyName());
    aSvxFontItem.SetStyleName(OUString());
    aSvxFontItem.SetPitch( aFont.GetPitch());
    aSvxFontItem.SetCharSet( aFont.GetCharSet() );
    pItemPool->SetUserDefaultItem(aSvxFontItem);
 
    // get DEFAULTFONT_CJK_TEXT and set at pool as dynamic default
    vcl::Font aFontCJK(OutputDevice::GetDefaultFont(DefaultFontType::CJK_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne));
    aSvxFontItemCJK.SetFamily( aFontCJK.GetFamilyType());
    aSvxFontItemCJK.SetFamilyName(aFontCJK.GetFamilyName());
    aSvxFontItemCJK.SetStyleName(OUString());
    aSvxFontItemCJK.SetPitch( aFontCJK.GetPitch());
    aSvxFontItemCJK.SetCharSet( aFontCJK.GetCharSet());
    pItemPool->SetUserDefaultItem(aSvxFontItemCJK);
 
    // get DEFAULTFONT_CTL_TEXT and set at pool as dynamic default
    vcl::Font aFontCTL(OutputDevice::GetDefaultFont(DefaultFontType::CTL_TEXT, nLanguage, GetDefaultFontFlags::OnlyOne));
    aSvxFontItemCTL.SetFamily(aFontCTL.GetFamilyType());
    aSvxFontItemCTL.SetFamilyName(aFontCTL.GetFamilyName());
    aSvxFontItemCTL.SetStyleName(OUString());
    aSvxFontItemCTL.SetPitch( aFontCTL.GetPitch() );
    aSvxFontItemCTL.SetCharSet( aFontCTL.GetCharSet());
    pItemPool->SetUserDefaultItem(aSvxFontItemCTL);
 
    // set dynamic FontHeight defaults
    pItemPool->SetUserDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT ) );
    pItemPool->SetUserDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT_CJK ) );
    pItemPool->SetUserDefaultItem( SvxFontHeightItem(nDefTextHgt, 100, EE_CHAR_FONTHEIGHT_CTL ) );
 
    // set FontColor defaults
    pItemPool->SetUserDefaultItem( SvxColorItem(SdrEngineDefaults::GetFontColor(), EE_CHAR_COLOR) );
}
 
SdrOutliner& SdrModel::GetDrawOutliner(const SdrTextObj* pObj) const
{
    m_pDrawOutliner->SetTextObj(pObj);
    return *m_pDrawOutliner;
}
 
SdrOutliner& SdrModel::GetChainingOutliner(const SdrTextObj* pObj) const
{
    m_pChainingOutliner->SetTextObj(pObj);
    return *m_pChainingOutliner;
}
 
const SdrTextObj* SdrModel::GetFormattingTextObj() const
{
    if (m_pDrawOutliner!=nullptr) {
        return m_pDrawOutliner->GetTextObj();
    }
    return nullptr;
}
 
void SdrModel::ImpSetOutlinerDefaults( SdrOutliner* pOutliner, bool bInit )
{
    // Initialization of the Outliners for drawing text and HitTest
    if( bInit )
    {
        pOutliner->EraseVirtualDevice();
        pOutliner->SetUpdateLayout(false);
        pOutliner->SetEditTextObjectPool(m_pItemPool.get());
        pOutliner->SetDefTab(m_nDefaultTabulator);
    }
 
    pOutliner->SetRefDevice(GetRefDevice());
    Outliner::SetForbiddenCharsTable(GetForbiddenCharsTable());
    pOutliner->SetAsianCompressionMode( mnCharCompressType );
    pOutliner->SetKernAsianPunctuation( IsKernAsianPunctuation() );
    pOutliner->SetAddExtLeading( IsAddExtLeading() );
 
    if ( !GetRefDevice() )
    {
        MapMode aMapMode(m_eObjUnit);
        pOutliner->SetRefMapMode(aMapMode);
    }
}
 
void SdrModel::SetRefDevice(OutputDevice* pDev)
{
    m_pRefOutDev=pDev;
    ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
    ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
    RefDeviceChanged();
}
 
void SdrModel::ImpReformatAllTextObjects()
{
    if( isLocked() )
        return;
 
    sal_uInt16 nCount=GetMasterPageCount();
    sal_uInt16 nNum;
    for (nNum=0; nNum<nCount; nNum++) {
        GetMasterPage(nNum)->ReformatAllTextObjects();
    }
    nCount=GetPageCount();
    for (nNum=0; nNum<nCount; nNum++) {
        GetPage(nNum)->ReformatAllTextObjects();
    }
}
 
/*  steps over all available pages and sends notify messages to
    all edge objects that are connected to other objects so that
    they may reposition themselves
*/
void SdrModel::ImpReformatAllEdgeObjects()
{
    if( isLocked() )
        return;
 
    sal_uInt16 nCount=GetMasterPageCount();
    sal_uInt16 nNum;
    for (nNum=0; nNum<nCount; nNum++)
    {
        GetMasterPage(nNum)->ReformatAllEdgeObjects();
    }
    nCount=GetPageCount();
    for (nNum=0; nNum<nCount; nNum++)
    {
        GetPage(nNum)->ReformatAllEdgeObjects();
    }
}
 
uno::Reference<embed::XStorage> SdrModel::GetDocumentStorage() const
{
    uno::Reference<document::XStorageBasedDocument> const xSBD(
            const_cast<SdrModel*>(this)->getUnoModel(), uno::UNO_QUERY);
    if (!xSBD.is())
    {
        SAL_WARN("svx", "no UNO model");
        return nullptr;
    }
    return xSBD->getDocumentStorage();
}
 
uno::Reference<io::XInputStream>
SdrModel::GetDocumentStream( OUString const& rURL,
                ::comphelper::LifecycleProxy const & rProxy) const
{
    uno::Reference<embed::XStorage> const xStorage(GetDocumentStorage());
    if (!xStorage.is())
    {
        SAL_WARN("svx", "no storage?");
        return nullptr;
    }
    try {
        uno::Reference<io::XStream> const xStream(
            ::comphelper::OStorageHelper::GetStreamAtPackageURL(
                xStorage, rURL, embed::ElementModes::READ, rProxy));
        return (xStream.is()) ? xStream->getInputStream() : nullptr;
    }
    catch (container::NoSuchElementException const&)
    {
        SAL_INFO("svx", "not found");
    }
    catch (uno::Exception const&)
    {
        TOOLS_WARN_EXCEPTION("svx", "");
    }
    return nullptr;
}
 
// convert template attributes from the string into "hard" attributes
void SdrModel::BurnInStyleSheetAttributes()
{
    sal_uInt16 nCount=GetMasterPageCount();
    sal_uInt16 nNum;
    for (nNum=0; nNum<nCount; nNum++) {
        GetMasterPage(nNum)->BurnInStyleSheetAttributes();
    }
    nCount=GetPageCount();
    for (nNum=0; nNum<nCount; nNum++) {
        GetPage(nNum)->BurnInStyleSheetAttributes();
    }
}
 
void SdrModel::RefDeviceChanged()
{
    Broadcast(SdrHint(SdrHintKind::RefDeviceChange));
    ImpReformatAllTextObjects();
}
 
void SdrModel::SetDefaultFontHeight(sal_Int32 nVal)
{
    if (nVal!=mnDefTextHgt) {
        mnDefTextHgt=nVal;
        ImpReformatAllTextObjects();
    }
}
 
void SdrModel::SetDefaultTabulator(sal_uInt16 nVal)
{
    if (m_nDefaultTabulator!=nVal) {
        m_nDefaultTabulator=nVal;
        Outliner& rOutliner=GetDrawOutliner();
        rOutliner.SetDefTab(nVal);
        Broadcast(SdrHint(SdrHintKind::DefaultTabChange));
        ImpReformatAllTextObjects();
    }
}
 
void SdrModel::ImpSetUIUnit()
{
    if(0 == m_aUIScale.GetNumerator() || 0 == m_aUIScale.GetDenominator())
    {
        m_aUIScale = Fraction(1,1);
    }
 
    m_nUIUnitDecimalMark = 0;
 
    o3tl::Length eFrom = MapToO3tlLength(m_eObjUnit, o3tl::Length::invalid);
    o3tl::Length eTo;
 
    switch (m_eUIUnit)
    {
        case FieldUnit::CHAR:
        case FieldUnit::LINE:
            eTo = o3tl::Length::invalid;
            break;
        case FieldUnit::PERCENT:
            m_nUIUnitDecimalMark += 2;
            [[fallthrough]];
        default:
            eTo = FieldToO3tlLength(m_eUIUnit, o3tl::Length::invalid);
    } // switch
 
    sal_Int32 nMul = 1, nDiv = 1;
    if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
    {
        const auto [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo);
        nMul = mul;
        nDiv = div;
    }
    // #i89872# take Unit of Measurement into account
    if(1 != m_aUIScale.GetDenominator() || 1 != m_aUIScale.GetNumerator())
    {
        // divide by UIScale
        nMul *= m_aUIScale.GetDenominator();
        nDiv *= m_aUIScale.GetNumerator();
    }
 
    // shorten trailing zeros for dividend
    while(0 == (nMul % 10))
    {
        m_nUIUnitDecimalMark--;
        nMul /= 10;
    }
 
    // shorten trailing zeros for divisor
    while(0 == (nDiv % 10))
    {
        m_nUIUnitDecimalMark++;
        nDiv /= 10;
    }
 
    // end preparations, set member values
    m_aUIUnitFact = Fraction(sal_Int32(nMul), sal_Int32(nDiv));
    m_aUIUnitStr = GetUnitString(m_eUIUnit);
}
 
void SdrModel::SetScaleUnit(MapUnit eMap)
{
    if (m_eObjUnit!=eMap) {
        m_eObjUnit=eMap;
        m_pItemPool->SetDefaultMetric(m_eObjUnit);
        ImpSetUIUnit();
        ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
        ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
        ImpReformatAllTextObjects();
    }
}
 
void SdrModel::SetUIUnit(FieldUnit eUnit)
{
    if (m_eUIUnit!=eUnit) {
        m_eUIUnit=eUnit;
        ImpSetUIUnit();
        ImpReformatAllTextObjects();
    }
}
 
void SdrModel::SetUIScale(const Fraction& rScale)
{
    if (m_aUIScale!=rScale) {
        m_aUIScale=rScale;
        ImpSetUIUnit();
        ImpReformatAllTextObjects();
    }
}
 
void SdrModel::SetUIUnit(FieldUnit eUnit, const Fraction& rScale)
{
    if (m_eUIUnit!=eUnit || m_aUIScale!=rScale) {
        m_eUIUnit=eUnit;
        m_aUIScale=rScale;
        ImpSetUIUnit();
        ImpReformatAllTextObjects();
    }
}
 
OUString SdrModel::GetUnitString(FieldUnit eUnit)
{
    switch(eUnit)
    {
        default:
        case FieldUnit::NONE   :
        case FieldUnit::CUSTOM :
            return OUString();
        case FieldUnit::MM_100TH:
            return u"/100mm"_ustr;
        case FieldUnit::MM     :
            return u"mm"_ustr;
        case FieldUnit::CM     :
            return u"cm"_ustr;
        case FieldUnit::M      :
            return u"m"_ustr;
        case FieldUnit::KM     :
            return u"km"_ustr;
        case FieldUnit::TWIP   :
            return u"twip"_ustr;
        case FieldUnit::POINT  :
            return u"pt"_ustr;
        case FieldUnit::PICA   :
            return u"pica"_ustr;
        case FieldUnit::INCH   :
            return u"\""_ustr;
        case FieldUnit::FOOT   :
            return u"ft"_ustr;
        case FieldUnit::MILE   :
            return u"mile(s)"_ustr;
        case FieldUnit::PERCENT:
            return u"%"_ustr;
    }
}
 
OUString SdrModel::GetMetricString(tools::Long nVal, bool bNoUnitChars, sal_Int32 nNumDigits) const
{
    // #i22167#
    // change to double precision usage to not lose decimal places
    const bool bNegative(nVal < 0);
    SvtSysLocale aSysLoc;
    const LocaleDataWrapper& rLoc(aSysLoc.GetLocaleData());
    double fLocalValue(double(nVal) * double(m_aUIUnitFact));
 
    if(bNegative)
    {
        fLocalValue = -fLocalValue;
    }
 
    if( -1 == nNumDigits )
    {
        nNumDigits = LocaleDataWrapper::getNumDigits();
    }
 
    sal_Int32 nDecimalMark(m_nUIUnitDecimalMark);
 
    if(nDecimalMark > nNumDigits)
    {
        const sal_Int32 nDiff(nDecimalMark - nNumDigits);
        const double fFactor(pow(10.0, static_cast<int>(nDiff)));
 
        fLocalValue /= fFactor;
        nDecimalMark = nNumDigits;
    }
    else if(nDecimalMark < nNumDigits)
    {
        const sal_Int32 nDiff(nNumDigits - nDecimalMark);
        const double fFactor(pow(10.0, static_cast<int>(nDiff)));
 
        fLocalValue *= fFactor;
        nDecimalMark = nNumDigits;
    }
 
    OUStringBuffer aBuf = OUString::number(static_cast<sal_Int32>(fLocalValue + 0.5));
 
    if(nDecimalMark < 0)
    {
        // negative nDecimalMark (decimal point) means: add zeros
        sal_Int32 nCount(-nDecimalMark);
 
        for(sal_Int32 i=0; i<nCount; i++)
            aBuf.append('0');
 
        nDecimalMark = 0;
    }
 
    // the second condition needs to be <= since inside this loop
    // also the leading zero is inserted.
    if (nDecimalMark > 0 && aBuf.getLength() <= nDecimalMark)
    {
        // if necessary, add zeros before the decimal point
        sal_Int32 nCount = nDecimalMark - aBuf.getLength();
 
        if(nCount >= 0 && LocaleDataWrapper::isNumLeadingZero())
            nCount++;
 
        for(sal_Int32 i=0; i<nCount; i++)
            aBuf.insert(0, '0');
    }
 
    const sal_Unicode cDec( rLoc.getNumDecimalSep()[0] );
 
    // insert the decimal mark character
    sal_Int32 nBeforeDecimalMark = aBuf.getLength() - nDecimalMark;
 
    if(nDecimalMark > 0)
        aBuf.insert(nBeforeDecimalMark, cDec);
 
    if(!LocaleDataWrapper::isNumTrailingZeros())
    {
        sal_Int32 aPos=aBuf.getLength()-1;
 
        // Remove all trailing zeros.
        while (aPos>=0 && aBuf[aPos]=='0')
            --aPos;
 
        // Remove decimal if it's the last character.
        if (aPos>=0 && aBuf[aPos]==cDec)
            --aPos;
 
        // Adjust aPos to index first char to be truncated, if any
        if (++aPos<aBuf.getLength())
            aBuf.truncate(aPos);
    }
 
    // if necessary, add separators before every third digit
    if( nBeforeDecimalMark > 3 )
    {
        const OUString& aThoSep( rLoc.getNumThousandSep() );
        if ( !aThoSep.isEmpty() )
        {
            sal_Unicode cTho( aThoSep[0] );
            sal_Int32 i(nBeforeDecimalMark - 3);
 
            while(i > 0)
            {
                aBuf.insert(i, cTho);
                i -= 3;
            }
        }
    }
 
    if (aBuf.isEmpty())
        aBuf.append("0");
 
    if(bNegative)
    {
        aBuf.insert(0, "-");
    }
 
    if(!bNoUnitChars)
        aBuf.append(m_aUIUnitStr);
 
    return aBuf.makeStringAndClear();
}
 
OUString SdrModel::GetAngleString(Degree100 nAngle)
{
    bool bNeg = nAngle < 0_deg100;
 
    if(bNeg)
        nAngle = -nAngle;
 
    OUStringBuffer aBuf;
    aBuf.append(static_cast<sal_Int32>(nAngle));
 
    SvtSysLocale aSysLoc;
    const LocaleDataWrapper& rLoc = aSysLoc.GetLocaleData();
    sal_Int32 nCount = 2;
 
    if(LocaleDataWrapper::isNumLeadingZero())
        nCount++;
 
    while(aBuf.getLength() < nCount)
        aBuf.insert(0, '0');
 
    aBuf.insert(aBuf.getLength()-2, rLoc.getNumDecimalSep()[0]);
 
    if(bNeg)
        aBuf.insert(0, '-');
 
    aBuf.append(DEGREE_CHAR);
 
    return aBuf.makeStringAndClear();
}
 
OUString SdrModel::GetPercentString(const Fraction& rVal)
{
    sal_Int32 nMul(rVal.GetNumerator());
    sal_Int32 nDiv(rVal.GetDenominator());
    bool bNeg {false};
 
    if (nDiv < 0)
    {
        bNeg = !bNeg;
        nDiv = -nDiv;
    }
 
    if (nMul < 0)
    {
        bNeg = !bNeg;
        nMul = -nMul;
    }
 
    sal_Int32 nPct = ((nMul*100) + nDiv/2)/nDiv;
 
    if (bNeg)
        nPct = -nPct;
 
    return OUString::number(nPct) + "%";
}
 
void SdrModel::SetChanged(bool bFlg)
{
    mbChanged = bFlg;
}
 
void SdrModel::RecalcPageNums(bool bMaster)
{
    if(bMaster)
    {
        if (m_nMasterPageNumsDirtyFrom != SAL_MAX_UINT16)
        {
            sal_uInt16 nCount=sal_uInt16(maMasterPages.size());
            for (sal_uInt16 i=m_nMasterPageNumsDirtyFrom; i<nCount; i++) {
                SdrPage* pPg = maMasterPages[i].get();
                pPg->SetPageNum(i);
            }
            m_nMasterPageNumsDirtyFrom = SAL_MAX_UINT16;
        }
    }
    else
    {
        if (m_nPageNumsDirtyFrom != SAL_MAX_UINT16)
        {
            sal_uInt16 nCount=sal_uInt16(maPages.size());
            for (sal_uInt16 i = m_nPageNumsDirtyFrom; i<nCount; i++) {
                SdrPage* pPg = maPages[i].get();
                pPg->SetPageNum(i);
            }
            m_nPageNumsDirtyFrom = SAL_MAX_UINT16;
        }
    }
}
 
void SdrModel::InsertPage(SdrPage* pPage, sal_uInt16 nPos)
{
    sal_uInt16 nCount = GetPageCount();
    if (nPos > nCount)
        nPos = nCount;
 
    maPages.insert(maPages.begin() + nPos, pPage);
    PageListChanged();
    pPage->SetInserted();
    pPage->SetPageNum(nPos);
 
    if (mbMakePageObjectsNamesUnique)
        pPage->MakePageObjectsNamesUnique();
 
    if (nPos<nCount) m_nPageNumsDirtyFrom = std::min(m_nPageNumsDirtyFrom, static_cast<sal_uInt16>(nPos + 1));
    SetChanged();
    SdrHint aHint(SdrHintKind::PageOrderChange, pPage);
    Broadcast(aHint);
}
 
void SdrModel::DeletePage(sal_uInt16 nPgNum)
{
    RemovePage(nPgNum);
}
 
rtl::Reference<SdrPage> SdrModel::RemovePage(sal_uInt16 nPgNum)
{
    rtl::Reference<SdrPage> pPg = maPages[nPgNum];
    maPages.erase(maPages.begin()+nPgNum);
    PageListChanged();
    if (pPg) {
        pPg->SetInserted(false);
    }
    m_nPageNumsDirtyFrom = std::min(m_nPageNumsDirtyFrom, nPgNum);
    SetChanged();
    SdrHint aHint(SdrHintKind::PageOrderChange, pPg.get());
    Broadcast(aHint);
    return pPg;
}
 
void SdrModel::MovePage(sal_uInt16 nPgNum, sal_uInt16 nNewPos)
{
    rtl::Reference<SdrPage> pPg = std::move(maPages[nPgNum]);
    if (pPg) {
        maPages.erase(maPages.begin()+nPgNum); // shortcut to avoid two broadcasts
        PageListChanged();
        pPg->SetInserted(false);
        InsertPage(pPg.get(), nNewPos);
    }
    else
        RemovePage(nPgNum);
}
 
void SdrModel::InsertMasterPage(SdrPage* pPage, sal_uInt16 nPos)
{
    sal_uInt16 nCount=GetMasterPageCount();
    if (nPos>nCount) nPos=nCount;
    maMasterPages.insert(maMasterPages.begin()+nPos,pPage);
    MasterPageListChanged();
    pPage->SetInserted();
    pPage->SetPageNum(nPos);
 
    if (nPos<nCount) {
        m_nMasterPageNumsDirtyFrom = std::min(m_nMasterPageNumsDirtyFrom, static_cast<sal_uInt16>(nPos + 1));
    }
 
    SetChanged();
    SdrHint aHint(SdrHintKind::PageOrderChange, pPage);
    Broadcast(aHint);
}
 
void SdrModel::DeleteMasterPage(sal_uInt16 nPgNum)
{
    RemoveMasterPage(nPgNum);
}
 
rtl::Reference<SdrPage> SdrModel::RemoveMasterPage(sal_uInt16 nPgNum)
{
    rtl::Reference<SdrPage> pRetPg = std::move(maMasterPages[nPgNum]);
    maMasterPages.erase(maMasterPages.begin()+nPgNum);
    MasterPageListChanged();
 
    if(pRetPg)
    {
        // Now delete the links from the normal drawing pages to the deleted master page.
        sal_uInt16 nPageCnt(GetPageCount());
 
        for(sal_uInt16 np(0); np < nPageCnt; np++)
        {
            GetPage(np)->TRG_ImpMasterPageRemoved(*pRetPg);
        }
 
        pRetPg->SetInserted(false);
    }
 
    m_nMasterPageNumsDirtyFrom = std::min(m_nMasterPageNumsDirtyFrom, nPgNum);
    SetChanged();
    SdrHint aHint(SdrHintKind::PageOrderChange, pRetPg.get());
    Broadcast(aHint);
    return pRetPg;
}
 
void SdrModel::MoveMasterPage(sal_uInt16 nPgNum, sal_uInt16 nNewPos)
{
    rtl::Reference<SdrPage> pPg = std::move(maMasterPages[nPgNum]);
    maMasterPages.erase(maMasterPages.begin()+nPgNum);
    MasterPageListChanged();
    if (pPg) {
        pPg->SetInserted(false);
        maMasterPages.insert(maMasterPages.begin()+nNewPos,pPg);
        MasterPageListChanged();
    }
    m_nMasterPageNumsDirtyFrom = std::min(m_nMasterPageNumsDirtyFrom, std::min(nPgNum, nNewPos));
    SetChanged();
    SdrHint aHint(SdrHintKind::PageOrderChange, pPg.get());
    Broadcast(aHint);
}
 
 
void SdrModel::CopyPages(sal_uInt16 nFirstPageNum, sal_uInt16 nLastPageNum,
                         sal_uInt16 nDestPos,
                         bool bUndo, bool bMoveNoCopy)
{
    if( bUndo && !IsUndoEnabled() )
        bUndo = false;
 
    if( bUndo )
        BegUndo(SvxResId(STR_UndoMergeModel));
 
    sal_uInt16 nPageCnt=GetPageCount();
    sal_uInt16 nMaxPage=nPageCnt;
 
    if (nMaxPage!=0)
        nMaxPage--;
    if (nFirstPageNum>nMaxPage)
        nFirstPageNum=nMaxPage;
    if (nLastPageNum>nMaxPage)
        nLastPageNum =nMaxPage;
    bool bReverse=nLastPageNum<nFirstPageNum;
    if (nDestPos>nPageCnt)
        nDestPos=nPageCnt;
 
    // at first, save the pointers of the affected pages in an array
    sal_uInt16 nPageNum=nFirstPageNum;
    sal_uInt16 nCopyCnt=((!bReverse)?(nLastPageNum-nFirstPageNum):(nFirstPageNum-nLastPageNum))+1;
    std::unique_ptr<SdrPage*[]> pPagePtrs(new SdrPage*[nCopyCnt]);
    sal_uInt16 nCopyNum;
    for(nCopyNum=0; nCopyNum<nCopyCnt; nCopyNum++)
    {
        pPagePtrs[nCopyNum]=GetPage(nPageNum);
        if (bReverse)
            nPageNum--;
        else
            nPageNum++;
    }
 
    // now copy the pages
    sal_uInt16 nDestNum=nDestPos;
    for (nCopyNum=0; nCopyNum<nCopyCnt; nCopyNum++)
    {
        rtl::Reference<SdrPage> pPg = pPagePtrs[nCopyNum];
        sal_uInt16 nPageNum2=pPg->GetPageNum();
        if (!bMoveNoCopy)
        {
            const SdrPage* pPg1=GetPage(nPageNum2);
 
            // Clone to local model
            pPg = pPg1->CloneSdrPage(*this);
 
            InsertPage(pPg.get(), nDestNum);
            if (bUndo)
                AddUndo(GetSdrUndoFactory().CreateUndoCopyPage(*pPg));
            nDestNum++;
        }
        else
        {
            // TODO: Move is untested!
            if (nDestNum>nPageNum2)
                nDestNum--;
 
            if(bUndo)
                AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*GetPage(nPageNum2),nPageNum2,nDestNum));
 
            pPg=RemovePage(nPageNum2);
            InsertPage(pPg.get(), nDestNum);
            nDestNum++;
        }
 
        if(bReverse)
            nPageNum2--;
        else
            nPageNum2++;
    }
 
    pPagePtrs.reset();
    if(bUndo)
        EndUndo();
}
 
void SdrModel::Merge(SdrModel& rSourceModel,
                     sal_uInt16 nFirstPageNum, sal_uInt16 nLastPageNum,
                     sal_uInt16 nDestPos,
                     bool bMergeMasterPages, bool bAllMasterPages,
                     bool bUndo, bool bTreadSourceAsConst)
{
    if (&rSourceModel==this)
    {
        CopyPages(nFirstPageNum,nLastPageNum,nDestPos,bUndo,!bTreadSourceAsConst);
        return;
    }
 
    if( bUndo && !IsUndoEnabled() )
        bUndo = false;
 
    if (bUndo)
        BegUndo(SvxResId(STR_UndoMergeModel));
 
    sal_uInt16 nSrcPageCnt=rSourceModel.GetPageCount();
    sal_uInt16 nSrcMasterPageCnt=rSourceModel.GetMasterPageCount();
    sal_uInt16 nDstMasterPageCnt=GetMasterPageCount();
    bool bInsPages=(nFirstPageNum<nSrcPageCnt || nLastPageNum<nSrcPageCnt);
    sal_uInt16 nMaxSrcPage=nSrcPageCnt; if (nMaxSrcPage!=0) nMaxSrcPage--;
    if (nFirstPageNum>nMaxSrcPage) nFirstPageNum=nMaxSrcPage;
    if (nLastPageNum>nMaxSrcPage)  nLastPageNum =nMaxSrcPage;
    bool bReverse=nLastPageNum<nFirstPageNum;
 
    std::unique_ptr<sal_uInt16[]> pMasterMap;
    std::unique_ptr<bool[]> pMasterNeed;
    sal_uInt16    nMasterNeed=0;
    if (bMergeMasterPages && nSrcMasterPageCnt!=0) {
        // determine which MasterPages from rSrcModel we need
        pMasterMap.reset(new sal_uInt16[nSrcMasterPageCnt]);
        pMasterNeed.reset(new bool[nSrcMasterPageCnt]);
        memset(pMasterMap.get(),0xFF,nSrcMasterPageCnt*sizeof(sal_uInt16));
        if (bAllMasterPages) {
            memset(pMasterNeed.get(), true, nSrcMasterPageCnt * sizeof(bool));
        } else {
            memset(pMasterNeed.get(), false, nSrcMasterPageCnt * sizeof(bool));
            sal_uInt16 nStart= bReverse ? nLastPageNum : nFirstPageNum;
            sal_uInt16 nEnd= bReverse ? nFirstPageNum : nLastPageNum;
            for (sal_uInt16 i=nStart; i<=nEnd; i++) {
                const SdrPage* pPg=rSourceModel.GetPage(i);
                if(pPg->TRG_HasMasterPage())
                {
                    SdrPage& rMasterPage = pPg->TRG_GetMasterPage();
                    sal_uInt16 nMPgNum(rMasterPage.GetPageNum());
 
                    if(nMPgNum < nSrcMasterPageCnt)
                    {
                        pMasterNeed[nMPgNum] = true;
                    }
                }
            }
        }
        // now determine the Mapping of the MasterPages
        sal_uInt16 nCurrentMaPagNum=nDstMasterPageCnt;
        for (sal_uInt16 i=0; i<nSrcMasterPageCnt; i++) {
            if (pMasterNeed[i]) {
                pMasterMap[i]=nCurrentMaPagNum;
                nCurrentMaPagNum++;
                nMasterNeed++;
            }
        }
    }
 
    // get the MasterPages
    if (pMasterMap && pMasterNeed && nMasterNeed!=0) {
        for (sal_uInt16 i=nSrcMasterPageCnt; i>0;) {
            i--;
            if (pMasterNeed[i])
            {
                // Always Clone to new model
                const SdrPage* pPg1(rSourceModel.GetMasterPage(i));
                rtl::Reference<SdrPage> pPg = pPg1->CloneSdrPage(*this);
 
                if(!bTreadSourceAsConst)
                {
                    // if requested, delete original/modify original model
                    rSourceModel.RemoveMasterPage(i);
                }
 
                if (pPg!=nullptr) {
                    // Now append all of them to the end of the DstModel.
                    // Don't use InsertMasterPage(), because everything is
                    // inconsistent until all are in.
                    maMasterPages.insert(maMasterPages.begin()+nDstMasterPageCnt, pPg);
                    MasterPageListChanged();
                    pPg->SetInserted();
                    m_nMasterPageNumsDirtyFrom = std::min(m_nMasterPageNumsDirtyFrom, nDstMasterPageCnt);
                    if (bUndo) AddUndo(GetSdrUndoFactory().CreateUndoNewPage(*pPg));
                } else {
                    OSL_FAIL("SdrModel::Merge(): MasterPage not found in SourceModel.");
                }
            }
        }
    }
 
    // get the drawing pages
    if (bInsPages) {
        sal_uInt16 nSourcePos=nFirstPageNum;
        sal_uInt16 nMergeCount=sal_uInt16(std::abs(static_cast<tools::Long>(static_cast<tools::Long>(nFirstPageNum)-nLastPageNum))+1);
        if (nDestPos>GetPageCount()) nDestPos=GetPageCount();
        while (nMergeCount>0)
        {
            // Always Clone to new model
            const SdrPage* pPg1(rSourceModel.GetPage(nSourcePos));
            rtl::Reference<SdrPage> pPg = pPg1->CloneSdrPage(*this);
 
            if(!bTreadSourceAsConst)
            {
                // if requested, delete original/modify original model
                rSourceModel.RemovePage(nSourcePos);
            }
 
            if (pPg!=nullptr) {
                InsertPage(pPg.get(),nDestPos);
                if (bUndo) AddUndo(GetSdrUndoFactory().CreateUndoNewPage(*pPg));
 
                if(pPg->TRG_HasMasterPage())
                {
                    SdrPage& rMasterPage = pPg->TRG_GetMasterPage();
                    sal_uInt16 nMaPgNum(rMasterPage.GetPageNum());
 
                    if (bMergeMasterPages)
                    {
                        sal_uInt16 nNewNum(0xFFFF);
 
                        if(pMasterMap)
                        {
                            nNewNum = pMasterMap[nMaPgNum];
                        }
 
                        if(nNewNum != 0xFFFF)
                        {
                            // tdf#90357 here pPg and the to-be-set new masterpage are parts of the new model
                            // already, but the currently set masterpage is part of the old model. Remove master
                            // page from already cloned page to prevent creating wrong undo action that can
                            // eventually crash the app.
                            // Do *not* remove it directly after cloning - the old masterpage is still needed
                            // later to find the new to-be-set masterpage.
                            pPg->TRG_ClearMasterPage();
 
                            if(bUndo)
                            {
                                AddUndo(GetSdrUndoFactory().CreateUndoPageChangeMasterPage(*pPg));
                            }
 
                            pPg->TRG_SetMasterPage(*GetMasterPage(nNewNum));
                        }
                        DBG_ASSERT(nNewNum!=0xFFFF,"SdrModel::Merge(): Something is crooked with the mapping of the MasterPages.");
                    } else {
                        if (nMaPgNum>=nDstMasterPageCnt) {
                            // This is outside of the original area of the MasterPage of the DstModel.
                            pPg->TRG_ClearMasterPage();
                        }
                    }
                }
 
            } else {
                OSL_FAIL("SdrModel::Merge(): Drawing page not found in SourceModel.");
            }
            nDestPos++;
            if (bReverse) nSourcePos--;
            else if (bTreadSourceAsConst) nSourcePos++;
            nMergeCount--;
        }
    }
 
    pMasterMap.reset();
    pMasterNeed.reset();
 
    m_nMasterPageNumsDirtyFrom = 0;
    m_nPageNumsDirtyFrom = 0;
 
    SetChanged();
    // TODO: Missing: merging and mapping of layers
    // at the objects as well as at the MasterPageDescriptors
    if (bUndo) EndUndo();
}
 
void SdrModel::SetStarDrawPreviewMode(bool bPreview)
{
    if (!bPreview && m_bStarDrawPreviewMode && GetPageCount())
    {
        // Resetting is not allowed, because the Model might not be loaded completely
        SAL_WARN("svx", "SdrModel::SetStarDrawPreviewMode(): Resetting not allowed, because Model might not be complete.");
    }
    else
    {
        m_bStarDrawPreviewMode = bPreview;
    }
}
 
void SdrModel::setTheme(std::shared_ptr<model::Theme> const& pTheme)
{
    mpImpl->mpTheme = pTheme;
}
 
std::shared_ptr<model::Theme> const& SdrModel::getTheme() const
{
    return mpImpl->mpTheme;
}
 
uno::Reference< frame::XModel > const & SdrModel::getUnoModel()
{
    if( !mxUnoModel.is() )
        mxUnoModel = createUnoModel();
 
    return mxUnoModel;
}
 
void SdrModel::setUnoModel(const uno::Reference<frame::XModel>& xModel)
{
    mxUnoModel = xModel;
}
 
void SdrModel::adaptSizeAndBorderForAllPages(
    const Size& /*rNewSize*/,
    tools::Long /*nLeft*/,
    tools::Long /*nRight*/,
    tools::Long /*nUpper*/,
    tools::Long /*nLower*/)
{
    // base implementation does currently nothing. It may be added if needed,
    // but we are on SdrModel level here, thus probably have not enough information
    // to do this for higher-level (derived) Models (e.g. Draw/Impress)
}
 
uno::Reference< frame::XModel > SdrModel::createUnoModel()
{
    OSL_FAIL( "SdrModel::createUnoModel() - base implementation should not be called!" );
    return nullptr;
}
 
void SdrModel::setLock( bool bLock )
{
    if( mbModelLocked != bLock )
    {
        // #i120437# need to set first, else ImpReformatAllEdgeObjects will do nothing
        mbModelLocked = bLock;
 
        if( !bLock )
        {
            ImpReformatAllEdgeObjects();
        }
    }
}
 
 
void SdrModel::MigrateItemSet( const SfxItemSet* pSourceSet, SfxItemSet* pDestSet, SdrModel& rNewModel )
{
    if( !(pSourceSet && pDestSet && (pSourceSet != pDestSet )) )
        return;
 
    SfxWhichIter aWhichIter(*pSourceSet);
    sal_uInt16 nWhich(aWhichIter.FirstWhich());
    const SfxPoolItem *pPoolItem;
 
    while(nWhich)
    {
        if(SfxItemState::SET == aWhichIter.GetItemState(false, &pPoolItem))
        {
            std::unique_ptr<SfxPoolItem> pResultItem;
 
            switch( nWhich )
            {
            case XATTR_FILLBITMAP:
                pResultItem = static_cast<const XFillBitmapItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_LINEDASH:
                pResultItem = static_cast<const XLineDashItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_LINESTART:
                pResultItem = static_cast<const XLineStartItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_LINEEND:
                pResultItem = static_cast<const XLineEndItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_FILLGRADIENT:
                pResultItem = static_cast<const XFillGradientItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_FILLFLOATTRANSPARENCE:
                // allow all kinds of XFillFloatTransparenceItem to be set
                pResultItem = static_cast<const XFillFloatTransparenceItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            case XATTR_FILLHATCH:
                pResultItem = static_cast<const XFillHatchItem*>(pPoolItem)->checkForUniqueItem( rNewModel );
                break;
            }
 
            // set item
            if( pResultItem )
                pDestSet->Put(std::move(pResultItem));
            else
                pDestSet->Put(*pPoolItem);
        }
        nWhich = aWhichIter.NextWhich();
    }
}
 
 
void SdrModel::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars)
{
    mpForbiddenCharactersTable = xForbiddenChars;
 
    ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
    ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
}
 
 
void SdrModel::SetCharCompressType( CharCompressType nType )
{
    if( nType != mnCharCompressType )
    {
        mnCharCompressType = nType;
        ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
        ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
    }
}
 
void SdrModel::SetKernAsianPunctuation( bool bEnabled )
{
    if( mbKernAsianPunctuation != bEnabled )
    {
        mbKernAsianPunctuation = bEnabled;
        ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
        ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
    }
}
 
void SdrModel::SetAddExtLeading( bool bEnabled )
{
    if( mbAddExtLeading != bEnabled )
    {
        mbAddExtLeading = bEnabled;
        ImpSetOutlinerDefaults( m_pDrawOutliner.get() );
        ImpSetOutlinerDefaults( m_pHitTestOutliner.get() );
    }
}
 
void SdrModel::SetCompatibilityFlag(SdrCompatibilityFlag eFlag, bool bEnabled)
{
    switch (eFlag)
    {
        case SdrCompatibilityFlag::AnchoredTextOverflowLegacy:
            mpImpl->mbAnchoredTextOverflowLegacy = bEnabled;
            break;
        case SdrCompatibilityFlag::LegacyFontwork:
            mpImpl->mbLegacyFontwork = bEnabled;
            break;
        case SdrCompatibilityFlag::ConnectorUseSnapRect:
            mpImpl->mbConnectorUseSnapRect = bEnabled;
            break;
        case SdrCompatibilityFlag::IgnoreBreakAfterMultilineField:
            mpImpl->mbIgnoreBreakAfterMultilineField = bEnabled;
            break;
    }
}
 
bool SdrModel::GetCompatibilityFlag(SdrCompatibilityFlag eFlag) const
{
    switch (eFlag)
    {
        case SdrCompatibilityFlag::AnchoredTextOverflowLegacy:
            return mpImpl->mbAnchoredTextOverflowLegacy;
        case SdrCompatibilityFlag::LegacyFontwork:
            return mpImpl->mbLegacyFontwork;
        case SdrCompatibilityFlag::ConnectorUseSnapRect:
            return mpImpl->mbConnectorUseSnapRect;
        case SdrCompatibilityFlag::IgnoreBreakAfterMultilineField:
            return mpImpl->mbIgnoreBreakAfterMultilineField;
        default:
            return false;
    }
}
 
void SdrModel::ReformatAllTextObjects()
{
    ImpReformatAllTextObjects();
}
 
std::unique_ptr<SdrOutliner> SdrModel::createOutliner( OutlinerMode nOutlinerMode )
{
    if( !mpOutlinerCache )
        mpOutlinerCache.reset(new SdrOutlinerCache(this));
 
    return mpOutlinerCache->createOutliner( nOutlinerMode );
}
 
std::vector<SdrOutliner*> SdrModel::GetActiveOutliners() const
{
    std::vector< SdrOutliner* > aRet(mpOutlinerCache ? mpOutlinerCache->GetActiveOutliners() : std::vector< SdrOutliner* >());
    aRet.push_back(m_pDrawOutliner.get());
    aRet.push_back(m_pHitTestOutliner.get());
 
    return aRet;
}
 
void SdrModel::disposeOutliner( std::unique_ptr<SdrOutliner> pOutliner )
{
    if( mpOutlinerCache )
        mpOutlinerCache->disposeOutliner( std::move(pOutliner) );
}
 
SvxNumType SdrModel::GetPageNumType() const
{
    return SVX_NUM_ARABIC;
}
 
void SdrModel::ReadUserDataSequenceValue(const beans::PropertyValue* pValue)
{
    if (pValue->Name == "AnchoredTextOverflowLegacy")
    {
        bool bBool = false;
        if (pValue->Value >>= bBool)
        {
            mpImpl->mbAnchoredTextOverflowLegacy = bBool;
        }
    }
    else if (pValue->Name == "ConnectorUseSnapRect")
    {
        bool bBool = false;
        if (pValue->Value >>= bBool)
        {
            mpImpl->mbConnectorUseSnapRect = bBool;
        }
    }
    else if (pValue->Name == "LegacySingleLineFontwork")
    {
        bool bBool = false;
        if ((pValue->Value >>= bBool) && mpImpl->mbLegacyFontwork != bBool)
        {
            mpImpl->mbLegacyFontwork = bBool;
            // tdf#148000 hack: reset all CustomShape geometry as they may depend on this property
            // Ideally this ReadUserDataSequenceValue should be called before geometry creation
            // Once the calling order will be fixed, this hack will not be needed.
            for (size_t i = 0; i < maPages.size(); ++i)
            {
                if (const SdrPage* pPage = maPages[i].get())
                {
                    SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups);
                    while (aIter.IsMore())
                    {
                        SdrObject* pTempObj = aIter.Next();
                        if (SdrObjCustomShape* pShape = dynamic_cast<SdrObjCustomShape*>(pTempObj))
                        {
                            pShape->InvalidateRenderGeometry();
                        }
                    }
                }
            }
        }
    }
    else if (pValue->Name == "IgnoreBreakAfterMultilineField")
    {
        bool bBool = false;
        if (pValue->Value >>= bBool)
        {
            mpImpl->mbIgnoreBreakAfterMultilineField = bBool;
        }
    }
}
 
void SdrModel::WriteUserDataSequence(uno::Sequence <beans::PropertyValue>& rValues)
{
    std::vector< std::pair< OUString, uno::Any > > aUserData
    {
        { "AnchoredTextOverflowLegacy", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::AnchoredTextOverflowLegacy)) },
        { "LegacySingleLineFontwork", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::LegacyFontwork)) },
        { "ConnectorUseSnapRect", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect)) },
        { "IgnoreBreakAfterMultilineField", uno::Any(GetCompatibilityFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField)) }
    };
 
    const sal_Int32 nOldLength = rValues.getLength();
    rValues.realloc(nOldLength + aUserData.size());
 
    beans::PropertyValue* pValue = &(rValues.getArray()[nOldLength]);
 
    for (const auto &aIter : aUserData)
    {
        pValue->Name = aIter.first;
        pValue->Value = aIter.second;
        ++pValue;
    }
}
 
const SdrPage* SdrModel::GetPage(sal_uInt16 nPgNum) const
{
    return nPgNum < maPages.size() ? maPages[nPgNum].get() : nullptr;
}
 
SdrPage* SdrModel::GetPage(sal_uInt16 nPgNum)
{
    return nPgNum < maPages.size() ? maPages[nPgNum].get() : nullptr;
}
 
sal_uInt16 SdrModel::GetPageCount() const
{
    return sal_uInt16(maPages.size());
}
 
void SdrModel::PageListChanged()
{
}
 
TextChain *SdrModel::GetTextChain() const
{
    return m_pTextChain.get();
}
 
const SdrPage* SdrModel::GetMasterPage(sal_uInt16 nPgNum) const
{
    DBG_ASSERT(nPgNum < maMasterPages.size(), "SdrModel::GetMasterPage: Access out of range (!)");
    return maMasterPages[nPgNum].get();
}
 
SdrPage* SdrModel::GetMasterPage(sal_uInt16 nPgNum)
{
    DBG_ASSERT(nPgNum < maMasterPages.size(), "SdrModel::GetMasterPage: Access out of range (!)");
    return maMasterPages[nPgNum].get();
}
 
sal_uInt16 SdrModel::GetMasterPageCount() const
{
    return sal_uInt16(maMasterPages.size());
}
 
void SdrModel::MasterPageListChanged()
{
}
 
void SdrModel::SetSdrUndoManager( SfxUndoManager* pUndoManager )
{
    mpImpl->mpUndoManager = pUndoManager;
}
 
SfxUndoManager* SdrModel::GetSdrUndoManager() const
{
    return mpImpl->mpUndoManager;
}
 
SdrUndoFactory& SdrModel::GetSdrUndoFactory() const
{
    if( !mpImpl->mpUndoFactory )
        mpImpl->mpUndoFactory = new SdrUndoFactory;
    return *mpImpl->mpUndoFactory;
}
 
void SdrModel::SetSdrUndoFactory( SdrUndoFactory* pUndoFactory )
{
    if( pUndoFactory && (pUndoFactory != mpImpl->mpUndoFactory) )
    {
        delete mpImpl->mpUndoFactory;
        mpImpl->mpUndoFactory = pUndoFactory;
    }
}
 
void SdrModel::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrModel"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
 
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("maMasterPages"));
    for (size_t i = 0; i < maMasterPages.size(); ++i)
    {
        if (const SdrPage* pPage = maMasterPages[i].get())
        {
            pPage->dumpAsXml(pWriter);
        }
    }
    (void)xmlTextWriterEndElement(pWriter);
 
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("maPages"));
    for (size_t i = 0; i < maPages.size(); ++i)
    {
        if (const SdrPage* pPage = maPages[i].get())
        {
            pPage->dumpAsXml(pWriter);
        }
    }
    (void)xmlTextWriterEndElement(pWriter);
 
    if (mpImpl->mpTheme)
    {
        mpImpl->mpTheme->dumpAsXml(pWriter);
    }
 
    (void)xmlTextWriterEndElement(pWriter);
}
 
const uno::Sequence<sal_Int8>& SdrModel::getUnoTunnelId()
{
    static const comphelper::UnoIdInit theSdrModelUnoTunnelImplementationId;
    return theSdrModelUnoTunnelImplementationId.getSeq();
}
 
 
SdrHint::SdrHint(SdrHintKind eNewHint)
:   SfxHint(SfxHintId::ThisIsAnSdrHint),
    meHint(eNewHint),
    mpObj(nullptr),
    mpPage(nullptr)
{
}
 
SdrHint::SdrHint(SdrHintKind eNewHint, const SdrObject& rNewObj)
:   SfxHint(SfxHintId::ThisIsAnSdrHint),
    meHint(eNewHint),
    mpObj(&rNewObj),
    mpPage(rNewObj.getSdrPageFromSdrObject())
{
}
 
SdrHint::SdrHint(SdrHintKind eNewHint, const SdrPage* pPage)
:   SfxHint(SfxHintId::ThisIsAnSdrHint),
    meHint(eNewHint),
    mpObj(nullptr),
    mpPage(pPage)
{
}
 
SdrHint::SdrHint(SdrHintKind eNewHint, const SdrObject& rNewObj, const SdrPage* pPage)
:   SfxHint(SfxHintId::ThisIsAnSdrHint),
    meHint(eNewHint),
    mpObj(&rNewObj),
    mpPage(pPage)
{
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'truncate' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V1053 Calling the 'DeletePage' virtual function indirectly in the destructor may lead to unexpected result at runtime. Check lines: 'svdmodel.cxx:222', 'svdmodel.cxx:217', 'svdmodel.cxx:606', 'svdmodel.hxx:405'.