/* -*- 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 <config_features.h>
 
#include <com/sun/star/frame/Desktop.hpp>
 
#include <scitems.hxx>
#include <editeng/flstitem.hxx>
#include <sfx2/fcontnr.hxx>
#include <sfx2/infobar.hxx>
#include <sfx2/objface.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/sfxresid.hxx>
#include <sfx2/strings.hrc>
#include <svtools/ehdl.hxx>
#include <svtools/langtab.hxx>
#include <basic/sbxcore.hxx>
#include <basic/sberrors.hxx>
#include <svtools/sfxecode.hxx>
#include <svx/ofaitem.hxx>
#include <svl/stritem.hxx>
#include <svl/whiter.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <svx/dataaccessdescriptor.hxx>
#include <svx/drawitem.hxx>
#include <svx/fmshell.hxx>
#include <sfx2/passwd.hxx>
#include <sfx2/filedlghelper.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/sfxdlg.hxx>
#include <svl/PasswordHelper.hxx>
#include <svl/documentlockfile.hxx>
#include <svl/sharecontrolfile.hxx>
#include <tools/json_writer.hxx>
#include <unotools/securityoptions.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <o3tl/string_view.hxx>
 
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <docuno.hxx>
 
#include <docsh.hxx>
#include "docshimp.hxx"
#include <docfunc.hxx>
#include <scres.hrc>
#include <strings.hrc>
#include <stlsheet.hxx>
#include <stlpool.hxx>
#include <appoptio.hxx>
#include <globstr.hrc>
#include <global.hxx>
#include <dbdocfun.hxx>
#include <printfun.hxx>
#include <viewdata.hxx>
#include <tabvwsh.hxx>
#include <impex.hxx>
#include <undodat.hxx>
#include <undocell.hxx>
#include <inputhdl.hxx>
#include <dbdata.hxx>
#include <servobj.hxx>
#include <rangenam.hxx>
#include <scmod.hxx>
#include <chgviset.hxx>
#include <reffact.hxx>
#include <chartlis.hxx>
#include <chartpos.hxx>
#include <tablink.hxx>
#include <drwlayer.hxx>
#include <docoptio.hxx>
#include <undostyl.hxx>
#include <rangeseq.hxx>
#include <chgtrack.hxx>
#include <com/sun/star/document/UpdateDocMode.hpp>
#include <scresid.hxx>
#include <scabstdlg.hxx>
#include <sharedocdlg.hxx>
#include <conditio.hxx>
#include <sheetevents.hxx>
#include <formulacell.hxx>
#include <documentlinkmgr.hxx>
#include <memory>
#include <sfx2/notebookbar/SfxNotebookBar.hxx>
#include <helpids.h>
#include <editeng/eeitem.hxx>
#include <editeng/langitem.hxx>
#include <officecfg/Office/Common.hxx>
 
#include <svx/xdef.hxx>
 
using namespace ::com::sun::star;
 
void ScDocShell::SetInitialLinkUpdate( const SfxMedium* pMed )
{
    if (pMed)
    {
        const SfxUInt16Item* pUpdateDocItem = pMed->GetItemSet().GetItem(SID_UPDATEDOCMODE, false);
        m_nCanUpdate = pUpdateDocItem ? pUpdateDocItem->GetValue() : css::document::UpdateDocMode::NO_UPDATE;
    }
 
    // GetLinkUpdateModeState() evaluates m_nCanUpdate so that must have
    // been set first. Do not override an already forbidden LinkUpdate (the
    // default is allow).
    comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = getEmbeddedObjectContainer();
    if (rEmbeddedObjectContainer.getUserAllowsLinkUpdate())
    {
        // For anything else than LM_ALWAYS we need user confirmation.
        rEmbeddedObjectContainer.setUserAllowsLinkUpdate( GetLinkUpdateModeState() == LM_ALWAYS);
    }
}
 
ScLkUpdMode ScDocShell::GetLinkUpdateModeState() const
{
    ScLkUpdMode nSet;
    if (m_nCanUpdate == css::document::UpdateDocMode::NO_UPDATE)
        nSet = LM_NEVER;
    else if (m_nCanUpdate == css::document::UpdateDocMode::FULL_UPDATE)
        nSet = LM_ALWAYS;
    else
    {
        nSet = GetDocument().GetLinkMode();
        if (nSet == LM_UNKNOWN)
        {
            ScAppOptions aAppOptions = SC_MOD()->GetAppOptions();
            nSet = aAppOptions.GetLinkMode();
        }
    }
 
    if (nSet != LM_NEVER
        && (SvtSecurityOptions::isUntrustedReferer(
                GetMedium() == nullptr ? OUString() : GetMedium()->GetName())
            || (IsDocShared() && SvtSecurityOptions::isUntrustedReferer(GetSharedFileURL()))))
    {
        nSet = LM_NEVER;
    }
    else if (nSet == LM_ALWAYS
            && !(SvtSecurityOptions::isTrustedLocationUriForUpdatingLinks(
                    GetMedium() == nullptr ? OUString() : GetMedium()->GetName())
                || (IsDocShared()
                    && SvtSecurityOptions::isTrustedLocationUriForUpdatingLinks(
                        GetSharedFileURL()))))
    {
        nSet = LM_ON_DEMAND;
    }
    if (m_nCanUpdate == css::document::UpdateDocMode::QUIET_UPDATE
            && nSet == LM_ON_DEMAND)
    {
        nSet = LM_NEVER;
    }
 
    return nSet;
}
 
void ScDocShell::AllowLinkUpdate()
{
    m_pDocument->SetLinkFormulaNeedingCheck(false);
    getEmbeddedObjectContainer().setUserAllowsLinkUpdate(true);
}
 
void ScDocShell::ReloadAllLinks()
{
    AllowLinkUpdate();
 
    ReloadTabLinks();
    weld::Window *pDialogParent = GetActiveDialogParent();
    m_pDocument->UpdateExternalRefLinks(pDialogParent);
 
    bool bAnyDde = m_pDocument->GetDocLinkManager().updateDdeOrOleOrWebServiceLinks(pDialogParent);
 
    if (bAnyDde)
    {
        //  calculate formulas and paint like in the TrackTimeHdl
        m_pDocument->TrackFormulas();
        Broadcast(SfxHint(SfxHintId::ScDataChanged));
 
        //  Should FID_DATACHANGED become asynchronous some time
        //  (e.g., with Invalidate at Window), an update needs to be forced here.
    }
 
    m_pDocument->UpdateAreaLinks();
}
 
IMPL_LINK( ScDocShell, ReloadAllLinksHdl, weld::Button&, rButton, void )
{
    ScDocument& rDoc = GetDocument();
    if (rDoc.HasLinkFormulaNeedingCheck() && rDoc.GetDocLinkManager().hasExternalRefLinks())
    {
        // If we have WEBSERVICE/Dde link and other external links in the document, it might indicate some
        // exfiltration attempt, add *another* warning about this on top of the "Security Warning"
        // shown in the infobar before they got here.
        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(&rButton,
                                                       VclMessageType::Warning, VclButtonsType::YesNo,
                                                       ScResId(STR_TRUST_DOCUMENT_WARNING)));
        xQueryBox->set_secondary_text(ScResId(STR_WEBSERVICE_WITH_LINKS_WARNING));
        xQueryBox->set_default_response(RET_NO);
        if (xQueryBox->run() != RET_YES)
            return;
    }
 
    ReloadAllLinks();
 
    ScTabViewShell* pViewSh = GetBestViewShell();
    SfxViewFrame* pViewFrame = pViewSh ? pViewSh->GetFrame() : nullptr;
    if (pViewFrame)
        pViewFrame->RemoveInfoBar(u"enablecontent");
    SAL_WARN_IF(!pViewFrame, "sc", "expected there to be a ViewFrame");
}
 
namespace
{
    class LinkHelp
    {
    public:
        DECL_STATIC_LINK(LinkHelp, DispatchHelpLinksHdl, weld::Button&, void);
    };
}
 
IMPL_STATIC_LINK(LinkHelp, DispatchHelpLinksHdl, weld::Button&, rBtn, void)
{
    if (Help* pHelp = Application::GetHelp())
        pHelp->Start(HID_UPDATE_LINK_WARNING, &rBtn);
}
 
void ScDocShell::Execute( SfxRequest& rReq )
{
    const SfxItemSet* pReqArgs = rReq.GetArgs();
    SfxBindings* pBindings = GetViewBindings();
    bool bUndo (m_pDocument->IsUndoEnabled());
 
    sal_uInt16 nSlot = rReq.GetSlot();
    switch ( nSlot )
    {
        case SID_SC_SETTEXT:
        {
            const SfxPoolItem* pColItem;
            const SfxPoolItem* pRowItem;
            const SfxPoolItem* pTabItem;
            const SfxPoolItem* pTextItem;
            if( pReqArgs && pReqArgs->HasItem( FN_PARAM_1, &pColItem ) &&
                            pReqArgs->HasItem( FN_PARAM_2, &pRowItem ) &&
                            pReqArgs->HasItem( FN_PARAM_3, &pTabItem ) &&
                            pReqArgs->HasItem( SID_SC_SETTEXT, &pTextItem ) )
            {
                //  parameters are  1-based !!!
                SCCOL nCol = static_cast<const SfxInt16Item*>(pColItem)->GetValue() - 1;
                SCROW nRow = static_cast<const SfxInt32Item*>(pRowItem)->GetValue() - 1;
                SCTAB nTab = static_cast<const SfxInt16Item*>(pTabItem)->GetValue() - 1;
 
                SCTAB nTabCount = m_pDocument->GetTableCount();
                if ( m_pDocument->ValidCol(nCol) && m_pDocument->ValidRow(nRow) && ValidTab(nTab,nTabCount) )
                {
                    if ( m_pDocument->IsBlockEditable( nTab, nCol,nRow, nCol, nRow ) )
                    {
                        OUString aVal = static_cast<const SfxStringItem*>(pTextItem)->GetValue();
                        m_pDocument->SetString( nCol, nRow, nTab, aVal );
 
                        PostPaintCell( nCol, nRow, nTab );
                        SetDocumentModified();
 
                        rReq.Done();
                        break;
                    }
                    else                // protected cell
                    {
#if HAVE_FEATURE_SCRIPTING
                        SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER );      //! which error ?
#endif
                        break;
                    }
                }
            }
#if HAVE_FEATURE_SCRIPTING
            SbxBase::SetError( ERRCODE_BASIC_NO_OBJECT );
#endif
        }
        break;
 
        case SID_SBA_IMPORT:
        {
            if (pReqArgs)
            {
                const SfxPoolItem* pItem;
                svx::ODataAccessDescriptor aDesc;
                if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET )
                {
                    uno::Any aAny = static_cast<const SfxUnoAnyItem*>(pItem)->GetValue();
                    uno::Sequence<beans::PropertyValue> aProperties;
                    if ( aAny >>= aProperties )
                        aDesc.initializeFrom( aProperties );
                }
 
                OUString sTarget;
                if ( pReqArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET )
                    sTarget = static_cast<const SfxStringItem*>(pItem)->GetValue();
 
                bool bIsNewArea = true;         // Default sal_True (no inquiry)
                if ( pReqArgs->GetItemState( FN_PARAM_2, true, &pItem ) == SfxItemState::SET )
                    bIsNewArea = static_cast<const SfxBoolItem*>(pItem)->GetValue();
 
                // if necessary, create new database area
                bool bMakeArea = false;
                if (bIsNewArea)
                {
                    ScDBCollection* pDBColl = m_pDocument->GetDBCollection();
                    if ( !pDBColl || !pDBColl->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(sTarget)) )
                    {
                        ScAddress aPos;
                        if ( aPos.Parse( sTarget, *m_pDocument, m_pDocument->GetAddressConvention() ) & ScRefFlags::VALID )
                        {
                            bMakeArea = true;
                            if (bUndo)
                            {
                                OUString aStrImport = ScResId( STR_UNDO_IMPORTDATA );
                                ViewShellId nViewShellId(-1);
                                if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
                                    nViewShellId = pViewSh->GetViewShellId();
                                GetUndoManager()->EnterListAction( aStrImport, aStrImport, 0, nViewShellId );
                            }
 
                            ScDBData* pDBData = GetDBData( ScRange(aPos), SC_DB_IMPORT, ScGetDBSelection::Keep );
                            assert(pDBData && "Cannot create DB data");
                            sTarget = pDBData->GetName();
                        }
                    }
                }
 
                // inquire, before old DB range gets overwritten
                bool bDo = true;
                if (!bIsNewArea)
                {
                    OUString aTemplate = ScResId( STR_IMPORT_REPLACE );
                    OUString aMessage = o3tl::getToken(aTemplate, 0, '#' )
                        + sTarget
                        + o3tl::getToken(aTemplate, 1, '#' );
 
                    std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
                                                                   VclMessageType::Question, VclButtonsType::YesNo,
                                                                   aMessage));
                    xQueryBox->set_default_response(RET_YES);
                    bDo = xQueryBox->run() == RET_YES;
                }
 
                if (bDo)
                {
                    ScDBDocFunc(*this).UpdateImport( sTarget, aDesc );
                    rReq.Done();
 
                    //  UpdateImport also updates the internal operations
                }
                else
                    rReq.Ignore();
 
                if ( bMakeArea && bUndo)
                    GetUndoManager()->LeaveListAction();
            }
            else
            {
                OSL_FAIL( "arguments expected" );
            }
        }
        break;
 
        case SID_CHART_SOURCE:
        case SID_CHART_ADDSOURCE:
            ExecuteChartSource(rReq);
            break;
 
        case FID_AUTO_CALC:
            {
                bool bNewVal;
                const SfxPoolItem* pItem;
                if ( pReqArgs && SfxItemState::SET == pReqArgs->GetItemState( nSlot, true, &pItem ) )
                    bNewVal = static_cast<const SfxBoolItem*>(pItem)->GetValue();
                else
                    bNewVal = !m_pDocument->GetAutoCalc();     // Toggle for menu
                m_pDocument->SetAutoCalc( bNewVal );
                SetDocumentModified();
                if (pBindings)
                {
                    pBindings->Invalidate( FID_AUTO_CALC );
                }
                rReq.AppendItem( SfxBoolItem( FID_AUTO_CALC, bNewVal ) );
                rReq.Done();
            }
            break;
        case SID_OPEN_HYPERLINK:
            {
                ScViewData* pViewData = GetViewData();
                if ( !pViewData )
                {
                    rReq.Ignore();
                    break;
                }
 
                if (SC_MOD()->IsEditMode())
                {
                    if (EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart()))
                    {
                        const SvxFieldItem* pFieldItem = pEditView->GetFieldAtSelection(/*bAlsoCheckBeforeCursor=*/true);
                        const SvxFieldData* pField = pFieldItem ? pFieldItem->GetField() : nullptr;
                        if (const SvxURLField* pURLField = dynamic_cast<const SvxURLField*>(pField))
                        {
                            ScGlobal::OpenURL(pURLField->GetURL(), pURLField->GetTargetFrame(), true);
                            rReq.Done();
                            break;
                        }
                    }
                    rReq.Ignore();
                    break;
                }
 
                ScGridWindow* pWin = pViewData->GetActiveWin();
                if ( !pWin )
                {
                    rReq.Ignore();
                    break;
                }
 
                ScAddress aCell {pViewData->GetCurPos()};
                std::vector<UrlData> vUrls = pWin->GetEditUrls(aCell);
                if (vUrls.empty())
                {
                    rReq.Ignore();
                    break;
                }
 
                for (UrlData& data : vUrls)
                {
                    ScGlobal::OpenURL(data.aUrl, data.aTarget, true);
                }
                rReq.Done();
            }
            break;
        case FID_RECALC:
            DoRecalc( rReq.IsAPI() );
            rReq.Done();
            break;
        case FID_HARD_RECALC:
            DoHardRecalc();
            rReq.Done();
            break;
        case SID_UPDATETABLINKS:
            {
                ScLkUpdMode nSet = GetLinkUpdateModeState();
 
                if (nSet == LM_ALWAYS)
                {
                    ReloadAllLinks();
                    rReq.Done();
                }
                else if (nSet == LM_NEVER)
                {
                    getEmbeddedObjectContainer().setUserAllowsLinkUpdate(false);
                    rReq.Ignore();
                }
                else if (nSet == LM_ON_DEMAND)
                {
                    ScTabViewShell* pViewSh = GetBestViewShell();
                    SfxViewFrame* pViewFrame = pViewSh ? pViewSh->GetFrame() : nullptr;
                    if (pViewFrame)
                    {
                        pViewFrame->RemoveInfoBar(u"enablecontent");
                        auto pInfoBar = pViewFrame->AppendInfoBar(u"enablecontent"_ustr, SfxResId(RID_SECURITY_WARNING_TITLE),
                                                                  ScResId(STR_RELOAD_TABLES), InfobarType::WARNING);
                        if (pInfoBar)
                        {
                            weld::Button& rHelpBtn = pInfoBar->addButton();
                            rHelpBtn.set_label(GetStandardText(StandardButtonType::Help).replaceFirst("~", ""));
                            rHelpBtn.connect_clicked(LINK(nullptr, LinkHelp, DispatchHelpLinksHdl));
                            weld::Button& rBtn = pInfoBar->addButton();
                            rBtn.set_label(ScResId(STR_ENABLE_CONTENT));
                            rBtn.set_tooltip_text(ScResId(STR_ENABLE_CONTENT_TOOLTIP));
                            rBtn.connect_clicked(LINK(this, ScDocShell, ReloadAllLinksHdl));
 
                            // when active content is disabled the "Allow updating" button has no functionality.
                            if (officecfg::Office::Common::Security::Scripting::DisableActiveContent::get())
                            {
                                rBtn.set_tooltip_text(ScResId(STR_ENABLE_CONTENT_TOOLTIP_DISABLED));
                                rBtn.set_sensitive(false);
                            }
                        }
                    }
                    rReq.Done();
                }
            }
            break;
 
        case SID_REIMPORT_AFTER_LOAD:
            {
                //  Is called after loading if there are DB areas with omitted data
 
                bool bDone = false;
                ScDBCollection* pDBColl = m_pDocument->GetDBCollection();
 
                if ((m_nCanUpdate != css::document::UpdateDocMode::NO_UPDATE) &&
                   (m_nCanUpdate != css::document::UpdateDocMode::QUIET_UPDATE))
                {
                    ScRange aRange;
                    ScTabViewShell* pViewSh = GetBestViewShell();
                    OSL_ENSURE(pViewSh,"SID_REIMPORT_AFTER_LOAD: no View");
                    if (pViewSh && pDBColl)
                    {
                        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetActiveDialogParent(),
                                                                       VclMessageType::Question, VclButtonsType::YesNo,
                                                                       ScResId(STR_REIMPORT_AFTER_LOAD)));
                        xQueryBox->set_default_response(RET_YES);
                        if (xQueryBox->run() == RET_YES)
                        {
                            ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs();
                            for (const auto& rxDB : rDBs)
                            {
                                ScDBData& rDBData = *rxDB;
                                if ( rDBData.IsStripData() &&
                                     rDBData.HasImportParam() && !rDBData.HasImportSelection() )
                                {
                                    rDBData.GetArea(aRange);
                                    pViewSh->MarkRange(aRange);
 
                                    //  Import and internal operations like SID_REFRESH_DBAREA
                                    //  (inquiry for import not needed here)
 
                                    ScImportParam aImportParam;
                                    rDBData.GetImportParam( aImportParam );
                                    bool bContinue = pViewSh->ImportData( aImportParam );
                                    rDBData.SetImportParam( aImportParam );
 
                                    //  mark (size may have changed)
                                    rDBData.GetArea(aRange);
                                    pViewSh->MarkRange(aRange);
 
                                    if ( bContinue )    // error at import -> abort
                                    {
                                        //  internal operations, if some where saved
 
                                        if ( rDBData.HasQueryParam() || rDBData.HasSortParam() ||
                                             rDBData.HasSubTotalParam() )
                                            pViewSh->RepeatDB();
 
                                        //  pivot tables, which have the range as source data
 
                                        RefreshPivotTables(aRange);
                                    }
                                }
                            }
                            bDone = true;
                        }
                    }
                }
 
                if ( !bDone && pDBColl )
                {
                    //  if not, but then update the dependent formulas
                    //! also for individual ranges, which cannot be updated
 
                    m_pDocument->CalcAll();        //! only for the dependent
                    PostDataChanged();
                }
 
                if (bDone)
                    rReq.Done();
                else
                    rReq.Ignore();
            }
            break;
 
        case SID_AUTO_STYLE:
            OSL_FAIL("use ScAutoStyleHint instead of SID_AUTO_STYLE");
            break;
 
        case SID_GET_COLORLIST:
            {
                const SvxColorListItem* pColItem = GetItem(SID_COLOR_TABLE);
                const XColorListRef& pList = pColItem->GetColorList();
                rReq.SetReturnValue(OfaXColorListItem(SID_GET_COLORLIST, pList));
            }
            break;
 
        case FID_CHG_RECORD:
            {
                ScDocument& rDoc = GetDocument();
                // get argument (recorded macro)
                const SfxBoolItem* pItem = rReq.GetArg<SfxBoolItem>(FID_CHG_RECORD);
                bool bDo = true;
 
                // desired state
                ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
                bool bActivateTracking = (pChangeTrack == nullptr);   // toggle
                if ( pItem )
                    bActivateTracking = pItem->GetValue();      // from argument
 
                if ( !bActivateTracking )
                {
                    if ( !pItem )
                    {
                        // no dialog on playing the macro
                        std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(),
                                                                   VclMessageType::Warning, VclButtonsType::YesNo,
                                                                   ScResId(STR_END_REDLINING)));
                        xWarn->set_default_response(RET_NO);
                        bDo = (xWarn->run() == RET_YES );
                    }
 
                    if ( bDo )
                    {
                        if (pChangeTrack)
                        {
                            if ( pChangeTrack->IsProtected() )
                                bDo = ExecuteChangeProtectionDialog();
                        }
                        if ( bDo )
                        {
                            rDoc.EndChangeTracking();
                            PostPaintGridAll();
                        }
                    }
                }
                else
                {
                    rDoc.StartChangeTracking();
                    ScChangeViewSettings aChangeViewSet;
                    aChangeViewSet.SetShowChanges(true);
                    rDoc.SetChangeViewSettings(aChangeViewSet);
                }
 
                if ( bDo )
                {
                    UpdateAcceptChangesDialog();
 
                    // invalidate slots
                    if (pBindings)
                        pBindings->InvalidateAll(false);
                    if ( !pItem )
                        rReq.AppendItem( SfxBoolItem( FID_CHG_RECORD, bActivateTracking ) );
                    rReq.Done();
                }
                else
                    rReq.Ignore();
            }
            break;
 
        case SID_CHG_PROTECT :
            {
                if ( ExecuteChangeProtectionDialog() )
                {
                    rReq.Done();
                    SetDocumentModified();
                }
                else
                    rReq.Ignore();
            }
            break;
 
        case SID_DOCUMENT_MERGE:
        case SID_DOCUMENT_COMPARE:
            {
                bool bDo = true;
                ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack();
                if ( pChangeTrack && !m_pImpl->bIgnoreLostRedliningWarning )
                {
                    if ( nSlot == SID_DOCUMENT_COMPARE )
                    {   //! old changes trace will be lost
                        std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(),
                                                                   VclMessageType::Warning, VclButtonsType::YesNo,
                                                                   ScResId(STR_END_REDLINING)));
                        xWarn->set_default_response(RET_NO);
                        if (xWarn->run() == RET_YES)
                            bDo = ExecuteChangeProtectionDialog( true );
                        else
                            bDo = false;
                    }
                    else    // merge might reject some actions
                        bDo = ExecuteChangeProtectionDialog( true );
                }
                if ( !bDo )
                {
                    rReq.Ignore();
                    break;
                }
                SfxApplication* pApp = SfxGetpApp();
                const SfxPoolItem* pItem;
                const SfxStringItem* pFileNameItem(nullptr);
                SfxMedium* pMed = nullptr;
                if (pReqArgs)
                    pFileNameItem = pReqArgs->GetItemIfSet(SID_FILE_NAME);
                if (pFileNameItem)
                {
                    OUString aFileName = pFileNameItem->GetValue();
 
                    OUString aFilterName;
                    if (const SfxStringItem* pFilterItem = pReqArgs->GetItemIfSet(SID_FILTER_NAME))
                    {
                        aFilterName = pFilterItem->GetValue();
                    }
                    OUString aOptions;
                    if (const SfxStringItem* pOptionsItem = pReqArgs->GetItemIfSet(SID_FILE_FILTEROPTIONS))
                    {
                        aOptions = pOptionsItem->GetValue();
                    }
                    short nVersion = 0;
                    const SfxInt16Item* pInt16Item(nullptr);
                    if (pReqArgs->GetItemState(SID_VERSION, true, &pItem) == SfxItemState::SET)
                        pInt16Item = dynamic_cast<const SfxInt16Item*>(pItem);
                    if (pInt16Item)
                    {
                        nVersion = pInt16Item->GetValue();
                    }
 
                    //  no filter specified -> detection
                    if (aFilterName.isEmpty())
                        ScDocumentLoader::GetFilterName( aFileName, aFilterName, aOptions, true, false );
 
                    //  filter name from dialog contains application prefix,
                    //  GetFilter needs name without the prefix.
                    ScDocumentLoader::RemoveAppPrefix( aFilterName );
 
                    std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName( aFilterName );
                    auto pSet = std::make_shared<SfxAllItemSet>( pApp->GetPool() );
                    if (!aOptions.isEmpty())
                        pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, aOptions ) );
                    if ( nVersion != 0 )
                        pSet->Put( SfxInt16Item( SID_VERSION, nVersion ) );
                    pMed = new SfxMedium( aFileName, StreamMode::STD_READ, std::move(pFilter), std::move(pSet) );
                }
                else
                {
                    const sfx2::DocumentInserter::Mode mode { nSlot==SID_DOCUMENT_COMPARE
                        ? sfx2::DocumentInserter::Mode::Compare
                        : sfx2::DocumentInserter::Mode::Merge};
                    // start file dialog asynchronous
                    m_pImpl->bIgnoreLostRedliningWarning = true;
                    m_pImpl->pRequest.reset(new SfxRequest( rReq ));
                    m_pImpl->pDocInserter.reset();
 
                    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
                    weld::Window* pParent = pViewSh ? pViewSh->GetFrameWeld() : nullptr;
                    m_pImpl->pDocInserter.reset( new ::sfx2::DocumentInserter(pParent,
                        ScDocShell::Factory().GetFactoryName(), mode ) );
                    m_pImpl->pDocInserter->StartExecuteModal( LINK( this, ScDocShell, DialogClosedHdl ) );
                    return ;
                }
 
                // now execute in earnest...
                SfxErrorContext aEc( ERRCTX_SFX_OPENDOC, pMed->GetName() );
 
                // pOtherDocSh->DoClose() will be called explicitly later, but it is still more safe to use SfxObjectShellLock here
                rtl::Reference<ScDocShell> pOtherDocSh = new ScDocShell;
                pOtherDocSh->DoLoad( pMed );
                ErrCodeMsg nErr = pOtherDocSh->GetErrorCode();
                if (nErr)
                    ErrorHandler::HandleError( nErr );          // also warnings
 
                if ( !pOtherDocSh->GetErrorIgnoreWarning() )                 // only errors
                {
                    bool bHadTrack = ( m_pDocument->GetChangeTrack() != nullptr );
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
                    sal_uLong nStart = 0;
                    if ( nSlot == SID_DOCUMENT_MERGE && pChangeTrack )
                    {
                        nStart = pChangeTrack->GetActionMax() + 1;
                    }
#endif
                    if ( nSlot == SID_DOCUMENT_COMPARE )
                        CompareDocument( pOtherDocSh->GetDocument() );
                    else
                        MergeDocument( pOtherDocSh->GetDocument() );
 
                    //  show "accept changes" dialog
                    //! get view for this document!
                    if ( !IsDocShared() )
                    {
                        SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                        if ( pViewFrm )
                        {
                            pViewFrm->ShowChildWindow( ScAcceptChgDlgWrapper::GetChildWindowId() ); //@51669
                        }
                        if ( pBindings )
                        {
                            pBindings->Invalidate( FID_CHG_ACCEPT );
                        }
                    }
 
                    rReq.SetReturnValue( SfxInt32Item( TypedWhichId<SfxInt32Item>(nSlot), 0 ) );        //! ???????
                    rReq.Done();
 
                    if (!bHadTrack)         //  newly turned on -> show as well
                    {
                        ScChangeViewSettings* pOldSet = m_pDocument->GetChangeViewSettings();
                        if ( !pOldSet || !pOldSet->ShowChanges() )
                        {
                            ScChangeViewSettings aChangeViewSet;
                            aChangeViewSet.SetShowChanges(true);
                            m_pDocument->SetChangeViewSettings(aChangeViewSet);
                        }
                    }
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
                    else if ( nSlot == SID_DOCUMENT_MERGE && IsDocShared() && pChangeTrack )
                    {
                        sal_uLong nEnd = pChangeTrack->GetActionMax();
                        if ( nEnd >= nStart )
                        {
                            // only show changes from merged document
                            ScChangeViewSettings aChangeViewSet;
                            aChangeViewSet.SetShowChanges( true );
                            aChangeViewSet.SetShowAccepted( true );
                            aChangeViewSet.SetHasActionRange();
                            aChangeViewSet.SetTheActionRange( nStart, nEnd );
                            m_pDocument->SetChangeViewSettings( aChangeViewSet );
 
                            // update view
                            PostPaintExtras();
                            PostPaintGridAll();
                        }
                    }
#endif
                }
                pOtherDocSh->DoClose();     // delete happens with the Ref
            }
            break;
 
        case SID_DELETE_SCENARIO:
            if (pReqArgs)
            {
                const SfxPoolItem* pItem;
                if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET )
                {
                    if (const SfxStringItem* pStringItem = dynamic_cast<const SfxStringItem*>(pItem))
                    {
                        const OUString& aName = pStringItem->GetValue();
                        SCTAB nTab;
                        if (m_pDocument->GetTable( aName, nTab ))
                        {
                            //  move DeleteTable from viewfunc to docfunc!
 
                            ScTabViewShell* pSh = GetBestViewShell();
                            if ( pSh )
                            {
                                //! omit SetTabNo in DeleteTable?
                                SCTAB nDispTab = pSh->GetViewData().GetTabNo();
                                pSh->DeleteTable( nTab );
                                pSh->SetTabNo(nDispTab);
                                rReq.Done();
                            }
                        }
                    }
                }
            }
            break;
 
        case SID_EDIT_SCENARIO:
            {
                const SfxPoolItem* pItem;
                if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET )
                {
                    if (const SfxStringItem* pStringItem = dynamic_cast<const SfxStringItem*>(pItem))
                    {
                        OUString aName = pStringItem->GetValue();
                        SCTAB nTab;
                        if (m_pDocument->GetTable( aName, nTab ))
                        {
                            if (m_pDocument->IsScenario(nTab))
                            {
                                OUString aComment;
                                Color aColor;
                                ScScenarioFlags nFlags;
                                m_pDocument->GetScenarioData( nTab, aComment, aColor, nFlags );
 
                                // Determine if the Sheet that the Scenario was created on
                                // is protected. But first we need to find that Sheet.
                                // Rewind back to the actual sheet.
                                SCTAB nActualTab = nTab;
                                do
                                {
                                    nActualTab--;
                                }
                                while(m_pDocument->IsScenario(nActualTab));
                                bool bSheetProtected = m_pDocument->IsTabProtected(nActualTab);
 
                                ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
 
                                ScopedVclPtr<AbstractScNewScenarioDlg> pNewDlg(pFact->CreateScNewScenarioDlg(GetActiveDialogParent(), aName, true, bSheetProtected));
                                pNewDlg->SetScenarioData( aName, aComment, aColor, nFlags );
                                if ( pNewDlg->Execute() == RET_OK )
                                {
                                    pNewDlg->GetScenarioData( aName, aComment, aColor, nFlags );
                                    ModifyScenario( nTab, aName, aComment, aColor, nFlags );
                                    rReq.Done();
                                }
                            }
                        }
                    }
                }
            }
            break;
 
        case SID_ATTR_YEAR2000 :
        {
            const SfxPoolItem* pItem;
            if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET )
            {
                if (const SfxUInt16Item* pInt16Item = dynamic_cast<const SfxUInt16Item*>(pItem))
                {
                    sal_uInt16 nY2k = pInt16Item->GetValue();
                    // set always to DocOptions, so that it is also saved for S050
                    // (and all inquiries run up until now on it as well).
                    // SetDocOptions propagates that to the NumberFormatter
                    ScDocOptions aDocOpt( m_pDocument->GetDocOptions() );
                    aDocOpt.SetYear2000( nY2k );
                    m_pDocument->SetDocOptions( aDocOpt );
                    // the FormShell shall notice it as well
                    ScTabViewShell* pSh = GetBestViewShell();
                    if ( pSh )
                    {
                        FmFormShell* pFSh = pSh->GetFormShell();
                        if ( pFSh )
                            pFSh->SetY2KState( nY2k );
                    }
                }
            }
        }
        break;
 
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
        case SID_SHARE_DOC:
            {
                ScViewData* pViewData = GetViewData();
                if ( !pViewData )
                {
                    rReq.Ignore();
                    break;
                }
 
                weld::Window* pWin = GetActiveDialogParent();
                ScShareDocumentDlg aDlg(pWin, pViewData);
                if (aDlg.run() == RET_OK)
                {
                    bool bSetShared = aDlg.IsShareDocumentChecked();
                    if ( bSetShared != IsDocShared() )
                    {
                        if ( bSetShared )
                        {
                            bool bContinue = true;
                            if ( HasName() )
                            {
                                std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pWin,
                                                                               VclMessageType::Question, VclButtonsType::YesNo,
                                                                               ScResId(STR_DOC_WILLBESAVED)));
                                xQueryBox->set_default_response(RET_YES);
                                if (xQueryBox->run() == RET_NO)
                                {
                                    bContinue = false;
                                }
                            }
                            if ( bContinue )
                            {
                                EnableSharedSettings( true );
 
                                SC_MOD()->SetInSharedDocSaving( true );
                                if ( !SwitchToShared( true, true ) )
                                {
                                    // TODO/LATER: what should be done in case the switch has failed?
                                    // for example in case the user has cancelled the saveAs operation
                                }
 
                                SC_MOD()->SetInSharedDocSaving( false );
 
                                InvalidateName();
                                GetUndoManager()->Clear();
 
                                ScTabView* pTabView = pViewData->GetView();
                                if ( pTabView )
                                {
                                    pTabView->UpdateLayerLocks();
                                }
                            }
                        }
                        else
                        {
                            uno::Reference< frame::XModel > xModel;
                            try
                            {
                                // load shared file
                                xModel.set( LoadSharedDocument(), uno::UNO_SET_THROW );
                                uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY_THROW );
 
                                // check if shared flag is set in shared file
                                bool bShared = false;
                                ScModelObj* pDocObj = comphelper::getFromUnoTunnel<ScModelObj>( xModel );
                                if ( pDocObj )
                                {
                                    ScDocShell* pDocShell = dynamic_cast< ScDocShell* >( pDocObj->GetEmbeddedObject() );
                                    if ( pDocShell )
                                    {
                                        bShared = pDocShell->HasSharedXMLFlagSet();
                                    }
                                }
 
                                // #i87870# check if shared status was disabled and enabled again
                                bool bOwnEntry = false;
                                try
                                {
                                    ::svt::ShareControlFile aControlFile( GetSharedFileURL() );
                                    bOwnEntry = aControlFile.HasOwnEntry();
                                }
                                catch ( uno::Exception& )
                                {
                                }
 
                                if ( bShared && bOwnEntry )
                                {
                                    uno::Reference< frame::XStorable > xStorable( xModel, uno::UNO_QUERY_THROW );
                                    if ( xStorable->isReadonly() )
                                    {
                                        xCloseable->close( true );
 
                                        OUString aUserName( ScResId( STR_UNKNOWN_USER ) );
                                        try
                                        {
                                            ::svt::DocumentLockFile aLockFile( GetSharedFileURL() );
                                            LockFileEntry aData = aLockFile.GetLockData();
                                            if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() )
                                            {
                                                aUserName = aData[LockFileComponent::OOOUSERNAME];
                                            }
                                            else if ( !aData[LockFileComponent::SYSUSERNAME].isEmpty() )
                                            {
                                                aUserName = aData[LockFileComponent::SYSUSERNAME];
                                            }
                                        }
                                        catch ( uno::Exception& )
                                        {
                                        }
                                        OUString aMessage( ScResId( STR_FILE_LOCKED_TRY_LATER ) );
                                        aMessage = aMessage.replaceFirst( "%1", aUserName );
 
                                        std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin,
                                                                                   VclMessageType::Warning, VclButtonsType::Ok,
                                                                                   aMessage));
                                        xWarn->run();
                                    }
                                    else
                                    {
                                        std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin,
                                                                                   VclMessageType::Warning, VclButtonsType::YesNo,
                                                                                   ScResId(STR_DOC_DISABLESHARED)));
                                        xWarn->set_default_response(RET_YES);
 
                                        if (xWarn->run() == RET_YES)
                                        {
                                            xCloseable->close( true );
 
                                            if ( !SwitchToShared( false, true ) )
                                            {
                                                // TODO/LATER: what should be done in case the switch has failed?
                                                // for example in case the user has cancelled the saveAs operation
                                            }
 
                                            EnableSharedSettings( false );
 
                                            // Do *not* use dispatch mechanism in this place - we don't want others (extensions etc.) to intercept this.
                                            GetModel()->store();
 
                                            ScTabView* pTabView = pViewData->GetView();
                                            if ( pTabView )
                                            {
                                                pTabView->UpdateLayerLocks();
                                            }
                                        }
                                        else
                                        {
                                            xCloseable->close( true );
                                        }
                                    }
                                }
                                else
                                {
                                    xCloseable->close( true );
                                    std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin,
                                                                               VclMessageType::Warning, VclButtonsType::Ok,
                                                                               ScResId(STR_DOC_NOLONGERSHARED)));
                                    xWarn->run();
                                }
                            }
                            catch ( uno::Exception& )
                            {
                                TOOLS_WARN_EXCEPTION( "sc", "SID_SHARE_DOC" );
                                SC_MOD()->SetInSharedDocSaving( false );
 
                                try
                                {
                                    uno::Reference< util::XCloseable > xClose( xModel, uno::UNO_QUERY_THROW );
                                    xClose->close( true );
                                }
                                catch ( uno::Exception& )
                                {
                                }
                            }
                        }
                    }
                }
                rReq.Done();
            }
            break;
