/* -*- 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 = ScModule::get()->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 (ScModule::get()->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 );
 
                                ScModule* mod = ScModule::get();
                                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
                                }
 
                                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" );
                                ScModule::get()->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.start.nIndex = 0;
                        aSel.end.nIndex = EE_TEXTPOS_MAX;
                        pEditView->SetSelection( aSel );
                    }
 
                    pEditView->SetAttribs( aAttrs );
                    if (bParagraph)
                        pEditView->SetSelection( aOldSel );
                }
                else if ( eLang != eLatin )
                {
                    if ( ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell() )
                    {
                        ScInputHandler* pInputHandler = ScModule::get()->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 ? ScModule::get()->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;
    ScModule* mod = ScModule::get();
    try
    {
        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 );
        mod->SetInSharedDocLoading(false);
    }
    catch ( uno::Exception& )
    {
        OSL_FAIL( "ScDocShell::LoadSharedDocument(): caught exception" );
        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.