/* -*- 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 <scitems.hxx>
#include <comphelper/fileformat.h>
#include <comphelper/processfactory.hxx>
#include <officecfg/Office/Common.hxx>
#include <tools/urlobj.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/langitem.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/printer.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/viewsh.hxx>
#include <svl/flagitem.hxx>
#include <svl/intitem.hxx>
#include <svl/numformat.hxx>
#include <svl/zformat.hxx>
#include <svl/ctloptions.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>

#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld.hxx>
#include <vcl/TaskStopwatch.hxx>

#include <inputopt.hxx>
#include <global.hxx>
#include <table.hxx>
#include <column.hxx>
#include <poolhelp.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <docoptio.hxx>
#include <viewopti.hxx>
#include <scextopt.hxx>
#include <rechead.hxx>
#include <ddelink.hxx>
#include <scmatrix.hxx>
#include <arealink.hxx>
#include <patattr.hxx>
#include <editutil.hxx>
#include <progress.hxx>
#include <document.hxx>
#include <chartlis.hxx>
#include <chartlock.hxx>
#include <refupdat.hxx>
#include <markdata.hxx>
#include <scmod.hxx>
#include <externalrefmgr.hxx>
#include <globstr.hrc>
#include <strings.hrc>
#include <sc.hrc>
#include <charthelper.hxx>
#include <macromgr.hxx>
#include <docuno.hxx>
#include <scresid.hxx>
#include <columniterator.hxx>
#include <globalnames.hxx>
#include <stringutil.hxx>
#include <documentlinkmgr.hxx>
#include <tokenarray.hxx>
#include <recursionhelper.hxx>

#include <memory>
#include <utility>

using namespace com::sun::star;

namespace {

sal_uInt16 getScaleValue(SfxStyleSheetBase& rStyle, sal_uInt16 nWhich)
{
    return static_cast<const SfxUInt16Item&>(rStyle.GetItemSet().Get(nWhich)).GetValue();
}

}

void ScDocument::ImplCreateOptions()
{
    pDocOptions.reset( new ScDocOptions() );
    pViewOptions.reset( new ScViewOptions() );
}

void ScDocument::ImplDeleteOptions()
{
    pDocOptions.reset();
    pViewOptions.reset();
    pExtDocOptions.reset();
}

SfxPrinter* ScDocument::GetPrinter(bool bCreateIfNotExist)
{
    if (!mpPrinter && bCreateIfNotExist && mxPoolHelper)
    {
        auto pSet = std::make_unique<SfxItemSet>(
            SfxItemSet::makeFixedSfxItemSet<SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN,
                                            SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC,
                                            SID_PRINT_SELECTEDSHEET, SID_PRINT_SELECTEDSHEET,
                                            SID_SCPRINTOPTIONS, SID_SCPRINTOPTIONS>(*mxPoolHelper->GetDocPool()));
        SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
        if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
            nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
        if (officecfg::Office::Common::Print::Warning::PaperSize::get())
            nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
        pSet->Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
        pSet->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );

        mpPrinter = VclPtr<SfxPrinter>::Create( std::move(pSet) );
        mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
        UpdateDrawPrinter();
        mpPrinter->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
    }

    return mpPrinter;
}

void ScDocument::SetPrinter( VclPtr<SfxPrinter> const & pNewPrinter )
{
    if ( pNewPrinter == mpPrinter.get() )
    {
        //  #i6706# SetPrinter is called with the same printer again if
        //  the JobSetup has changed. In that case just call UpdateDrawPrinter
        //  (SetRefDevice for drawing layer) because of changed text sizes.
        UpdateDrawPrinter();
    }
    else
    {
        ScopedVclPtr<SfxPrinter> xKeepAlive( mpPrinter );
        mpPrinter = pNewPrinter;
        UpdateDrawPrinter();
        mpPrinter->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
    }
    InvalidateTextWidth(nullptr, nullptr, false);     // in both cases
}

void ScDocument::SetPrintOptions()
{
    if ( !mpPrinter ) GetPrinter(); // this sets mpPrinter
    OSL_ENSURE( mpPrinter, "Error in printer creation :-/" );

    if ( !mpPrinter )
        return;

    SfxItemSet aOptSet( mpPrinter->GetOptions() );

    SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
    if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
        nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
    if (officecfg::Office::Common::Print::Warning::PaperSize::get())
        nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
    aOptSet.Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
    aOptSet.Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );

    mpPrinter->SetOptions( aOptSet );
}

VirtualDevice* ScDocument::GetVirtualDevice_100th_mm()
{
    if (!mpVirtualDevice_100th_mm)
    {
#ifdef IOS
        mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::GRAYSCALE);
#else
        mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
#endif
        mpVirtualDevice_100th_mm->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1);
        MapMode aMapMode( mpVirtualDevice_100th_mm->GetMapMode() );
        aMapMode.SetMapUnit( MapUnit::Map100thMM );
        mpVirtualDevice_100th_mm->SetMapMode( aMapMode );
    }
    return mpVirtualDevice_100th_mm;
}

OutputDevice* ScDocument::GetRefDevice(bool bForceVirtDev)
{
    // Create printer like ref device, see Writer...
    OutputDevice* pRefDevice = nullptr;
    if (!bForceVirtDev && ScModule::get()->GetInputOptions().GetTextWysiwyg())
    {
        pRefDevice = GetPrinter();
        SAL_WARN_IF(!pRefDevice, "sc", "unable to get a printer, fallback to virdev");
    }
    if (!pRefDevice)
        pRefDevice = GetVirtualDevice_100th_mm();
    return pRefDevice;
}

void ScDocument::ModifyStyleSheet( SfxStyleSheetBase& rStyleSheet,
                                   const SfxItemSet&  rChanges )
{
    SfxItemSet& rSet = rStyleSheet.GetItemSet();

    switch ( rStyleSheet.GetFamily() )
    {
        case SfxStyleFamily::Page:
            {
                const sal_uInt16 nOldScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
                const sal_uInt16 nOldScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);
                rSet.Put( rChanges );
                const sal_uInt16 nNewScale        = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
                const sal_uInt16 nNewScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);

                if ( (nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages) )
                    InvalidateTextWidth( rStyleSheet.GetName() );

                if( SvtCTLOptions::IsCTLFontEnabled() )
                {
                    if( rChanges.GetItemState(ATTR_WRITINGDIR ) == SfxItemState::SET )
                        ScChartHelper::DoUpdateAllCharts( *this );
                }
            }
            break;

        case SfxStyleFamily::Para:
            {
                bool bNumFormatChanged;
                if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
                        rSet, rChanges ) )
                    InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged );

                for (SCTAB nTab=0; nTab<=MAXTAB; ++nTab)
                    if (maTabs[nTab])
                        maTabs[nTab]->SetStreamValid( false );

                sal_uInt32 nOldFormat =
                    rSet.Get( ATTR_VALUE_FORMAT ).GetValue();
                sal_uInt32 nNewFormat =
                    rChanges.Get( ATTR_VALUE_FORMAT ).GetValue();
                LanguageType eNewLang, eOldLang;
                eNewLang = eOldLang = LANGUAGE_DONTKNOW;
                if ( nNewFormat != nOldFormat )
                {
                    SvNumberFormatter* pFormatter = GetFormatTable();
                    eOldLang = pFormatter->GetEntry( nOldFormat )->GetLanguage();
                    eNewLang = pFormatter->GetEntry( nNewFormat )->GetLanguage();
                }

                // Explanation to Items in rChanges:
                //  Set Item        - take over change
                //  Dontcare        - Set Default
                //  Default         - No change
                // ("no change" is not possible with PutExtended, thus the loop)
                for (sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; nWhich++)
                {
                    const SfxPoolItem* pItem;
                    SfxItemState eState = rChanges.GetItemState( nWhich, false, &pItem );
                    if ( eState == SfxItemState::SET )
                        rSet.Put( *pItem );
                    else if ( eState == SfxItemState::INVALID )
                        rSet.ClearItem( nWhich );
                    // when Default nothing
                }

                if ( eNewLang != eOldLang )
                    rSet.Put(
                        SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) );
            }
            break;
        default:
        {
            // added to avoid warnings
        }
    }
}

void ScDocument::CopyStdStylesFrom( const ScDocument& rSrcDoc )
{
    // number format exchange list has to be handled here, too
    NumFmtMergeHandler aNumFmtMergeHdl(*this, rSrcDoc);
    mxPoolHelper->GetStylePool()->CopyStdStylesFrom( rSrcDoc.mxPoolHelper->GetStylePool() );
}

void ScDocument::InvalidateTextWidth( std::u16string_view rStyleName )
{
    const SCTAB nCount = GetTableCount();
    for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
        if ( maTabs[i]->GetPageStyle() == rStyleName )
            InvalidateTextWidth( i );
}

void ScDocument::InvalidateTextWidth( SCTAB nTab )
{
    ScAddress aAdrFrom( 0,    0,        nTab );
    ScAddress aAdrTo  ( MaxCol(), MaxRow(), nTab );
    InvalidateTextWidth( &aAdrFrom, &aAdrTo, false );
}

bool ScDocument::IsPageStyleInUse( std::u16string_view rStrPageStyle, SCTAB* pInTab )
{
    bool         bInUse = false;
    const SCTAB nCount = GetTableCount();
    SCTAB i;

    for ( i = 0; !bInUse && i < nCount && maTabs[i]; i++ )
        bInUse = ( maTabs[i]->GetPageStyle() == rStrPageStyle );

    if ( pInTab )
        *pInTab = i-1;

    return bInUse;
}

bool ScDocument::RemovePageStyleInUse( std::u16string_view rStyle )
{
    bool bWasInUse = false;
    const SCTAB nCount = GetTableCount();

    for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
        if ( maTabs[i]->GetPageStyle() == rStyle )
        {
            bWasInUse = true;
            maTabs[i]->SetPageStyle( ScResId(STR_STYLENAME_STANDARD) );
        }

    return bWasInUse;
}

bool ScDocument::RenamePageStyleInUse( std::u16string_view rOld, const OUString& rNew )
{
    bool bWasInUse = false;
    const SCTAB nCount = GetTableCount();

    for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
        if ( maTabs[i]->GetPageStyle() == rOld )
        {
            bWasInUse = true;
            maTabs[i]->SetPageStyle( rNew );
        }

    return bWasInUse;
}

EEHorizontalTextDirection ScDocument::GetEditTextDirection(SCTAB nTab) const
{
    EEHorizontalTextDirection eRet = EEHorizontalTextDirection::Default;

    OUString aStyleName = GetPageStyle( nTab );
    SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( aStyleName, SfxStyleFamily::Page );
    if ( pStyle )
    {
        SfxItemSet& rStyleSet = pStyle->GetItemSet();
        SvxFrameDirection eDirection =
            rStyleSet.Get( ATTR_WRITINGDIR ).GetValue();

        if ( eDirection == SvxFrameDirection::Horizontal_LR_TB )
            eRet = EEHorizontalTextDirection::L2R;
        else if ( eDirection == SvxFrameDirection::Horizontal_RL_TB )
            eRet = EEHorizontalTextDirection::R2L;
        // else (invalid for EditEngine): keep "default"
    }

    return eRet;
}

ScMacroManager* ScDocument::GetMacroManager()
{
    if (!mpMacroMgr)
        mpMacroMgr.reset(new ScMacroManager(*this));
    return mpMacroMgr.get();
}

void ScDocument::FillMatrix(
    ScMatrix& rMat, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    if (nCol1 > nCol2 || nRow1 > nRow2)
        return;

    SCSIZE nC, nR;
    rMat.GetDimensions(nC, nR);
    if (static_cast<SCROW>(nR) != nRow2 - nRow1 + 1 || static_cast<SCCOL>(nC) != nCol2 - nCol1 + 1)
        return;

    pTab->FillMatrix(rMat, nCol1, nRow1, nCol2, nRow2, pPool);
}

void ScDocument::SetFormulaResults( const ScAddress& rTopPos, const double* pResults, size_t nLen )
{
    ScTable* pTab = FetchTable(rTopPos.Tab());
    if (!pTab)
        return;

    pTab->SetFormulaResults(rTopPos.Col(), rTopPos.Row(), pResults, nLen);
}

void ScDocument::CalculateInColumnInThread( ScInterpreterContext& rContext, const ScRange& rCalcRange, unsigned nThisThread, unsigned nThreadsTotal)
{
    ScTable* pTab = FetchTable(rCalcRange.aStart.Tab());
    if (!pTab)
        return;

    assert(IsThreadedGroupCalcInProgress());

    maThreadSpecific.pContext = &rContext;
    pTab->CalculateInColumnInThread(rContext, rCalcRange.aStart.Col(), rCalcRange.aEnd.Col(), rCalcRange.aStart.Row(), rCalcRange.aEnd.Row(), nThisThread, nThreadsTotal);

    assert(IsThreadedGroupCalcInProgress());
    maThreadSpecific.pContext = nullptr;
    // If any of the thread_local data would cause problems if they stay around for too long
    // (and e.g. outlive the ScDocument), clean them up here, they cannot be cleaned up
    // later from the main thread.
    if(maThreadSpecific.xRecursionHelper)
        maThreadSpecific.xRecursionHelper->Clear();
}

void ScDocument::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen, SCTAB nTab, ScInterpreter* pInterpreter )
{
    assert(!IsThreadedGroupCalcInProgress());
    for( const DelayedSetNumberFormat& data : GetNonThreadedContext().maDelayedSetNumberFormat)
        SetNumberFormat( ScAddress( data.mCol, data.mRow, nTab ), data.mnNumberFormat );
    GetNonThreadedContext().maDelayedSetNumberFormat.clear();

    ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->HandleStuffAfterParallelCalculation(nColStart, nColEnd, nRow, nLen, pInterpreter);
}

void ScDocument::InvalidateTextWidth( const ScAddress* pAdrFrom, const ScAddress* pAdrTo,
                                      bool bNumFormatChanged )
{
    bool bBroadcast = (bNumFormatChanged && GetDocOptions().IsCalcAsShown() && !IsImportingXML() && !IsClipboard());
    if ( pAdrFrom && !pAdrTo )
    {
        const SCTAB nTab = pAdrFrom->Tab();

        if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
            maTabs[nTab]->InvalidateTextWidth( pAdrFrom, nullptr, bNumFormatChanged, bBroadcast );
    }
    else
    {
        const SCTAB nTabStart = pAdrFrom ? pAdrFrom->Tab() : 0;
        const SCTAB nTabEnd   = pAdrTo   ? pAdrTo->Tab()   : MAXTAB;

        for ( SCTAB nTab=nTabStart; nTab<=nTabEnd && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
            if ( maTabs[nTab] )
                maTabs[nTab]->InvalidateTextWidth( pAdrFrom, pAdrTo, bNumFormatChanged, bBroadcast );
    }
}

#define CALCMAX                 1000    // Calculations

namespace {

class IdleCalcTextWidthScope : public TaskStopwatch
{
    ScDocument& mrDoc;
    ScAddress& mrCalcPos;
    MapMode maOldMapMode;
    ScStyleSheetPool* mpStylePool;
    bool mbNeedMore;
    bool mbProgress;

public:
    IdleCalcTextWidthScope(ScDocument& rDoc, ScAddress& rCalcPos) :
        mrDoc(rDoc),
        mrCalcPos(rCalcPos),
        mpStylePool(rDoc.GetStyleSheetPool()),
        mbNeedMore(false),
        mbProgress(false)
    {
        mrDoc.EnableIdle(false);
    }

    ~IdleCalcTextWidthScope()
    {
        suppress_fun_call_w_exception(ImplDestroy());
    }

    SCTAB Tab() const { return mrCalcPos.Tab(); }
    SCCOL Col() const { return mrCalcPos.Col(); }
    SCROW Row() const { return mrCalcPos.Row(); }

    void setTab(SCTAB nTab) { mrCalcPos.SetTab(nTab); }
    void setCol(SCCOL nCol) { mrCalcPos.SetCol(nCol); }
    void setRow(SCROW nRow) { mrCalcPos.SetRow(nRow); }

    void incTab() { mrCalcPos.IncTab(); }
    void incCol(SCCOL nInc) { mrCalcPos.IncCol(nInc); }

    void setOldMapMode(const MapMode& rOldMapMode) { maOldMapMode = rOldMapMode; }

    void setNeedMore(bool b) { mbNeedMore = b; }
    bool getNeedMore() const { return mbNeedMore; }

    void createProgressBar()
    {
        ScProgress::CreateInterpretProgress(&mrDoc, false);
        mbProgress = true;
    }

    bool hasProgressBar() const { return mbProgress; }

    ScStyleSheetPool* getStylePool() { return mpStylePool; }

private:
    void ImplDestroy()
    {
        SfxPrinter* pDev = mrDoc.GetPrinter();
        if (pDev)
            pDev->SetMapMode(maOldMapMode);

        if (mbProgress)
            ScProgress::DeleteInterpretProgress();

        mrDoc.EnableIdle(true);
    }
};

}

bool ScDocument::IdleCalcTextWidth()            // true = try next again
{
    // #i75610# if a printer hasn't been set or created yet, don't create one for this
    if (!mbIdleEnabled || IsInLinkUpdate() || GetPrinter(false) == nullptr)
        return false;

    IdleCalcTextWidthScope aScope(*this, aCurTextWidthCalcPos);

    if (!ValidRow(aScope.Row()))
    {
        aScope.setRow(0);
        aScope.incCol(-1);
    }

    if (aScope.Col() < 0)
    {
        aScope.setCol(MaxCol());
        aScope.incTab();
    }

    if (!HasTable(aScope.Tab()))
        aScope.setTab(0);

    ScTable* pTab = maTabs[aScope.Tab()].get();
    ScStyleSheet* pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(pTab->aPageStyle, SfxStyleFamily::Page));
    OSL_ENSURE( pStyle, "Missing StyleSheet :-/" );

    if (!pStyle || getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
    {
        // Move to the next sheet as the current one has scale-to-pages set,
        // and bail out.
        aScope.incTab();
        return false;
    }

    sal_uInt16 nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
    Fraction aZoomFract(nZoom, 100);

    aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
    // Start at specified cell position (nCol, nRow, nTab).
    ScColumn* pCol  = &pTab->aCol[aScope.Col()];
    std::optional<ScColumnTextWidthIterator> pColIter(std::in_place, *this, *pCol, aScope.Row(), MaxRow());

    OutputDevice* pDev = nullptr;
    sal_uInt16 nRestart = 0;
    sal_uInt16 nCount = 0;
    while ( (nZoom > 0) && (nCount < CALCMAX) && (nRestart < 2) )
    {
        if (pColIter->hasCell())
        {
            // More cell in this column.
            SCROW nRow = pColIter->getPos();
            aScope.setRow(nRow);

            if (pColIter->getValue() == TEXTWIDTH_DIRTY)
            {
                // Calculate text width for this cell.
                double nPPTX = 0.0;
                double nPPTY = 0.0;
                if (!pDev)
                {
                    pDev = GetPrinter();
                    assert(pDev);
                    aScope.setOldMapMode(pDev->GetMapMode());
                    pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize

                    Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
                    nPPTX = aPix1000.X() / 1000.0;
                    nPPTY = aPix1000.Y() / 1000.0;
                }

                if (!aScope.hasProgressBar() && pCol->IsFormulaDirty(nRow))
                    aScope.createProgressBar();

                sal_uInt16 nNewWidth = static_cast<sal_uInt16>(GetNeededSize(
                    aScope.Col(), aScope.Row(), aScope.Tab(),
                    pDev, nPPTX, nPPTY, aZoomFract,aZoomFract, true, true));   // bTotalSize

                pColIter->setValue(nNewWidth);
                aScope.setNeedMore(true);
            }
            pColIter->next();
        }
        else
        {
            // No more cell in this column.  Move to the left column and start at row 0.

            bool bNewTab = false;

            aScope.setRow(0);
            aScope.incCol(-1);

            if (aScope.Col() < 0)
            {
                // No more column to the left.  Move to the right-most column of the next sheet.
                aScope.setCol(MaxCol());
                aScope.incTab();
                bNewTab = true;
            }

            if (!HasTable(aScope.Tab()))
            {
                // Sheet doesn't exist at specified sheet position.  Restart at sheet 0.
                aScope.setTab(0);
                nRestart++;
                bNewTab = true;
            }

            if ( nRestart < 2 )
            {
                if ( bNewTab )
                {
                    pTab = maTabs[aScope.Tab()].get();
                    aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
                    pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(
                        pTab->aPageStyle, SfxStyleFamily::Page));

                    if ( pStyle )
                    {
                        // Check if the scale-to-pages setting is set. If
                        // set, we exit the loop.  If not, get the page
                        // scale factor of the new sheet.
                        if (getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
                        {
                            nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
                            aZoomFract = Fraction(nZoom, 100);
                        }
                        else
                            nZoom = 0;
                    }
                    else
                    {
                        OSL_FAIL( "Missing StyleSheet :-/" );
                    }
                }

                if ( nZoom > 0 )
                {
                    pCol  = &pTab->aCol[aScope.Col()];
                    pColIter.emplace(*this, *pCol, aScope.Row(), MaxRow());
                }
                else
                {
                    aScope.incTab(); // Move to the next sheet as the current one has scale-to-pages set.
                    return false;
                }
            }
        }

        ++nCount;

        if (!aScope.continueIter())
            break;
    }

    return aScope.getNeedMore();
}

void ScDocument::RepaintRange( const ScRange& rRange )
{
    if ( bIsVisible && mpShell )
    {
        ScModelObj* pModel = mpShell->GetModel();
        if ( pModel )
            pModel->RepaintRange( rRange );     // locked repaints are checked there
    }
}

void ScDocument::RepaintRange( const ScRangeList& rRange )
{
    if ( bIsVisible && mpShell )
    {
        ScModelObj* pModel = mpShell->GetModel();
        if ( pModel )
            pModel->RepaintRange( rRange );     // locked repaints are checked there
    }
}

void ScDocument::SaveDdeLinks(SvStream& rStream) const
{
    //  when 4.0-Export, remove all with mode != DEFAULT
    bool bExport40 = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_40 );

    const ::sfx2::SvBaseLinks& rLinks = GetLinkManager()->GetLinks();
    sal_uInt16 nCount = rLinks.size();

    // Count them first

    sal_uInt16 nDdeCount = 0;
    sal_uInt16 i;
    for (i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
            if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
                ++nDdeCount;
    }

    //  Header

    ScMultipleWriteHeader aHdr( rStream );
    rStream.WriteUInt16( nDdeCount );

    // Save links

    for (i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
        {
            if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
                pLink->Store( rStream, aHdr );
        }
    }
}

void ScDocument::LoadDdeLinks(SvStream& rStream)
{
    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
    if (!pMgr)
        return;

    ScMultipleReadHeader aHdr( rStream );

    sal_uInt16 nCount(0);
    rStream.ReadUInt16( nCount );

    const rtl_TextEncoding eCharSet = rStream.GetStreamCharSet();
    const size_t nMinStringSize = eCharSet == RTL_TEXTENCODING_UNICODE ? sizeof(sal_uInt32) : sizeof(sal_uInt16);
    const size_t nMinRecordSize = 1 + nMinStringSize*3;
    const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize;
    if (nCount > nMaxRecords)
    {
        SAL_WARN("sc", "Parsing error: " << nMaxRecords <<
                 " max possible entries, but " << nCount << " claimed, truncating");
        nCount = nMaxRecords;
    }

    for (sal_uInt16 i=0; i<nCount; ++i)
    {
        ScDdeLink* pLink = new ScDdeLink( *this, rStream, aHdr );
        pMgr->InsertDDELink(pLink, pLink->GetAppl(), pLink->GetTopic(), pLink->GetItem());
    }
}

void ScDocument::SetInLinkUpdate(bool bSet)
{
    //  called from TableLink and AreaLink

    OSL_ENSURE( bInLinkUpdate != bSet, "SetInLinkUpdate twice" );
    bInLinkUpdate = bSet;
}

bool ScDocument::IsInLinkUpdate() const
{
    return bInLinkUpdate || IsInDdeLinkUpdate();
}

void ScDocument::UpdateExternalRefLinks(weld::Window* pWin)
{
    if (!pExternalRefMgr)
        return;

    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
    if (!pMgr)
        return;

    const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    sal_uInt16 nCount = rLinks.size();

    bool bAny = false;

    // Collect all the external ref links first.
    std::vector<ScExternalRefLink*> aRefLinks;
    for (sal_uInt16 i = 0; i < nCount; ++i)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        ScExternalRefLink* pRefLink = dynamic_cast<ScExternalRefLink*>(pBase);
        if (pRefLink)
            aRefLinks.push_back(pRefLink);
    }

    weld::WaitObject aWaitSwitch(pWin);

    pExternalRefMgr->enableDocTimer(false);
    ScProgress aProgress(GetDocumentShell(), ScResId(SCSTR_UPDATE_EXTDOCS), aRefLinks.size(), true);
    for (size_t i = 0, n = aRefLinks.size(); i < n; ++i)
    {
        aProgress.SetState(i+1);

        ScExternalRefLink* pRefLink = aRefLinks[i];
        if (pRefLink->Update())
        {
            bAny = true;
            continue;
        }

        // Update failed.  Notify the user.

        OUString aFile;
        sfx2::LinkManager::GetDisplayNames(pRefLink, nullptr, &aFile);
        // Decode encoded URL for display friendliness.
        INetURLObject aUrl(aFile,INetURLObject::EncodeMechanism::WasEncoded);
        aFile = aUrl.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous);

        OUString sMessage = ScResId(SCSTR_EXTDOC_NOT_LOADED) +
            "\n\n" +
            aFile;
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
                                                  VclMessageType::Warning, VclButtonsType::Ok,
                                                  sMessage));
        xBox->run();
    }

    pExternalRefMgr->enableDocTimer(true);

    if (!bAny)
        return;

    TrackFormulas();
    mpShell->Broadcast( SfxHint(SfxHintId::ScDataChanged) );

    // #i101960# set document modified, as in TrackTimeHdl for DDE links
    if (!mpShell->IsModified())
    {
        mpShell->SetModified();
        SfxBindings* pBindings = GetViewBindings();
        if (pBindings)
        {
            pBindings->Invalidate( SID_SAVEDOC );
            pBindings->Invalidate( SID_DOC_MODIFIED );
        }
    }
}

void ScDocument::CopyDdeLinks( ScDocument& rDestDoc ) const
{
    if (bIsClip)        // Create from Stream
    {
        if (pClipData)
        {
            pClipData->Seek(0);
            rDestDoc.LoadDdeLinks(*pClipData);
        }

        return;
    }

    const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
    if (!pMgr)
        return;

    sfx2::LinkManager* pDestMgr = rDestDoc.GetDocLinkManager().getLinkManager(rDestDoc.bAutoCalc);
    if (!pDestMgr)
        return;

    const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    for (const auto & rLink : rLinks)
    {
        const sfx2::SvBaseLink* pBase = rLink.get();
        if (const ScDdeLink* p = dynamic_cast<const ScDdeLink*>(pBase))
        {
            ScDdeLink* pNew = new ScDdeLink(rDestDoc, *p);
            pDestMgr->InsertDDELink(
                pNew, pNew->GetAppl(), pNew->GetTopic(), pNew->GetItem());
        }
    }
}

namespace {

/** Tries to find the specified DDE link.
    @param pnDdePos  (out-param) if not 0, the index of the DDE link is returned here
                     (does not include other links from link manager).
    @return  The DDE link, if it exists, otherwise 0. */