#endif
        case SID_OPEN_CALC:
        {
            ScViewData* pViewData = GetViewData();
            if (pViewData)
            {
                SfxStringItem aApp(SID_DOC_SERVICE, u"com.sun.star.sheet.SpreadsheetDocument"_ustr);
                SfxStringItem aTarget(SID_TARGETNAME, u"_blank"_ustr);
                pViewData->GetDispatcher().ExecuteList(
                    SID_OPENDOC, SfxCallMode::API|SfxCallMode::SYNCHRON,
                    { &aApp, &aTarget });
            }
        }
        break;
        case SID_NOTEBOOKBAR:
        {
            const SfxStringItem* pFile = rReq.GetArg<SfxStringItem>( SID_NOTEBOOKBAR );
 
            if ( pBindings && sfx2::SfxNotebookBar::IsActive() )
                sfx2::SfxNotebookBar::ExecMethod(*pBindings, pFile ? pFile->GetValue() : u""_ustr);
            else if ( pBindings )
                sfx2::SfxNotebookBar::CloseMethod(*pBindings);
        }
        break;
        case SID_LANGUAGE_STATUS:
        {
            OUString aLangText;
            const SfxStringItem* pItem = rReq.GetArg<SfxStringItem>(nSlot);
            if ( pItem )
                aLangText = pItem->GetValue();
 
            if ( !aLangText.isEmpty() )
            {
                LanguageType eLang, eLatin, eCjk, eCtl;
                static constexpr OUString aSelectionLangPrefix(u"Current_"_ustr);
                static constexpr OUString aParagraphLangPrefix(u"Paragraph_"_ustr);
                static constexpr OUString aDocLangPrefix(u"Default_"_ustr);
 
                bool bSelection = false;
                bool bParagraph = false;
 
                ScDocument& rDoc = GetDocument();
                rDoc.GetLanguage( eLatin, eCjk, eCtl );
 
                sal_Int32 nPos = 0;
                if ( aLangText == "*" )
                {
                    if (ScTabViewShell* pSh = GetBestViewShell())
                    {
                        pSh->ExecuteCellFormatDlg(rReq, u"font"_ustr);
                        pBindings->Invalidate(SID_LANGUAGE_STATUS);
                    }
                }
                else if ( (nPos = aLangText.indexOf(aDocLangPrefix)) != -1 )
                {
                    aLangText = aLangText.replaceAt(nPos, aDocLangPrefix.getLength(), u"");
 
                    if ( aLangText == "LANGUAGE_NONE" )
                    {
                        eLang = LANGUAGE_NONE;
                        rDoc.SetLanguage( eLang, eCjk, eCtl );
                    }
                    else if ( aLangText == "RESET_LANGUAGES" )
                    {
                        ScModule::GetSpellSettings(eLang, eCjk, eCtl);
                        rDoc.SetLanguage(eLang, eCjk, eCtl);
                    }
                    else
                    {
                        eLang = SvtLanguageTable::GetLanguageType( aLangText );
                        if ( eLang != LANGUAGE_DONTKNOW  && SvtLanguageOptions::GetScriptTypeOfLanguage(eLang) == SvtScriptType::LATIN )
                        {
                            rDoc.SetLanguage( eLang, eCjk, eCtl );
                        }
                        else
                        {
                            eLang = eLatin;
                        }
                    }
                }
                else if (-1 != (nPos = aLangText.indexOf( aSelectionLangPrefix )))
                {
                    bSelection = true;
                    aLangText = aLangText.replaceAt( nPos, aSelectionLangPrefix.getLength(), u"" );
                }
                else if (-1 != (nPos = aLangText.indexOf( aParagraphLangPrefix )))
                {
                    bParagraph = true;
                    aLangText = aLangText.replaceAt( nPos, aParagraphLangPrefix.getLength(), u"" );
                }
 
                if (bSelection)
                {
                    ScTabViewShell* pViewShell = GetBestViewShell();
                    if (pViewShell)
                    {
                        const ScPatternAttr* pSelAttrs = pViewShell->GetSelectionPattern();
                        if (pSelAttrs)
                        {
                            const SfxItemSet& rOldSet = pSelAttrs->GetItemSet();
                            SfxItemPool* pItemPool = rOldSet.GetPool();
                            auto pNewSet = std::make_shared<SfxItemSet>(rOldSet);
 
                            if (aLangText == "LANGUAGE_NONE")
                            {
                                pNewSet->Put(SvxLanguageItem(LANGUAGE_NONE,
                                                             pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_LANGUAGE)));
                                pNewSet->Put(SvxLanguageItem(LANGUAGE_NONE,
                                                             pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_CJK_LANGUAGE)));
                                pNewSet->Put(SvxLanguageItem(LANGUAGE_NONE,
                                                             pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_CTL_LANGUAGE)));
                            }
                            else
                            {
                                const LanguageType nLangType = SvtLanguageTable::GetLanguageType(aLangText);
                                const SvtScriptType nScriptType =
                                    SvtLanguageOptions::GetScriptTypeOfLanguage(nLangType);
                                if (nScriptType == SvtScriptType::LATIN)
                                    pNewSet->Put(SvxLanguageItem(nLangType,
                                                                 pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_LANGUAGE)));
                                if (nScriptType == SvtScriptType::COMPLEX)
                                    pNewSet->Put(SvxLanguageItem(nLangType,
                                                                 pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_CTL_LANGUAGE)));
                                if (nScriptType == SvtScriptType::ASIAN)
                                    pNewSet->Put(SvxLanguageItem(nLangType,
                                                                 pItemPool->GetWhichIDFromSlotID(SID_ATTR_CHAR_CJK_LANGUAGE)));
                            }
                            pViewShell->ApplyAttributes(*pNewSet, rOldSet);
                            pBindings->Invalidate(SID_LANGUAGE_STATUS);
                        }
                    }
                }
                else if (bParagraph)
                {
                    ScViewData* pViewData = GetViewData();
                    if (!pViewData)
                        return;
 
                    EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart());
                    if (!pEditView)
                        return;
 
                    const LanguageType nLangToUse = SvtLanguageTable::GetLanguageType( aLangText );
                    SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLangToUse );
 
                    SfxItemSet aAttrs = pEditView->getEditEngine().GetEmptyItemSet();
                    if (nScriptType == SvtScriptType::LATIN)
                        aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE ) );
                    if (nScriptType == SvtScriptType::COMPLEX)
                        aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CTL ) );
                    if (nScriptType == SvtScriptType::ASIAN)
                        aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CJK ) );
                    ESelection aOldSel;
                    if (bParagraph)
                    {
                        ESelection aSel = pEditView->GetSelection();
                        aOldSel = aSel;
                        aSel.nStartPos = 0;
                        aSel.nEndPos = EE_TEXTPOS_ALL;
                        pEditView->SetSelection( aSel );
                    }
 
                    pEditView->SetAttribs( aAttrs );
                    if (bParagraph)
                        pEditView->SetSelection( aOldSel );
                }
                else if ( eLang != eLatin )
                {
                    if ( ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell() )
                    {
                        ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(pViewSh);
                        if ( pInputHandler )
                            pInputHandler->UpdateSpellSettings();
 
                        pViewSh->UpdateDrawTextOutliner();
                    }
 
                    SetDocumentModified();
                    Broadcast(SfxHint(SfxHintId::LanguageChanged));
                    PostPaintGridAll();
                }
            }
        }
        break;
        case SID_SPELLCHECK_IGNORE_ALL:
        {
            ScViewData* pViewData = GetViewData();
            if (!pViewData)
                return;
 
            EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart());
            if (!pEditView)
                return;
 
            OUString sIgnoreText;
            const SfxStringItem* pItem2 = rReq.GetArg<SfxStringItem>(FN_PARAM_1);
            if (pItem2)
                sIgnoreText = pItem2->GetValue();
 
            if(sIgnoreText == "Spelling")
            {
                ESelection aOldSel = pEditView->GetSelection();
                pEditView->SpellIgnoreWord();
                pEditView->SetSelection( aOldSel );
            }
        }
        break;
        case SID_SPELLCHECK_APPLY_SUGGESTION:
        {
            ScViewData* pViewData = GetViewData();
            if (!pViewData)
                return;
 
            EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart());
            if (!pEditView)
                return;
 
            OUString sApplyText;
            const SfxStringItem* pItem2 = rReq.GetArg<SfxStringItem>(FN_PARAM_1);
            if (pItem2)
                sApplyText = pItem2->GetValue();
 
            static constexpr OUString sSpellingRule(u"Spelling_"_ustr);
            sal_Int32 nPos = 0;
            if(-1 != (nPos = sApplyText.indexOf( sSpellingRule )))
            {
                sApplyText = sApplyText.replaceAt(nPos, sSpellingRule.getLength(), u"");
                pEditView->InsertText( sApplyText );
            }
        }
        break;
        case SID_REFRESH_VIEW:
        {
            PostPaintGridAll();
        }
        break;
        default:
        {
            // small (?) hack -> forwarding of the slots to TabViewShell
            ScTabViewShell* pSh = GetBestViewShell();
            if ( pSh )
                pSh->Execute( rReq );
#if HAVE_FEATURE_SCRIPTING
            else
                SbxBase::SetError( ERRCODE_BASIC_NO_ACTIVE_OBJECT );
#endif
        }
    }
}
 
void UpdateAcceptChangesDialog()
{
    //  update "accept changes" dialog
    //! notify all views
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    if ( pViewFrm && pViewFrm->HasChildWindow( FID_CHG_ACCEPT ) )
    {
        SfxChildWindow* pChild = pViewFrm->GetChildWindow( FID_CHG_ACCEPT );
        if ( pChild )
            static_cast<ScAcceptChgDlgWrapper*>(pChild)->ReInitDlg();
    }
}
 
void ScDocShell::ExecuteChartSource(SfxRequest& rReq)
{
    const SfxItemSet* pReqArgs = rReq.GetArgs();
    sal_uInt16 nSlot = rReq.GetSlot();
    bool bUndo (m_pDocument->IsUndoEnabled());
    if (!pReqArgs)
    {
        OSL_FAIL("SID_CHART_SOURCE without arguments");
        return;
    }
 
    ScDocument& rDoc = GetDocument();
    const   SfxPoolItem* pItem;
    OUString  aChartName, aRangeName;
 
    ScRange         aSingleRange;
    ScRangeListRef  aRangeListRef;
    bool            bMultiRange = false;
 
    bool bColHeaders = true;
    bool bRowHeaders = true;
    bool bColInit = false;
    bool bRowInit = false;
    bool bAddRange = (nSlot == SID_CHART_ADDSOURCE);
 
    if( const SfxStringItem* pChartItem = pReqArgs->GetItemIfSet( SID_CHART_NAME ) )
        aChartName = pChartItem->GetValue();
 
    if( const SfxStringItem* pChartItem = pReqArgs->GetItemIfSet( SID_CHART_SOURCE ) )
        aRangeName = pChartItem->GetValue();
 
    if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) )
    {
        bColHeaders = static_cast<const SfxBoolItem*>(pItem)->GetValue();
        bColInit = true;
    }
    if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) )
    {
        bRowHeaders = static_cast<const SfxBoolItem*>(pItem)->GetValue();
        bRowInit = true;
    }
 
    ScAddress::Details aDetails(rDoc.GetAddressConvention(), 0, 0);
    bool bValid = (aSingleRange.ParseAny(aRangeName, rDoc, aDetails) & ScRefFlags::VALID) != ScRefFlags::ZERO;
    if (!bValid)
    {
        aRangeListRef = new ScRangeList;
        aRangeListRef->Parse( aRangeName, rDoc, rDoc.GetAddressConvention());
        if ( !aRangeListRef->empty() )
        {
            bMultiRange = true;
            aSingleRange = aRangeListRef->front(); // for header
            bValid = true;
        }
        else
            aRangeListRef.clear();
    }
 
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if (!pViewSh || !bValid || aChartName.isEmpty() )
    {
        OSL_FAIL("UpdateChartArea: no ViewShell or wrong data");
        rReq.Done();
        return;
    }
 
    weld::Window* pParent = pViewSh->GetFrameWeld();
 
    SCCOL nCol1 = aSingleRange.aStart.Col();
    SCROW nRow1 = aSingleRange.aStart.Row();
    SCCOL nCol2 = aSingleRange.aEnd.Col();
    SCROW nRow2 = aSingleRange.aEnd.Row();
    SCTAB nTab = aSingleRange.aStart.Tab();
 
    //! limit always or not at all ???
    if (!bMultiRange)
        m_pDocument->LimitChartArea( nTab, nCol1,nRow1, nCol2,nRow2 );
 
    // Dialog for column/row headers
    if ( !bAddRange && ( !bColInit || !bRowInit ) )
    {
        ScChartPositioner aChartPositioner( *m_pDocument, nTab, nCol1,nRow1, nCol2,nRow2 );
        if (!bColInit)
            bColHeaders = aChartPositioner.HasColHeaders();
        if (!bRowInit)
            bRowHeaders = aChartPositioner.HasRowHeaders();
 
        auto xRequest = std::make_shared<SfxRequest>(rReq);
        rReq.Ignore(); // the 'old' request is not relevant any more
        ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
        VclPtr<AbstractScColRowLabelDlg> pDlg(pFact->CreateScColRowLabelDlg(pParent, bRowHeaders, bColHeaders));
        pDlg->StartExecuteAsync(
            [this, pDlg, xRequest=std::move(xRequest), bUndo, bMultiRange,
             aChartName, aRangeListRef=std::move(aRangeListRef), bAddRange,
             nCol1, nRow1, nCol2, nRow2, nTab] (sal_Int32 nResult)->void
            {
                if (nResult == RET_OK)
                {
                    bool bColHeaders2 = pDlg->IsRow();
                    bool bRowHeaders2 = pDlg->IsCol();
 
                    xRequest->AppendItem(SfxBoolItem(FN_PARAM_1, bColHeaders2));
                    xRequest->AppendItem(SfxBoolItem(FN_PARAM_2, bRowHeaders2));
                    ExecuteChartSourcePost(bUndo, bMultiRange,
                        aChartName, aRangeListRef, bColHeaders2, bRowHeaders2, bAddRange,
                        nCol1, nRow1, nCol2, nRow2, nTab);
                }
                pDlg->disposeOnce();
                xRequest->Done();
            }
        );
    }
    else
    {
        ExecuteChartSourcePost(bUndo, bMultiRange,
            aChartName, aRangeListRef, bColHeaders, bRowHeaders, bAddRange,
            nCol1, nRow1,nCol2, nRow2, nTab);
        rReq.Done();
    }
}
 