ScDdeLink* lclGetDdeLink(
        const sfx2::LinkManager* pLinkManager,
        std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem, sal_uInt8 nMode,
        size_t* pnDdePos = nullptr )
{
    if( pLinkManager )
    {
        const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
        if( pnDdePos ) *pnDdePos = 0;
        for( const auto& nLinks : rLinks )
        {
            if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( nLinks.get() )  )
            {
                if( (pDdeLink->GetAppl() == rAppl) &&
                    (pDdeLink->GetTopic() == rTopic) &&
                    (pDdeLink->GetItem() == rItem) &&
                    ((nMode == SC_DDE_IGNOREMODE) || (nMode == pDdeLink->GetMode())) )
                    return pDdeLink;
                if( pnDdePos ) ++*pnDdePos;
            }
        }
    }
    return nullptr;
}

/** Returns a pointer to the specified DDE link.
    @param nDdePos  Index of the DDE link (does not include other links from link manager).
    @return  The DDE link, if it exists, otherwise 0. */
ScDdeLink* lclGetDdeLink( const sfx2::LinkManager* pLinkManager, size_t nDdePos )
{
    if( pLinkManager )
    {
        size_t nDdeIndex = 0;       // counts only the DDE links
        for( const auto& pLink : pLinkManager->GetLinks() )
        {
            if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( pLink.get() )  )
            {
                if( nDdeIndex == nDdePos )
                    return pDdeLink;
                ++nDdeIndex;
            }
        }
    }
    return nullptr;
}

} // namespace