void ScDocShell::ExecuteChartSourcePost(bool bUndo, bool bMultiRange,
    const OUString& rChartName, const ScRangeListRef& rRangeListRef,
    bool bColHeaders, bool bRowHeaders, bool bAddRange,
    SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SCTAB nTab )
{
    if (bMultiRange)
    {
        if (bUndo)
        {
            GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoChartData>( this, rChartName, rRangeListRef,
                                        bColHeaders, bRowHeaders, bAddRange ) );
        }
        m_pDocument->UpdateChartArea( rChartName, rRangeListRef,
                                    bColHeaders, bRowHeaders, bAddRange );
    }
    else
    {
        ScRange aNewRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab );
        if (bUndo)
        {
            GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoChartData>( this, rChartName, aNewRange,
                                        bColHeaders, bRowHeaders, bAddRange ) );
        }
        m_pDocument->UpdateChartArea( rChartName, aNewRange,
                                    bColHeaders, bRowHeaders, bAddRange );
    }
}
 
bool ScDocShell::ExecuteChangeProtectionDialog( bool bJustQueryIfProtected )
{
    bool bDone = false;
    ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack();
    if ( pChangeTrack )
    {
        bool bProtected = pChangeTrack->IsProtected();
        if ( bJustQueryIfProtected && !bProtected )
            return true;
 
        OUString aTitle( ScResId( bProtected ? SCSTR_CHG_UNPROTECT : SCSTR_CHG_PROTECT ) );
        OUString aText( ScResId( SCSTR_PASSWORD ) );
        OUString aPassword;
 
        weld::Window* pWin = ScDocShell::GetActiveDialogParent();
        SfxPasswordDialog aDlg(pWin, &aText);
        aDlg.set_title(aTitle);
        aDlg.SetMinLen(1);
        aDlg.set_help_id(GetStaticInterface()->GetSlot(SID_CHG_PROTECT)->GetCommand());
        aDlg.SetEditHelpId( HID_CHG_PROTECT );
        if ( !bProtected )
            aDlg.ShowExtras(SfxShowExtras::CONFIRM);
        if (aDlg.run() == RET_OK)
            aPassword = aDlg.GetPassword();
 
        if (!aPassword.isEmpty())
        {
            if ( bProtected )
            {
                if ( SvPasswordHelper::CompareHashPassword(pChangeTrack->GetProtection(), aPassword) )
                {
                    if ( bJustQueryIfProtected )
                        bDone = true;
                    else
                        pChangeTrack->SetProtection( {} );
                }
                else
                {
                    std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin,
                                                                  VclMessageType::Info, VclButtonsType::Ok,
                                                                  ScResId(SCSTR_WRONGPASSWORD)));
                    xInfoBox->run();
                }
            }
            else
            {
                css::uno::Sequence< sal_Int8 > aPass;
                SvPasswordHelper::GetHashPassword( aPass, aPassword );
                pChangeTrack->SetProtection( aPass );
            }
            if ( bProtected != pChangeTrack->IsProtected() )
            {
                UpdateAcceptChangesDialog();
                bDone = true;
            }
        }
    }
    else if ( bJustQueryIfProtected )
        bDone = true;
    return bDone;
}
 