bool ScDocument::FindDdeLink( std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem,
        sal_uInt8 nMode, size_t& rnDdePos )
{
    return lclGetDdeLink( GetLinkManager(), rAppl, rTopic, rItem, nMode, &rnDdePos ) != nullptr;
}

bool ScDocument::GetDdeLinkData( size_t nDdePos, OUString& rAppl, OUString& rTopic, OUString& rItem ) const
{
    if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
    {
        rAppl  = pDdeLink->GetAppl();
        rTopic = pDdeLink->GetTopic();
        rItem  = pDdeLink->GetItem();
        return true;
    }
    return false;
}

bool ScDocument::GetDdeLinkMode( size_t nDdePos, sal_uInt8& rnMode ) const
{
    if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
    {
        rnMode = pDdeLink->GetMode();
        return true;
    }
    return false;
}

const ScMatrix* ScDocument::GetDdeLinkResultMatrix( size_t nDdePos ) const
{
    const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos );
    return pDdeLink ? pDdeLink->GetResult() : nullptr;
}

bool ScDocument::CreateDdeLink( const OUString& rAppl, const OUString& rTopic, const OUString& rItem, sal_uInt8 nMode, const ScMatrixRef& pResults )
{
    /*  Create a DDE link without updating it (i.e. for Excel import), to prevent
        unwanted connections. First try to find existing link. Set result array
        on existing and new links. */
    //TODO: store DDE links additionally at document (for efficiency)?
    OSL_ENSURE( nMode != SC_DDE_IGNOREMODE, "ScDocument::CreateDdeLink - SC_DDE_IGNOREMODE not allowed here" );

    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
    if (!pMgr)
        return false;

    if (nMode != SC_DDE_IGNOREMODE)
    {
        ScDdeLink* pDdeLink = lclGetDdeLink(pMgr, rAppl, rTopic, rItem, nMode);
        if( !pDdeLink )
        {
            // create a new DDE link, but without TryUpdate
            pDdeLink = new ScDdeLink( *this, rAppl, rTopic, rItem, nMode );
            pMgr->InsertDDELink(pDdeLink, rAppl, rTopic, rItem);
        }

        // insert link results
        if( pResults )
            pDdeLink->SetResult( pResults );

        return true;
    }
    return false;
}

bool ScDocument::SetDdeLinkResultMatrix( size_t nDdePos, const ScMatrixRef& pResults )
{
    if( ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
    {
        pDdeLink->SetResult( pResults );
        return true;
    }
    return false;
}

bool ScDocument::HasAreaLinks() const
{
    const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
    if (!pMgr)
        return false;

    const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    sal_uInt16 nCount = rLinks.size();
    for (sal_uInt16 i=0; i<nCount; i++)
        if (nullptr != dynamic_cast<const ScAreaLink* >(rLinks[i].get()))
            return true;

    return false;
}

void ScDocument::UpdateAreaLinks()
{
    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
    if (!pMgr)
        return;

    const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    // Note: SvBaseLink::Update can remove entries after the current one
    for (size_t i = 0; i < rLinks.size(); ++i)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (dynamic_cast<const ScAreaLink*>( pBase) !=  nullptr)
            pBase->Update();
    }
}

void ScDocument::DeleteAreaLinksOnTab( SCTAB nTab )
{
    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
    if (!pMgr)
        return;

    const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    sfx2::SvBaseLinks::size_type nPos = 0;
    while ( nPos < rLinks.size() )
    {
        const ::sfx2::SvBaseLink* pBase = rLinks[nPos].get();
        const ScAreaLink* pLink = dynamic_cast<const ScAreaLink*>(pBase);
        if (pLink && pLink->GetDestArea().aStart.Tab() == nTab)
            pMgr->Remove(nPos);
        else
            ++nPos;
    }
}