void ScDocShell::DoRecalc( bool bApi )
{
    if (m_pDocument->IsInDocShellRecalc())
    {
        SAL_WARN("sc","ScDocShell::DoRecalc tries re-entering while in Recalc; probably Forms->BASIC->Dispatcher.");
        return;
    }
    ScDocShellRecalcGuard aGuard(*m_pDocument);
    bool bDone = false;
    ScTabViewShell* pSh = GetBestViewShell();
    ScInputHandler* pHdl = ( pSh ? SC_MOD()->GetInputHdl( pSh ) : nullptr );
    if ( pSh )
    {
        if ( pHdl && pHdl->IsInputMode() && pHdl->IsFormulaMode() && !bApi )
        {
            pHdl->FormulaPreview();     // partial result as QuickHelp
            bDone = true;
        }
        else
        {
            ScTabView::UpdateInputLine();     // InputEnterHandler
            pSh->UpdateInputHandler();
        }
    }
    if (bDone)                         // otherwise re-calculate document
        return;
 
    weld::WaitObject aWaitObj( GetActiveDialogParent() );
    if ( pHdl )
    {
        // tdf97897 set current cell to Dirty to force recalculation of cell
        ScFormulaCell* pFC = m_pDocument->GetFormulaCell( pHdl->GetCursorPos());
        if (pFC)
            pFC->SetDirty();
    }
    m_pDocument->CalcFormulaTree();
    if ( pSh )
        pSh->UpdateCharts(true);
 
    m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) );
 
    //  If there are charts, then paint everything, so that PostDataChanged
    //  and the charts do not come one after the other and parts are painted twice.
 
    ScChartListenerCollection* pCharts = m_pDocument->GetChartListenerCollection();
    if ( pCharts && pCharts->hasListeners() )
        PostPaintGridAll();
    else
        PostDataChanged();
}
 
void ScDocShell::DoHardRecalc()
{
    if (m_pDocument->IsInDocShellRecalc())
    {
        SAL_WARN("sc","ScDocShell::DoHardRecalc tries re-entering while in Recalc; probably Forms->BASIC->Dispatcher.");
        return;
    }
    auto start = std::chrono::steady_clock::now();
    ScDocShellRecalcGuard aGuard(*m_pDocument);
    weld::WaitObject aWaitObj( GetActiveDialogParent() );
    ScTabViewShell* pSh = GetBestViewShell();
    if ( pSh )
    {
        ScTabView::UpdateInputLine();     // InputEnterHandler
        pSh->UpdateInputHandler();
    }
    m_pDocument->CalcAll();
    GetDocFunc().DetectiveRefresh();    // creates own Undo
    if ( pSh )
        pSh->UpdateCharts(true);
 
    // set notification flags for "calculate" event (used in SfxHintId::DataChanged broadcast)
    // (might check for the presence of any formulas on each sheet)
    SCTAB nTabCount = m_pDocument->GetTableCount();
    if (m_pDocument->HasAnySheetEventScript( ScSheetEventId::CALCULATE, true )) // search also for VBA handler
        for (SCTAB nTab=0; nTab<nTabCount; nTab++)
            m_pDocument->SetCalcNotification(nTab);
 
    // CalcAll doesn't broadcast value changes, so SfxHintId::ScCalcAll is broadcasted globally
    // in addition to SfxHintId::DataChanged.
    m_pDocument->BroadcastUno( SfxHint( SfxHintId::ScCalcAll ) );
    m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) );
 
    // use hard recalc also to disable stream-copying of all sheets
    // (somewhat consistent with charts)
    for (SCTAB nTab=0; nTab<nTabCount; nTab++)
        m_pDocument->SetStreamValid(nTab, false);
 
    PostPaintGridAll();
    auto end = std::chrono::steady_clock::now();
    SAL_INFO("sc.timing", "ScDocShell::DoHardRecalc(): took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms");
}
 
void ScDocShell::DoAutoStyle( const ScRange& rRange, const OUString& rStyle )
{
    ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool();
    ScStyleSheet* pStyleSheet = pStylePool->FindAutoStyle(rStyle);
    if (!pStyleSheet)
        return;
 
    OSL_ENSURE(rRange.aStart.Tab() == rRange.aEnd.Tab(),
                    "DoAutoStyle with several tables");
    SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCCOL nEndCol = rRange.aEnd.Col();
    SCROW nEndRow = rRange.aEnd.Row();
    m_pDocument->ApplyStyleAreaTab( nStartCol, nStartRow, nEndCol, nEndRow, nTab, *pStyleSheet );
    m_pDocument->ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, nTab );
    PostPaint( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab, PaintPartFlags::Grid );
}
 
void ScDocShell::NotifyStyle( const SfxStyleSheetHint& rHint )
{
    SfxHintId nId = rHint.GetId();
    const SfxStyleSheetBase* pStyle = rHint.GetStyleSheet();
    if (!pStyle)
        return;
 
    if ( pStyle->GetFamily() == SfxStyleFamily::Page )
    {
        if ( nId == SfxHintId::StyleSheetModified || nId == SfxHintId::StyleSheetModifiedExtended )
        {
            ScDocShellModificator aModificator( *this );
 
            const OUString& aNewName = pStyle->GetName();
            OUString aOldName = aNewName;
            if ( nId == SfxHintId::StyleSheetModifiedExtended )
            {
                const SfxStyleSheetModifiedHint& rExtendedHint = static_cast<const SfxStyleSheetModifiedHint&>(rHint); // name changed?
                aOldName = rExtendedHint.GetOldName();
            }
            if ( aNewName != aOldName )
                m_pDocument->RenamePageStyleInUse( aOldName, aNewName );
 
            SCTAB nTabCount = m_pDocument->GetTableCount();
            for (SCTAB nTab=0; nTab<nTabCount; nTab++)
                if (m_pDocument->GetPageStyle(nTab) == aNewName)   // already adjusted to new
                {
                    m_pDocument->PageStyleModified( nTab, aNewName );
                    ScPrintFunc aPrintFunc( this, GetPrinter(), nTab );
                    aPrintFunc.UpdatePages();
                }
 
            aModificator.SetDocumentModified();
 
            if (nId == SfxHintId::StyleSheetModifiedExtended)
            {
                SfxBindings* pBindings = GetViewBindings();
                if (pBindings)
                {
                    pBindings->Invalidate( SID_STATUS_PAGESTYLE );
                    pBindings->Invalidate( SID_STYLE_FAMILY4 );
                    pBindings->Invalidate( FID_RESET_PRINTZOOM );
                    pBindings->Invalidate( SID_ATTR_PARA_LEFT_TO_RIGHT );
                    pBindings->Invalidate( SID_ATTR_PARA_RIGHT_TO_LEFT );
                }
            }
        }
    }
    else if ( pStyle->GetFamily() == SfxStyleFamily::Para )
    {
        if (nId == SfxHintId::StyleSheetModifiedExtended)
        {
            const SfxStyleSheetModifiedHint& rExtendedHint = static_cast<const SfxStyleSheetModifiedHint&>(rHint);
            const OUString& aNewName = pStyle->GetName();
            const OUString& aOldName = rExtendedHint.GetOldName();
            if ( aNewName != aOldName )
            {
                for(SCTAB i = 0; i < m_pDocument->GetTableCount(); ++i)
                {
                    ScConditionalFormatList* pList = m_pDocument->GetCondFormList(i);
                    if (pList)
                        pList->RenameCellStyle( aOldName,aNewName );
                }
            }
        }
    }
 
    //  everything else goes via slots...
}
 
//  like in printfun.cxx
#define ZOOM_MIN    10
 
void ScDocShell::SetPrintZoom( SCTAB nTab, sal_uInt16 nScale, sal_uInt16 nPages )
{
    OUString aStyleName = m_pDocument->GetPageStyle( nTab );
    ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool();
    SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, SfxStyleFamily::Page );
    OSL_ENSURE( pStyleSheet, "PageStyle not found" );
    if ( !pStyleSheet )
        return;
 
    ScDocShellModificator aModificator( *this );
 
    SfxItemSet& rSet = pStyleSheet->GetItemSet();
    const bool bUndo(m_pDocument->IsUndoEnabled());
    if (bUndo)
    {
        sal_uInt16 nOldScale = rSet.Get(ATTR_PAGE_SCALE).GetValue();
        sal_uInt16 nOldPages = rSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue();
        GetUndoManager()->AddUndoAction( std::make_unique<ScUndoPrintZoom>(
                        this, nTab, nOldScale, nOldPages, nScale, nPages ) );
    }
 
    rSet.Put( SfxUInt16Item( ATTR_PAGE_SCALE, nScale ) );
    rSet.Put( SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, nPages ) );
 
    ScPrintFunc aPrintFunc( this, GetPrinter(), nTab );
    aPrintFunc.UpdatePages();
    aModificator.SetDocumentModified();
 
    SfxBindings* pBindings = GetViewBindings();
    if (pBindings)
        pBindings->Invalidate( FID_RESET_PRINTZOOM );
}
 
bool ScDocShell::AdjustPrintZoom( const ScRange& rRange )
{
    bool bChange = false;
    SCTAB nTab = rRange.aStart.Tab();
 
    OUString aStyleName = m_pDocument->GetPageStyle( nTab );
    ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool();
    SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, SfxStyleFamily::Page );
    OSL_ENSURE( pStyleSheet, "PageStyle not found" );
    if ( pStyleSheet )
    {
        SfxItemSet& rSet = pStyleSheet->GetItemSet();
        bool bHeaders = rSet.Get(ATTR_PAGE_HEADERS).GetValue();
        sal_uInt16 nOldScale = rSet.Get(ATTR_PAGE_SCALE).GetValue();
        sal_uInt16 nOldPages = rSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue();
        std::optional<ScRange> oRepeatCol = m_pDocument->GetRepeatColRange( nTab );
        std::optional<ScRange> oRepeatRow = m_pDocument->GetRepeatRowRange( nTab );
 
        //  calculate needed scaling for selection
 
        sal_uInt16 nNewScale = nOldScale;
 
        tools::Long nBlkTwipsX = 0;
        if (bHeaders)
            nBlkTwipsX += PRINT_HEADER_WIDTH;
        SCCOL nStartCol = rRange.aStart.Col();
        SCCOL nEndCol = rRange.aEnd.Col();
        if ( oRepeatCol && nStartCol >= oRepeatCol->aStart.Col() )
        {
            for (SCCOL i=oRepeatCol->aStart.Col(); i<=oRepeatCol->aEnd.Col(); i++ )
                nBlkTwipsX += m_pDocument->GetColWidth( i, nTab );
            if ( nStartCol <= oRepeatCol->aEnd.Col() )
                nStartCol = oRepeatCol->aEnd.Col() + 1;
        }
        // legacy compilers' own scope for i
        {
            for ( SCCOL i=nStartCol; i<=nEndCol; i++ )
                nBlkTwipsX += m_pDocument->GetColWidth( i, nTab );
        }
 
        tools::Long nBlkTwipsY = 0;
        if (bHeaders)
            nBlkTwipsY += PRINT_HEADER_HEIGHT;
        SCROW nStartRow = rRange.aStart.Row();
        SCROW nEndRow = rRange.aEnd.Row();
        if ( oRepeatRow && nStartRow >= oRepeatRow->aStart.Row() )
        {
            nBlkTwipsY += m_pDocument->GetRowHeight( oRepeatRow->aStart.Row(),
                    oRepeatRow->aEnd.Row(), nTab );
            if ( nStartRow <= oRepeatRow->aEnd.Row() )
                nStartRow = oRepeatRow->aEnd.Row() + 1;
        }
        nBlkTwipsY += m_pDocument->GetRowHeight( nStartRow, nEndRow, nTab );
 
        Size aPhysPage;
        tools::Long nHdr, nFtr;
        ScPrintFunc aOldPrFunc( this, GetPrinter(), nTab );
        aOldPrFunc.GetScaleData( aPhysPage, nHdr, nFtr );
        nBlkTwipsY += nHdr + nFtr;
 
        if ( nBlkTwipsX == 0 )      // hidden columns/rows may lead to 0
            nBlkTwipsX = 1;
        if ( nBlkTwipsY == 0 )
            nBlkTwipsY = 1;
 
        tools::Long nNeeded = std::min( aPhysPage.Width()  * 100 / nBlkTwipsX,
                            aPhysPage.Height() * 100 / nBlkTwipsY );
        if ( nNeeded < ZOOM_MIN )
            nNeeded = ZOOM_MIN;         // boundary
        if ( nNeeded < static_cast<tools::Long>(nNewScale) )
            nNewScale = static_cast<sal_uInt16>(nNeeded);
 
        bChange = ( nNewScale != nOldScale || nOldPages != 0 );
        if ( bChange )
            SetPrintZoom( nTab, nNewScale, 0 );
    }
    return bChange;
}
 