void ScDocument::UpdateRefAreaLinks( UpdateRefMode eUpdateRefMode,
                             const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
{
    sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
    if (!pMgr)
        return;

    bool bAnyUpdate = false;

    const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
    sal_uInt16 nCount = rLinks.size();
    for (sal_uInt16 i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (ScAreaLink* pLink = dynamic_cast<ScAreaLink*>(pBase))
        {
            ScRange aOutRange = pLink->GetDestArea();

            SCCOL nCol1 = aOutRange.aStart.Col();
            SCROW nRow1 = aOutRange.aStart.Row();
            SCTAB nTab1 = aOutRange.aStart.Tab();
            SCCOL nCol2 = aOutRange.aEnd.Col();
            SCROW nRow2 = aOutRange.aEnd.Row();
            SCTAB nTab2 = aOutRange.aEnd.Tab();

            ScRefUpdateRes eRes =
                ScRefUpdate::Update( *this, eUpdateRefMode,
                    rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
                    rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz,
                    nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
            if ( eRes != UR_NOTHING )
            {
                pLink->SetDestArea( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) );
                bAnyUpdate = true;
            }
        }
    }

    if ( !bAnyUpdate )
        return;

    // #i52120# Look for duplicates (after updating all positions).
    // If several links start at the same cell, the one with the lower index is removed
    // (file format specifies only one link definition for a cell).

    sal_uInt16 nFirstIndex = 0;
    while ( nFirstIndex < nCount )
    {
        bool bFound = false;
        ::sfx2::SvBaseLink* pFirst = rLinks[nFirstIndex].get();
        if (ScAreaLink* pFirstLink = dynamic_cast<ScAreaLink*>(pFirst))
        {
            ScAddress aFirstPos = pFirstLink->GetDestArea().aStart;
            for ( sal_uInt16 nSecondIndex = nFirstIndex + 1; nSecondIndex < nCount && !bFound; ++nSecondIndex )
            {
                ::sfx2::SvBaseLink* pSecond = rLinks[nSecondIndex].get();
                ScAreaLink* pSecondLink = dynamic_cast<ScAreaLink*>(pSecond);
                if (pSecondLink && pSecondLink->GetDestArea().aStart == aFirstPos)
                {
                    // remove the first link, exit the inner loop, don't increment nFirstIndex
                    pMgr->Remove(pFirst);
                    nCount = rLinks.size();
                    bFound = true;
                }
            }
        }
        if (!bFound)
            ++nFirstIndex;
    }
}

void ScDocument::CheckLinkFormulaNeedingCheck( const ScTokenArray& rCode )
{
    if (HasLinkFormulaNeedingCheck())
        return;

    // Prefer RPN over tokenized formula if available.
    if (rCode.GetCodeLen())
    {
        if (rCode.HasOpCodeRPN(ocDde) || rCode.HasOpCodeRPN(ocWebservice))
            SetLinkFormulaNeedingCheck(true);
    }
    else if (rCode.GetLen())
    {
        if (rCode.HasOpCode(ocDde) || rCode.HasOpCode(ocWebservice))
            SetLinkFormulaNeedingCheck(true);
    }
    else
    {
        // Possible with named expression without expression like Excel
        // internal print ranges, obscure user define names, ... formula error
        // cells without formula ...
        SAL_WARN("sc.core","ScDocument::CheckLinkFormulaNeedingCheck - called with empty ScTokenArray");
    }
}

// TimerDelays etc.
void ScDocument::KeyInput()
{
    if ( pChartListenerCollection->hasListeners() )
        pChartListenerCollection->StartTimer();
    if (apTemporaryChartLock)
        apTemporaryChartLock->StartOrContinueLocking();
}

SfxBindings* ScDocument::GetViewBindings()
{
    //  used to invalidate slots after changes to this document

    if ( !mpShell )
        return nullptr;        // no ObjShell -> no view

    //  first check current view
    SfxViewFrame* pViewFrame = SfxViewFrame::Current();
    if ( pViewFrame && pViewFrame->GetObjectShell() != mpShell )     // wrong document?
        pViewFrame = nullptr;

    //  otherwise use first view for this doc
    if ( !pViewFrame )
        pViewFrame = SfxViewFrame::GetFirst( mpShell );

    if (pViewFrame)
        return &pViewFrame->GetBindings();
    else
        return nullptr;
}

void ScDocument::TransliterateText( const ScMarkData& rMultiMark, TransliterationFlags nType )
{
    OSL_ENSURE( rMultiMark.IsMultiMarked(), "TransliterateText: no selection" );

    utl::TransliterationWrapper aTransliterationWrapper( comphelper::getProcessComponentContext(), nType );
    bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
    LanguageType nLanguage = LANGUAGE_SYSTEM;

    std::unique_ptr<ScEditEngineDefaulter> pEngine;        // not using mpEditEngine member because of defaults

    SCTAB nCount = GetTableCount();
    for (const SCTAB& nTab : rMultiMark)
    {
        if (nTab >= nCount)
            break;

        if ( maTabs[nTab] )
        {
            SCCOL nCol = 0;
            SCROW nRow = 0;

            bool bFound = rMultiMark.IsCellMarked( nCol, nRow );
            if (!bFound)
                bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );

            while (bFound)
            {
                ScRefCellValue aCell(*this, ScAddress(nCol, nRow, nTab));

                // fdo#32786 TITLE_CASE/SENTENCE_CASE need the extra handling in EditEngine (loop over words/sentences).
                // Still use TransliterationWrapper directly for text cells with other transliteration types,
                // for performance reasons.
                if (aCell.getType() == CELLTYPE_EDIT ||
                    (aCell.getType() == CELLTYPE_STRING &&
                     ( nType == TransliterationFlags::SENTENCE_CASE || nType == TransliterationFlags::TITLE_CASE)))
                {
                    if (!pEngine)
                        pEngine.reset(new ScFieldEditEngine(this, GetEditEnginePool()));

                    // defaults from cell attributes must be set so right language is used
                    const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
                    SfxItemSet aDefaults( pEngine->GetEmptyItemSet() );
                    if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, nRow, nTab ) )
                    {
                        ScPatternAttr aPreviewPattern( *pPattern );
                        aPreviewPattern.SetStyleSheet(pPreviewStyle);
                        aPreviewPattern.FillEditItemSet(&aDefaults);
                    }
                    else
                    {
                        SfxItemSet* pFontSet = GetPreviewFont( nCol, nRow, nTab );
                        pPattern->FillEditItemSet(&aDefaults, pFontSet);
                    }
                    pEngine->SetDefaults(std::move(aDefaults));
                    if (aCell.getType() == CELLTYPE_STRING)
                        pEngine->SetTextCurrentDefaults(aCell.getSharedString()->getString());
                    else if (aCell.getEditText())
                        pEngine->SetTextCurrentDefaults(*aCell.getEditText());

                    pEngine->ClearModifyFlag();

                    sal_Int32 nLastPar = pEngine->GetParagraphCount();
                    if (nLastPar)
                        --nLastPar;
                    sal_Int32 nTxtLen = pEngine->GetTextLen(nLastPar);
                    ESelection aSelAll( 0, 0, nLastPar, nTxtLen );

                    pEngine->TransliterateText( aSelAll, nType );

                    if ( pEngine->IsModified() )
                    {
                        ScEditAttrTester aTester( pEngine.get() );
                        if ( aTester.NeedsObject() )
                        {
                            // remove defaults (paragraph attributes) before creating text object
                            pEngine->SetDefaults(pEngine->GetEmptyItemSet());

                            // The cell will take ownership of the text object instance.
                            SetEditText(ScAddress(nCol,nRow,nTab), pEngine->CreateTextObject());
                        }
                        else
                        {
                            ScSetStringParam aParam;
                            aParam.setTextInput();
                            SetString(ScAddress(nCol,nRow,nTab), pEngine->GetText(), &aParam);
                        }
                    }
                }

                else if (aCell.getType() == CELLTYPE_STRING)
                {
                    OUString aOldStr = aCell.getSharedString()->getString();
                    sal_Int32 nOldLen = aOldStr.getLength();

                    if ( bConsiderLanguage )
                    {
                        SvtScriptType nScript = GetStringScriptType( aOldStr );        //TODO: cell script type?
                        sal_uInt16 nWhich = ( nScript == SvtScriptType::ASIAN ) ? ATTR_CJK_FONT_LANGUAGE :
                                        ( ( nScript == SvtScriptType::COMPLEX ) ? ATTR_CTL_FONT_LANGUAGE :
                                                                                ATTR_FONT_LANGUAGE );
                        nLanguage = static_cast<const SvxLanguageItem&>(GetAttr( nCol, nRow, nTab, nWhich )).GetValue();
                    }

                    uno::Sequence<sal_Int32> aOffsets;
                    OUString aNewStr = aTransliterationWrapper.transliterate( aOldStr, nLanguage, 0, nOldLen, &aOffsets );

                    if ( aNewStr != aOldStr )
                    {
                        ScSetStringParam aParam;
                        aParam.setTextInput();
                        SetString(ScAddress(nCol,nRow,nTab), aNewStr, &aParam);
                    }
                }
                bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );
            }
        }
    }
}

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