void ScDocShell::PageStyleModified( std::u16string_view rStyleName, bool bApi )
{
    ScDocShellModificator aModificator( *this );
 
    SCTAB nTabCount = m_pDocument->GetTableCount();
    SCTAB nUseTab = MAXTAB+1;
    for (SCTAB nTab=0; nTab<nTabCount && nUseTab>MAXTAB; nTab++)
        if ( m_pDocument->GetPageStyle(nTab) == rStyleName &&
                ( !bApi || m_pDocument->GetPageSize(nTab).Width() ) )
            nUseTab = nTab;
                                // at bApi only if breaks already shown
 
    if (ValidTab(nUseTab))      // not used -> nothing to do
    {
        bool bWarn = false;
 
        ScPrintFunc aPrintFunc( this, GetPrinter(), nUseTab );  //! cope without CountPages
        if (!aPrintFunc.UpdatePages())                          //  sets breaks on all tabs
            bWarn = true;
 
        if (bWarn && !bApi)
        {
            weld::Window* pWin = GetActiveDialogParent();
            weld::WaitObject aWaitOff(pWin);
            std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin,
                                                          VclMessageType::Info, VclButtonsType::Ok,
                                                          ScResId(STR_PRINT_INVALID_AREA)));
            xInfoBox->run();
        }
    }
 
    aModificator.SetDocumentModified();
 
    SfxBindings* pBindings = GetViewBindings();
    if (pBindings)
    {
        pBindings->Invalidate( FID_RESET_PRINTZOOM );
        pBindings->Invalidate( SID_ATTR_PARA_LEFT_TO_RIGHT );
        pBindings->Invalidate( SID_ATTR_PARA_RIGHT_TO_LEFT );
    }
}
 
void ScDocShell::ExecutePageStyle( const SfxViewShell& rCaller,
                                   SfxRequest&   rReq,
                                   SCTAB         nCurTab )
{
    const SfxItemSet* pReqArgs = rReq.GetArgs();
 
    switch ( rReq.GetSlot() )
    {
        case SID_STATUS_PAGESTYLE:  // click on StatusBar control
        case SID_FORMATPAGE:
            {
                if ( pReqArgs == nullptr )
                {
                    OUString aOldName = m_pDocument->GetPageStyle( nCurTab );
                    ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool();
                    SfxStyleSheetBase* pStyleSheet
                        = pStylePool->Find( aOldName, SfxStyleFamily::Page );
 
                    OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" );
 
                    if ( pStyleSheet )
                    {
                        auto xOldData = std::make_shared<ScStyleSaveData>();
                        const bool bUndo(m_pDocument->IsUndoEnabled());
                        if (bUndo)
                            xOldData->InitFromStyle(pStyleSheet);
 
                        SfxItemSet&     rStyleSet = pStyleSheet->GetItemSet();
                        rStyleSet.MergeRange( XATTR_FILL_FIRST, XATTR_FILL_LAST );
 
                        ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
 
                        VclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateScStyleDlg(GetActiveDialogParent(), *pStyleSheet, true));
 
                        auto xRequest = std::make_shared<SfxRequest>(rReq);
                        rReq.Ignore(); // the 'old' request is not relevant any more
                        pDlg->StartExecuteAsync([this, pDlg, xRequest=std::move(xRequest), pStyleSheet,
                                                 xOldData=std::move(xOldData), aOldName, &rStyleSet,
                                                 nCurTab, &rCaller, bUndo](sal_Int32 nResult) {
                            if ( nResult == RET_OK )
                            {
                                const SfxItemSet* pOutSet = pDlg->GetOutputItemSet();
 
                                weld::WaitObject aWait( GetActiveDialogParent() );
 
                                OUString aNewName = pStyleSheet->GetName();
                                if ( aNewName != aOldName &&
                                    m_pDocument->RenamePageStyleInUse( aOldName, aNewName ) )
                                {
                                    SfxBindings* pBindings = GetViewBindings();
                                    if (pBindings)
                                    {
                                        pBindings->Invalidate( SID_STATUS_PAGESTYLE );
                                        pBindings->Invalidate( FID_RESET_PRINTZOOM );
                                    }
                                }
 
                                if ( pOutSet )
                                    m_pDocument->ModifyStyleSheet( *pStyleSheet, *pOutSet );
 
                                // memorizing for GetState():
                                GetPageOnFromPageStyleSet( &rStyleSet, nCurTab, m_bHeaderOn, m_bFooterOn );
                                rCaller.GetViewFrame().GetBindings().Invalidate( SID_HFEDIT );
 
                                ScStyleSaveData aNewData;
                                aNewData.InitFromStyle( pStyleSheet );
                                if (bUndo)
                                {
                                    GetUndoManager()->AddUndoAction(
                                            std::make_unique<ScUndoModifyStyle>( this, SfxStyleFamily::Page,
                                                        *xOldData, aNewData ) );
                                }
 
                                PageStyleModified( aNewName, false );
                                xRequest->Done();
                            }
                            pDlg->disposeOnce();
                        });
                    }
                }
            }
            break;
 
        case SID_HFEDIT:
            {
                if ( pReqArgs == nullptr )
                {
                    OUString aStr( m_pDocument->GetPageStyle( nCurTab ) );
 
                    ScStyleSheetPool* pStylePool
                        = m_pDocument->GetStyleSheetPool();
 
                    SfxStyleSheetBase* pStyleSheet
                        = pStylePool->Find( aStr, SfxStyleFamily::Page );
 
                    OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" );
 
                    if ( pStyleSheet )
                    {
                        SfxItemSet&  rStyleSet = pStyleSheet->GetItemSet();
 
                        SvxPageUsage eUsage = rStyleSet.Get( ATTR_PAGE ).GetPageUsage();
                        bool bShareHeader = rStyleSet
                                                    .Get(ATTR_PAGE_HEADERSET)
                                                    .GetItemSet()
                                                    .Get(ATTR_PAGE_SHARED)
                                                    .GetValue();
                        bool bShareFooter = rStyleSet
                                                    .Get(ATTR_PAGE_FOOTERSET)
                                                    .GetItemSet()
                                                    .Get(ATTR_PAGE_SHARED)
                                                    .GetValue();
                        sal_uInt16 nResId = 0;
 
                        switch ( eUsage )
                        {
                            case SvxPageUsage::Left:
                            case SvxPageUsage::Right:
                            {
                                if ( m_bHeaderOn && m_bFooterOn )
                                    nResId = RID_SCDLG_HFEDIT;
                                else if ( SvxPageUsage::Right == eUsage )
                                {
                                    if ( !m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER;
                                    else if ( m_bHeaderOn && !m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_RIGHTHEADER;
                                }
                                else
                                {
                                    //  #69193a# respect "shared" setting
                                    if ( !m_bHeaderOn && m_bFooterOn )
                                        nResId = bShareFooter ?
                                                    RID_SCDLG_HFEDIT_RIGHTFOOTER :
                                                    RID_SCDLG_HFEDIT_LEFTFOOTER;
                                    else if ( m_bHeaderOn && !m_bFooterOn )
                                        nResId = bShareHeader ?
                                                    RID_SCDLG_HFEDIT_RIGHTHEADER :
                                                    RID_SCDLG_HFEDIT_LEFTHEADER;
                                }
                            }
                            break;
 
                            case SvxPageUsage::Mirror:
                            case SvxPageUsage::All:
                            default:
                            {
                                if ( !bShareHeader && !bShareFooter )
                                {
                                    if ( m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_ALL;
                                    else if ( !m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_FOOTER;
                                    else if ( m_bHeaderOn && !m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_HEADER;
                                }
                                else if ( bShareHeader && bShareFooter )
                                {
                                    if ( m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT;
                                    else
                                    {
                                        if ( !m_bHeaderOn && m_bFooterOn )
                                            nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER;
                                        else if ( m_bHeaderOn && !m_bFooterOn )
                                            nResId = RID_SCDLG_HFEDIT_RIGHTHEADER;
                                    }
                                }
                                else if ( !bShareHeader &&  bShareFooter )
                                {
                                    if ( m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_SFTR;
                                    else if ( !m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER;
                                    else if ( m_bHeaderOn && !m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_HEADER;
                                }
                                else if (  bShareHeader && !bShareFooter )
                                {
                                    if ( m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_SHDR;
                                    else if ( !m_bHeaderOn && m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_FOOTER;
                                    else if ( m_bHeaderOn && !m_bFooterOn )
                                        nResId = RID_SCDLG_HFEDIT_RIGHTHEADER;
                                }
                            }
                        }
 
                        ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
 
                        VclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateScHFEditDlg(
                                                                                GetActiveDialogParent(),
                                                                                rStyleSet,
                                                                                aStr,
                                                                                nResId));
                        auto xRequest = std::make_shared<SfxRequest>(rReq);
                        rReq.Ignore(); // the 'old' request is not relevant any more
                        pDlg->StartExecuteAsync([this, pDlg, pStyleSheet, xRequest=std::move(xRequest)](sal_Int32 nResult){
                            if ( nResult == RET_OK )
                            {
                                const SfxItemSet* pOutSet = pDlg->GetOutputItemSet();
 
                                if ( pOutSet )
                                    m_pDocument->ModifyStyleSheet( *pStyleSheet, *pOutSet );
 
                                SetDocumentModified();
                                xRequest->Done();
                            }
                            pDlg->disposeOnce();
                        });
                    }
                }
            }
            break;
 
        default:
        break;
    }
}
 
void ScDocShell::GetStatePageStyle( SfxItemSet&     rSet,
                                    SCTAB           nCurTab )
{
    SfxWhichIter aIter(rSet);
    sal_uInt16 nWhich = aIter.FirstWhich();
    while ( nWhich )
    {
        switch (nWhich)
        {
            case SID_STATUS_PAGESTYLE:
                rSet.Put( SfxStringItem( nWhich, m_pDocument->GetPageStyle( nCurTab ) ) );
                break;
 
            case SID_HFEDIT:
                {
                    OUString            aStr        = m_pDocument->GetPageStyle( nCurTab );
                    ScStyleSheetPool*   pStylePool  = m_pDocument->GetStyleSheetPool();
                    SfxStyleSheetBase*  pStyleSheet = pStylePool->Find( aStr, SfxStyleFamily::Page );
 
                    OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" );
 
                    if ( pStyleSheet )
                    {
                        SfxItemSet& rStyleSet = pStyleSheet->GetItemSet();
                        GetPageOnFromPageStyleSet( &rStyleSet, nCurTab, m_bHeaderOn, m_bFooterOn );
 
                        if ( !m_bHeaderOn && !m_bFooterOn )
                            rSet.DisableItem( nWhich );
                    }
                }
                break;
        }
 
        nWhich = aIter.NextWhich();
    }
}
 
void ScDocShell::GetState( SfxItemSet &rSet )
{
    bool bTabView = GetBestViewShell() != nullptr;
 
    SfxWhichIter aIter(rSet);
    for (sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich())
    {
        if (!bTabView)
        {
            rSet.DisableItem(nWhich);
            continue;
        }
 
        switch (nWhich)
        {
            case FID_AUTO_CALC:
                if ( m_pDocument->GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
                    rSet.DisableItem( nWhich );
                else
                    rSet.Put( SfxBoolItem( nWhich, m_pDocument->GetAutoCalc() ) );
                break;
 
            case FID_CHG_RECORD:
                if ( IsDocShared() )
                    rSet.DisableItem( nWhich );
                else
                    rSet.Put( SfxBoolItem( nWhich,
                        m_pDocument->GetChangeTrack() != nullptr ) );
                break;
 
            case SID_CHG_PROTECT:
                {
                    ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack();
                    if ( pChangeTrack && !IsDocShared() )
                        rSet.Put( SfxBoolItem( nWhich,
                            pChangeTrack->IsProtected() ) );
                    else
                        rSet.DisableItem( nWhich );
                }
                break;
 
            case SID_DOCUMENT_COMPARE:
                {
                    if ( IsDocShared() )
                    {
                        rSet.DisableItem( nWhich );
                    }
                }
                break;
 
            //  When a formula is edited, FID_RECALC must be enabled in any case. Recalc for
            //  the doc was disabled once because of a bug if AutoCalc was on, but is now
            //  always enabled because of another bug.
 
            case SID_TABLES_COUNT:
                rSet.Put( SfxInt16Item( nWhich, m_pDocument->GetTableCount() ) );
                break;
 
            case SID_ATTR_YEAR2000 :
                rSet.Put( SfxUInt16Item( nWhich,
                    m_pDocument->GetDocOptions().GetYear2000() ) );
            break;
 
            case SID_SHARE_DOC:
                {
                    if ( IsReadOnly() || GetObjectShell()->isExportLocked() )
                    {
                        rSet.DisableItem( nWhich );
                    }
                }
                break;
 
            case SID_ATTR_CHAR_FONTLIST:
                rSet.Put( SvxFontListItem( m_pImpl->pFontList.get(), nWhich ) );
                break;
 
            case SID_NOTEBOOKBAR:
                {
                    if (SfxBindings* pBindings = GetViewBindings())
                    {
                        bool bVisible = sfx2::SfxNotebookBar::StateMethod(*pBindings,
                                                                          u"modules/scalc/ui/");
                        rSet.Put( SfxBoolItem( SID_NOTEBOOKBAR, bVisible ) );
                    }
                }
                break;
 
            case SID_LANGUAGE_STATUS:
                {
                    OUString sLanguage;
                    sal_uInt16 nLangWhich = 0;
                    LanguageType eLatin = LANGUAGE_DONTKNOW, eCjk = LANGUAGE_DONTKNOW,
                        eCtl = LANGUAGE_DONTKNOW;
 
                    if (comphelper::LibreOfficeKit::isActive())
                    {
                        GetDocument().GetLanguage( eLatin, eCjk, eCtl );
                        sLanguage = SvtLanguageTable::GetLanguageString(eLatin);
 
                        if (eLatin == LANGUAGE_NONE)
                            sLanguage += ";-";
                        else
                            sLanguage += ";" + LanguageTag(eLatin).getBcp47(false);
                    }
                    else if (ScTabViewShell* pViewShell = GetBestViewShell())
                    {
                        ScMarkData aMark = pViewShell->GetViewData().GetMarkData();
                        SCCOL  nCol = pViewShell->GetViewData().GetCurX();
                        SCROW  nRow = pViewShell->GetViewData().GetCurY();
                        SCTAB  nTab = pViewShell->GetViewData().GetTabNo();
 
                        aMark.SetMarkArea(ScRange(nCol, nRow, nTab));
                        const ScPatternAttr* pSelAttrs = GetDocument().GetSelectionPattern(aMark);
                        if (pSelAttrs)
                        {
                            const SfxItemSet& rItemSet = pSelAttrs->GetItemSet();
                            nLangWhich = rItemSet.GetPool()->GetWhichIDFromSlotID(SID_ATTR_CHAR_LANGUAGE);
                            if (SfxItemState::SET == rItemSet.GetItemState(nLangWhich))
                                eLatin = static_cast<const SvxLanguageItem&>(rItemSet.Get(nLangWhich)).GetLanguage();
 
                            nLangWhich = rItemSet.GetPool()->GetWhichIDFromSlotID(SID_ATTR_CHAR_CJK_LANGUAGE);
                            if (SfxItemState::SET == rItemSet.GetItemState(nLangWhich))
                                eCjk = static_cast<const SvxLanguageItem&>(rItemSet.Get(nLangWhich)).GetLanguage();
 
                            nLangWhich = rItemSet.GetPool()->GetWhichIDFromSlotID(SID_ATTR_CHAR_CTL_LANGUAGE);
                            if (SfxItemState::SET == rItemSet.GetItemState(nLangWhich))
                                eCtl = static_cast<const SvxLanguageItem&>(rItemSet.Get(nLangWhich)).GetLanguage();
 
                            if (eLatin != LANGUAGE_DONTKNOW)
                                sLanguage = SvtLanguageTable::GetLanguageString(eLatin);
                            else if (eCjk != LANGUAGE_DONTKNOW)
                                sLanguage = SvtLanguageTable::GetLanguageString(eCjk);
                            else if (eCtl != LANGUAGE_DONTKNOW)
                                sLanguage = SvtLanguageTable::GetLanguageString(eCtl);
 
                            if (sLanguage.isEmpty())
                            {
                                GetDocument().GetLanguage(eLatin, eCjk, eCtl);
                                sLanguage = SvtLanguageTable::GetLanguageString(eLatin);
                            }
                        }
                    }
                    rSet.Put(SfxStringItem(nWhich, sLanguage));
                }
                break;
 
            default:
                {
                }
                break;
        }
    }
}
 
void ScDocShell::Draw( OutputDevice* pDev, const JobSetup & /* rSetup */, sal_uInt16 nAspect, bool /*bOutputToWindow*/ )
{
 
    SCTAB nVisTab = m_pDocument->GetVisibleTab();
    if (!m_pDocument->HasTable(nVisTab))
        return;
 
    vcl::text::ComplexTextLayoutFlags nOldLayoutMode = pDev->GetLayoutMode();
    pDev->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::Default );     // even if it's the same, to get the metafile action
 
    if ( nAspect == ASPECT_THUMBNAIL )
    {
        tools::Rectangle aBoundRect = GetVisArea( ASPECT_THUMBNAIL );
        ScViewData aTmpData( *this, nullptr );
        aTmpData.SetTabNo(nVisTab);
        SnapVisArea( aBoundRect );
        aTmpData.SetScreen( aBoundRect );
        ScPrintFunc::DrawToDev( *m_pDocument, pDev, 1.0, aBoundRect, &aTmpData, true );
    }
    else
    {
        tools::Rectangle aOldArea = SfxObjectShell::GetVisArea();
        tools::Rectangle aNewArea = aOldArea;
        ScViewData aTmpData( *this, nullptr );
        aTmpData.SetTabNo(nVisTab);
        SnapVisArea( aNewArea );
        if ( aNewArea != aOldArea && (m_pDocument->GetPosLeft() > 0 || m_pDocument->GetPosTop() > 0) )
            SfxObjectShell::SetVisArea( aNewArea );
        aTmpData.SetScreen( aNewArea );
        ScPrintFunc::DrawToDev( *m_pDocument, pDev, 1.0, aNewArea, &aTmpData, true );
    }
 
    pDev->SetLayoutMode( nOldLayoutMode );
}
 
tools::Rectangle ScDocShell::GetVisArea( sal_uInt16 nAspect ) const
{
    SfxObjectCreateMode eShellMode = GetCreateMode();
    if ( eShellMode == SfxObjectCreateMode::ORGANIZER )
    {
        //  without contents we also don't know how large are the contents;
        //  return empty rectangle, it will then be calculated after the loading
        return tools::Rectangle();
    }
 
    if( nAspect == ASPECT_THUMBNAIL )
    {
        SCTAB nVisTab = m_pDocument->GetVisibleTab();
        if (!m_pDocument->HasTable(nVisTab))
        {
            nVisTab = 0;
            const_cast<ScDocShell*>(this)->m_pDocument->SetVisibleTab(nVisTab);
        }
        Size aSize = m_pDocument->GetPageSize(nVisTab);
        const tools::Long SC_PREVIEW_SIZE_X = 10000;
        const tools::Long SC_PREVIEW_SIZE_Y = 12400;
        tools::Rectangle aArea( 0,0, SC_PREVIEW_SIZE_X, SC_PREVIEW_SIZE_Y);
        if (aSize.Width() > aSize.Height())
        {
            aArea.SetRight( SC_PREVIEW_SIZE_Y );
            aArea.SetBottom( SC_PREVIEW_SIZE_X );
        }
 
        bool bNegativePage = m_pDocument->IsNegativePage( m_pDocument->GetVisibleTab() );
        if ( bNegativePage )
            ScDrawLayer::MirrorRectRTL( aArea );
        SnapVisArea( aArea );
        return aArea;
    }
    else if( nAspect == ASPECT_CONTENT && eShellMode != SfxObjectCreateMode::EMBEDDED )
    {
        //  fetch visarea like after loading
 
        SCTAB nVisTab = m_pDocument->GetVisibleTab();
        if (!m_pDocument->HasTable(nVisTab))
        {
            nVisTab = 0;
            const_cast<ScDocShell*>(this)->m_pDocument->SetVisibleTab(nVisTab);
        }
        SCCOL nStartCol;
        SCROW nStartRow;
        m_pDocument->GetDataStart( nVisTab, nStartCol, nStartRow );
        SCCOL nEndCol;
        SCROW nEndRow;
        m_pDocument->GetPrintArea( nVisTab, nEndCol, nEndRow );
        if (nStartCol>nEndCol)
            nStartCol = nEndCol;
        if (nStartRow>nEndRow)
            nStartRow = nEndRow;
        tools::Rectangle aNewArea = m_pDocument
                                ->GetMMRect( nStartCol,nStartRow, nEndCol,nEndRow, nVisTab );
        return aNewArea;
    }
    else
        return SfxObjectShell::GetVisArea( nAspect );
}
 
namespace {
 
[[nodiscard]]
tools::Long SnapHorizontal( const ScDocument& rDoc, SCTAB nTab, tools::Long nVal, SCCOL& rStartCol )
{
    SCCOL nCol = 0;
    tools::Long nTwips = o3tl::convert(nVal, o3tl::Length::mm100, o3tl::Length::twip);
    tools::Long nSnap = 0;
    while ( nCol<rDoc.MaxCol() )
    {
        tools::Long nAdd = rDoc.GetColWidth(nCol, nTab);
        if ( nSnap + nAdd/2 < nTwips || nCol < rStartCol )
        {
            nSnap += nAdd;
            ++nCol;
        }
        else
            break;
    }
    nVal = o3tl::convert(nSnap, o3tl::Length::twip, o3tl::Length::mm100);
    rStartCol = nCol;
    return nVal;
}
 
[[nodiscard]]
tools::Long SnapVertical( const ScDocument& rDoc, SCTAB nTab, tools::Long nVal, SCROW& rStartRow )
{
    SCROW nRow = 0;
    tools::Long nTwips = o3tl::convert(nVal, o3tl::Length::mm100, o3tl::Length::twip);
    tools::Long nSnap = 0;
 
    bool bFound = false;
    for (SCROW i = nRow; i <= rDoc.MaxRow(); ++i)
    {
        SCROW nLastRow;
        if (rDoc.RowHidden(i, nTab, nullptr, &nLastRow))
        {
            i = nLastRow;
            continue;
        }
 
        nRow = i;
        tools::Long nAdd = rDoc.GetRowHeight(i, nTab);
        if ( nSnap + nAdd/2 < nTwips || nRow < rStartRow )
        {
            nSnap += nAdd;
            ++nRow;
        }
        else
        {
            bFound = true;
            break;
        }
    }
    if (!bFound)
        nRow = rDoc.MaxRow();  // all hidden down to the bottom
 
    nVal = o3tl::convert(nSnap, o3tl::Length::twip, o3tl::Length::mm100);
    rStartRow = nRow;
    return nVal;
}
 
}
 
void ScDocShell::SnapVisArea( tools::Rectangle& rRect ) const
{
    SCTAB nTab = m_pDocument->GetVisibleTab();
    tools::Long nOrigTop = rRect.Top();
    tools::Long nOrigLeft = rRect.Left();
    bool bNegativePage = m_pDocument->IsNegativePage( nTab );
    if ( bNegativePage )
        ScDrawLayer::MirrorRectRTL( rRect );        // calculate with positive (LTR) values
 
    SCCOL nCol = m_pDocument->GetPosLeft();
    tools::Long nSetLeft = SnapHorizontal( *m_pDocument, nTab, rRect.Left(), nCol );
    rRect.SetLeft( nSetLeft );
    ++nCol;                                         // at least one column
    tools::Long nCorrectionLeft = (nOrigLeft == 0 && nCol > 0) ? nSetLeft : 0; // initial correction
    rRect.SetRight( SnapHorizontal( *m_pDocument, nTab, rRect.Right() + nCorrectionLeft, nCol ));
 
    SCROW nRow = m_pDocument->GetPosTop();
    tools::Long nSetTop = SnapVertical( *m_pDocument, nTab, rRect.Top(), nRow );
    rRect.SetTop( nSetTop );
    ++nRow;                                         // at least one row
    tools::Long nCorrectionTop = (nOrigTop == 0 && nRow > 0) ? nSetTop : 0; // initial correction
    rRect.SetBottom( SnapVertical( *m_pDocument, nTab, rRect.Bottom() + nCorrectionTop, nRow ));
 
    if ( bNegativePage )
        ScDrawLayer::MirrorRectRTL( rRect );        // back to real rectangle
}
 
void ScDocShell::GetPageOnFromPageStyleSet( const SfxItemSet* pStyleSet,
                                            SCTAB             nCurTab,
                                            bool&             rbHeader,
                                            bool&             rbFooter )
{
    if ( !pStyleSet )
    {
        ScStyleSheetPool*  pStylePool  = m_pDocument->GetStyleSheetPool();
        SfxStyleSheetBase* pStyleSheet = pStylePool->
                                            Find( m_pDocument->GetPageStyle( nCurTab ),
                                                  SfxStyleFamily::Page );
 
        OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" );
 
        if ( pStyleSheet )
            pStyleSet = &pStyleSheet->GetItemSet();
        else
            rbHeader = rbFooter = false;
    }
 
    OSL_ENSURE( pStyleSet, "PageStyle-Set not found! :-(" );
    if (!pStyleSet)
        return;
 
    const SvxSetItem*   pSetItem = nullptr;
    const SfxItemSet*   pSet     = nullptr;
 
    pSetItem = &pStyleSet->Get( ATTR_PAGE_HEADERSET );
    pSet     = &pSetItem->GetItemSet();
    rbHeader = pSet->Get(ATTR_PAGE_ON).GetValue();
 
    pSetItem = &pStyleSet->Get( ATTR_PAGE_FOOTERSET );
    pSet     = &pSetItem->GetItemSet();
    rbFooter = pSet->Get(ATTR_PAGE_ON).GetValue();
}
 
#if defined(_WIN32)
bool ScDocShell::DdeGetData( const OUString& rItem,
                             const OUString& rMimeType,
                             css::uno::Any & rValue )
{
    SotClipboardFormatId eFormatId = SotExchange::GetFormatIdFromMimeType( rMimeType );
    if (SotClipboardFormatId::STRING == eFormatId || SotClipboardFormatId::STRING_TSVC == eFormatId)
    {
        if( rItem.equalsIgnoreAsciiCase( "Format" ) )
        {
            OString aFmtByte(OUStringToOString(m_aDdeTextFmt,
                osl_getThreadTextEncoding()));
            rValue <<= css::uno::Sequence< sal_Int8 >(
                                        reinterpret_cast<const sal_Int8*>(aFmtByte.getStr()),
                                        aFmtByte.getLength() + 1 );
            return true;
        }
        ScImportExport aObj( *m_pDocument, rItem );
        if ( !aObj.IsRef() )
            return false;                           // invalid range
 
        if( m_aDdeTextFmt[0] == 'F' )
            aObj.SetFormulas( true );
        if( m_aDdeTextFmt == "SYLK" ||
            m_aDdeTextFmt == "FSYLK" )
        {
            OString aData;
            if( aObj.ExportByteString( aData, osl_getThreadTextEncoding(),
                                        SotClipboardFormatId::SYLK ) )
            {
                rValue <<= css::uno::Sequence< sal_Int8 >(
                                            reinterpret_cast<const sal_Int8*>(aData.getStr()),
                                            aData.getLength() + 1 );
                return true;
            }
            else
                return false;
        }
        if( m_aDdeTextFmt == "CSV" ||
            m_aDdeTextFmt == "FCSV" )
            aObj.SetSeparator( ',' );
        aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, 0, false ) );
        return aObj.ExportData( rMimeType, rValue );
    }
 
    ScImportExport aObj( *m_pDocument, rItem );
    aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, 0, false ) );
    return aObj.IsRef() && aObj.ExportData( rMimeType, rValue );
}
 
bool ScDocShell::DdeSetData( const OUString& rItem,
                             const OUString& rMimeType,
                             const css::uno::Any & rValue )
{
    SotClipboardFormatId eFormatId = SotExchange::GetFormatIdFromMimeType( rMimeType );
    if (SotClipboardFormatId::STRING == eFormatId || SotClipboardFormatId::STRING_TSVC == eFormatId)
    {
        if( rItem.equalsIgnoreAsciiCase( "Format" ) )
        {
            if ( ScByteSequenceToString::GetString( m_aDdeTextFmt, rValue ) )
            {
                m_aDdeTextFmt = m_aDdeTextFmt.toAsciiUpperCase();
                return true;
            }
            return false;
        }
        ScImportExport aObj( *m_pDocument, rItem );
        if( m_aDdeTextFmt[0] == 'F' )
            aObj.SetFormulas( true );
        if( m_aDdeTextFmt == "SYLK" ||
            m_aDdeTextFmt == "FSYLK" )
        {
            OUString aData;
            if ( ScByteSequenceToString::GetString( aData, rValue ) )
            {
                return aObj.ImportString( aData, SotClipboardFormatId::SYLK );
            }
            return false;
        }
        if( m_aDdeTextFmt == "CSV" ||
            m_aDdeTextFmt == "FCSV" )
            aObj.SetSeparator( ',' );
        OSL_ENSURE( false, "Implementation is missing" );
        return false;
    }
    /*ScImportExport aObj( aDocument, rItem );
    return aObj.IsRef() && ScImportExport::ImportData( rMimeType, rValue );*/
    OSL_ENSURE( false, "Implementation is missing" );
    return false;
}
#endif
 
::sfx2::SvLinkSource* ScDocShell::DdeCreateLinkSource( const OUString& rItem )
{
    if (officecfg::Office::Common::Security::Scripting::DisableActiveContent::get())
        return nullptr;
 
    //  only check for valid item string - range is parsed again in ScServerObject ctor
 
    //  named range?
    OUString aPos = rItem;
    ScRangeName* pRange = m_pDocument->GetRangeName();
    if( pRange )
    {
        const ScRangeData* pData = pRange->findByUpperName(ScGlobal::getCharClass().uppercase(aPos));
        if (pData)
        {
            if( pData->HasType( ScRangeData::Type::RefArea    )
                || pData->HasType( ScRangeData::Type::AbsArea )
                || pData->HasType( ScRangeData::Type::AbsPos  ) )
                aPos = pData->GetSymbol();           // continue with the name's contents
        }
    }
 
    // Address in DDE function must be always parsed as CONV_OOO so that it
    // would always work regardless of current address conversion.  We do this
    // because the address item in a DDE entry is *not* normalized when saved
    // into ODF.
    ScRange aRange;
    bool bValid = ( (aRange.Parse(aPos, *m_pDocument, formula::FormulaGrammar::CONV_OOO ) & ScRefFlags::VALID) ||
                    (aRange.aStart.Parse(aPos, *m_pDocument, formula::FormulaGrammar::CONV_OOO) & ScRefFlags::VALID) );
 
    ScServerObject* pObj = nullptr;            // NULL = error
    if ( bValid )
        pObj = new ScServerObject( this, rItem );
 
    //  GetLinkManager()->InsertServer() is in the ScServerObject ctor
 
    return pObj;
}
 
void ScDocShell::LOKCommentNotify(LOKCommentNotificationType nType, const ScDocument& rDocument, const ScAddress& rPos, const ScPostIt* pNote)
{
    if ( !rDocument.IsDocVisible() || // don't want callbacks until document load
         !comphelper::LibreOfficeKit::isActive() ||
         comphelper::LibreOfficeKit::isTiledAnnotations() )
        return;
 
    tools::JsonWriter aAnnotation;
    {
        auto commentNode = aAnnotation.startNode("comment");
        aAnnotation.put("action", (nType == LOKCommentNotificationType::Add ? "Add" :
                                   (nType == LOKCommentNotificationType::Remove ? "Remove" :
                                    (nType == LOKCommentNotificationType::Modify ? "Modify" : "???"))));
 
        assert(pNote);
        aAnnotation.put("id", pNote->GetId());
        aAnnotation.put("tab", rPos.Tab());
 
        if (nType != LOKCommentNotificationType::Remove)
        {
            aAnnotation.put("author", pNote->GetAuthor());
            aAnnotation.put("dateTime", pNote->GetDate());
            aAnnotation.put("text", pNote->GetText());
 
            // Calculating the cell cursor position
            ScViewData* pViewData = GetViewData();
            if (pViewData && pViewData->GetActiveWin())
                aAnnotation.put("cellRange", ScPostIt::NoteRangeToJsonString(rDocument, rPos));
        }
    }
 
    OString aPayload = aAnnotation.finishAndGetAsOString();
 
    ScViewData* pViewData = GetViewData();
    SfxViewShell* pThisViewShell = ( pViewData ? pViewData->GetViewShell() : nullptr );
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pThisViewShell == nullptr || pViewShell->GetDocId() == pThisViewShell->GetDocId())
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_COMMENT, aPayload);
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
ScViewData* ScDocShell::GetViewData()
{
    SfxViewShell* pCur = SfxViewShell::Current();
    ScTabViewShell* pViewSh = dynamic_cast< ScTabViewShell *>( pCur );
    return pViewSh ? &pViewSh->GetViewData() : nullptr;
}
 
SCTAB ScDocShell::GetCurTab()
{
    //! this must be made non-static and use a ViewShell from this document!
 
    ScViewData* pViewData = GetViewData();
 
    return pViewData ? pViewData->GetTabNo() : static_cast<SCTAB>(0);
}
 
ScTabViewShell* ScDocShell::GetBestViewShell( bool bOnlyVisible )
{
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    // wrong Doc?
    if( pViewSh && pViewSh->GetViewData().GetDocShell() != this )
        pViewSh = nullptr;
    if( !pViewSh )
    {
        // 1. find ViewShell
        SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this, bOnlyVisible );
        if( pFrame )
        {
            SfxViewShell* p = pFrame->GetViewShell();
            pViewSh = dynamic_cast< ScTabViewShell *>( p );
        }
    }
    return pViewSh;
}
 
SfxBindings* ScDocShell::GetViewBindings()
{
    //  used to invalidate slots after changes to this document
 
    SfxViewShell* pViewSh = GetBestViewShell();
    if (pViewSh)
        return &pViewSh->GetViewFrame().GetBindings();
    else
        return nullptr;
}
 
ScDocShell* ScDocShell::GetShellByNum( sal_uInt16 nDocNo )      // static
{
    ScDocShell* pFound = nullptr;
    SfxObjectShell* pShell = SfxObjectShell::GetFirst();
    sal_uInt16 nShellCnt = 0;
 
    while ( pShell && !pFound )
    {
        if ( auto pDocSh = dynamic_cast<ScDocShell*>(pShell) )
        {
            if ( nShellCnt == nDocNo )
                pFound = pDocSh;
            else
                ++nShellCnt;
        }
        pShell = SfxObjectShell::GetNext( *pShell );
    }
 
    return pFound;
}
 
IMPL_LINK( ScDocShell, DialogClosedHdl, sfx2::FileDialogHelper*, _pFileDlg, void )
{
    assert( _pFileDlg && "ScDocShell::DialogClosedHdl(): no file dialog");
    OSL_ENSURE( m_pImpl->pDocInserter, "ScDocShell::DialogClosedHdl(): no document inserter" );
 
    if ( ERRCODE_NONE == _pFileDlg->GetError() )
    {
        sal_uInt16 nSlot = m_pImpl->pRequest->GetSlot();
        std::unique_ptr<SfxMedium> pMed = m_pImpl->pDocInserter->CreateMedium();
        // #i87094# If a .odt was selected pMed is NULL.
        if (pMed)
        {
            m_pImpl->pRequest->AppendItem( SfxStringItem( SID_FILE_NAME, pMed->GetName() ) );
            if ( SID_DOCUMENT_COMPARE == nSlot )
            {
                if ( pMed->GetFilter() )
                    m_pImpl->pRequest->AppendItem(
                            SfxStringItem( SID_FILTER_NAME, pMed->GetFilter()->GetFilterName() ) );
                OUString sOptions = ScDocumentLoader::GetOptions( *pMed );
                if ( !sOptions.isEmpty() )
                    m_pImpl->pRequest->AppendItem( SfxStringItem( SID_FILE_FILTEROPTIONS, sOptions ) );
            }
            const SfxPoolItem* pItem = nullptr;
            const SfxInt16Item* pInt16Item(nullptr);
            if (pMed->GetItemSet().GetItemState(SID_VERSION, true, &pItem) == SfxItemState::SET)
            {
                pInt16Item = dynamic_cast<const SfxInt16Item*>(pItem);
            }
            if (pInt16Item)
            {
                m_pImpl->pRequest->AppendItem( *pItem );
            }
 
            Execute( *(m_pImpl->pRequest) );
        }
    }
 
    m_pImpl->bIgnoreLostRedliningWarning = false;
}
 
#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
 
void ScDocShell::EnableSharedSettings( bool bEnable )
{
    SetDocumentModified();
 
    if ( bEnable )
    {
        m_pDocument->EndChangeTracking();
        m_pDocument->StartChangeTracking();
 
        // hide accept or reject changes dialog
        sal_uInt16 nId = ScAcceptChgDlgWrapper::GetChildWindowId();
        SfxViewFrame* pViewFrame = SfxViewFrame::Current();
        if ( pViewFrame && pViewFrame->HasChildWindow( nId ) )
        {
            pViewFrame->ToggleChildWindow( nId );
            SfxBindings* pBindings = GetViewBindings();
            if ( pBindings )
            {
                pBindings->Invalidate( FID_CHG_ACCEPT );
            }
        }
    }
    else
    {
        m_pDocument->EndChangeTracking();
    }
 
    ScChangeViewSettings aChangeViewSet;
    aChangeViewSet.SetShowChanges( false );
    m_pDocument->SetChangeViewSettings( aChangeViewSet );
}
 
uno::Reference< frame::XModel > ScDocShell::LoadSharedDocument()
{
    uno::Reference< frame::XModel > xModel;
    try
    {
        SC_MOD()->SetInSharedDocLoading( true );
        uno::Reference< frame::XDesktop2 > xLoader = frame::Desktop::create( ::comphelper::getProcessComponentContext() );
        uno::Sequence aArgs{ comphelper::makePropertyValue(u"Hidden"_ustr, true) };
 
        if ( GetMedium() )
        {
            const SfxStringItem* pPasswordItem = GetMedium()->GetItemSet().GetItem(SID_PASSWORD, false);
            if ( pPasswordItem && !pPasswordItem->GetValue().isEmpty() )
            {
                aArgs.realloc( 2 );
                auto pArgs = aArgs.getArray();
                pArgs[1].Name = "Password";
                pArgs[1].Value <<= pPasswordItem->GetValue();
            }
            const SfxUnoAnyItem* pEncryptionItem = GetMedium()->GetItemSet().GetItem(SID_ENCRYPTIONDATA, false);
            if (pEncryptionItem)
            {
                aArgs.realloc(aArgs.getLength() + 1);
                auto pArgs = aArgs.getArray();
                pArgs[aArgs.getLength() - 1].Name = "EncryptionData";
                pArgs[aArgs.getLength() - 1].Value = pEncryptionItem->GetValue();
            }
        }
 
        xModel.set(
            xLoader->loadComponentFromURL( GetSharedFileURL(), u"_blank"_ustr, 0, aArgs ),
            uno::UNO_QUERY_THROW );
        SC_MOD()->SetInSharedDocLoading( false );
    }
    catch ( uno::Exception& )
    {
        OSL_FAIL( "ScDocShell::LoadSharedDocument(): caught exception" );
        SC_MOD()->SetInSharedDocLoading( false );
        try
        {
            uno::Reference< util::XCloseable > xClose( xModel, uno::UNO_QUERY_THROW );
            xClose->close( true );
            return uno::Reference< frame::XModel >();
        }
        catch ( uno::Exception& )
        {
            return uno::Reference< frame::XModel >();
        }
    }
    return xModel;
}
 
#endif
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

V547 Expression 'bParagraph' is always true.

V547 Expression 'bParagraph' is always true.

V547 Expression 'GetBestViewShell()' is always true.

V788 The variable 'bAddRange', captured in a lambda expression, has a constant value.