/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the Collabora Office 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 <sal/config.h>

#include <formdata.hxx>

#include <sfx2/app.hxx>
#include <svx/dialogs.hrc>
#include <svx/extrusionbar.hxx>
#include <svx/fontworkbar.hxx>
#include <editeng/borderline.hxx>
#include <svx/fmshell.hxx>
#include <svx/sidebar/ContextChangeEventMultiplexer.hxx>
#include <sfx2/printer.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/ipclient.hxx>
#include <tools/urlobj.hxx>
#include <sfx2/docfile.hxx>
#include <tools/svborder.hxx>

#include <IAnyRefDialog.hxx>
#include <tabvwsh.hxx>
#include <sc.hrc>
#include <globstr.hrc>
#include <docsh.hxx>
#include <scmod.hxx>
#include <appoptio.hxx>
#include <drawsh.hxx>
#include <drformsh.hxx>
#include <editsh.hxx>
#include <pivotsh.hxx>
#include <SparklineShell.hxx>
#include <tableshell.hxx>
#include <auditsh.hxx>
#include <drtxtob.hxx>
#include <inputhdl.hxx>
#include <editutil.hxx>
#include <inputopt.hxx>
#include <inputwin.hxx>
#include <dbdata.hxx>
#include <reffact.hxx>
#include <viewuno.hxx>
#include <dispuno.hxx>
#include <chgtrack.hxx>
#include <cellsh.hxx>
#include <oleobjsh.hxx>
#include <chartsh.hxx>
#include <graphsh.hxx>
#include <mediash.hxx>
#include <pgbrksh.hxx>
#include <dpobject.hxx>
#include <prevwsh.hxx>
#include <scextopt.hxx>
#include <drawview.hxx>
#include <fupoor.hxx>
#include <navsett.hxx>
#include <scabstdlg.hxx>
#include <externalrefmgr.hxx>
#include <defaultsoptions.hxx>
#include <markdata.hxx>
#include <preview.hxx>
#include <gridwin.hxx>
#include <tablestyle.hxx>
#include <cellsuno.hxx>

#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/configuration/theDefaultProvider.hpp>
#include <com/sun/star/sheet/XCellRangeMovement.hpp>
#include <com/sun/star/sheet/XCellRangeData.hpp>
#include <com/sun/star/sheet/XCellRangeAddressable.hpp>
#include <comphelper/processfactory.hxx>
#include <sfx2/kit/helper.hxx>
#include <comphelper/flagguard.hxx>
#include <COKit/COKit.hxx>
#include <comphelper/kit.hxx>
#include <sfx2/sidebar/SidebarController.hxx>
#include <sfx2/dispatch.hxx>
#include <svl/srchitem.hxx>
#include <svx/srchdlg.hxx>
#include <docpool.hxx>
#include <drwlayer.hxx>

using namespace com::sun::star;
using namespace sfx2::sidebar;

namespace {

bool inChartOrMathContext(const ScTabViewShell* pViewShell)
{
    SidebarController* pSidebar = SidebarController::GetSidebarControllerForView(pViewShell);
    if (pSidebar)
        return pSidebar->hasChartOrMathContextCurrently();

    return false;
}

} // anonymous namespace

void ScTabViewShell::Activate(bool bMDI)
{
    SfxViewShell::Activate(bMDI);
    bIsActive = true;
    // here no GrabFocus, otherwise there will be problems when something is edited inplace!

    if ( bMDI )
    {
        // for input row (ClearCache)
        ScModule* pScMod = ScModule::get();
        pScMod->ViewShellChanged(/*bStopEditing=*/ !comphelper::COKit::isActive());

        ActivateView( true, bFirstActivate );

        // update AutoCorrect, if Writer has newly created this
        UpdateDrawTextOutliner();

        // RegisterNewTargetNames does not exist anymore

        SfxViewFrame& rThisFrame  = GetViewFrame();
        if ( mpInputHandler && rThisFrame.HasChildWindow(FID_INPUTLINE_STATUS) )
        {
            // actually only required for Reload (last version):
            // The InputWindow remains, but the View along with the InputHandler is newly created,
            // that is why the InputHandler must be set at the InputWindow.
            SfxChildWindow* pChild = rThisFrame.GetChildWindow(FID_INPUTLINE_STATUS);
            if (pChild)
            {
                ScInputWindow* pWin = static_cast<ScInputWindow*>(pChild->GetWindow());
                if (pWin && pWin->IsVisible())
                {
                    pWin->NumLinesChanged(); // tdf#150664
                    ScInputHandler* pOldHdl=pWin->GetInputHandler();

                    SfxViewShell* pSh = SfxViewShell::GetFirst( true, checkSfxViewShell<ScTabViewShell> );
                    while ( pSh!=nullptr && pOldHdl!=nullptr)
                    {
                        // Hmm, what if pSh is a shell for a different document? But as this code
                        // does not seem to be COKit-specific, probably that doesn't
                        // happen, because having multiple documents open simultaneously has of
                        // course not been a problem at all in traditional desktop LibreOffice.
                        // (Unlike in a COKit-based process where it has been a problem.)
                        if (static_cast<ScTabViewShell*>(pSh)->GetInputHandler() == pOldHdl)
                        {
                            pOldHdl->ResetDelayTimer();
                            break;
                        }
                        pSh = SfxViewShell::GetNext( *pSh, true, checkSfxViewShell<ScTabViewShell> );
                    }

                    pWin->SetInputHandler( mpInputHandler.get() );
                }
            }
        }

        bool isKit = comphelper::COKit::isActive();
        UpdateInputHandler( /*bForce=*/ !isKit, /*bStopEditing=*/ !isKit );

        if ( bFirstActivate )
        {
            SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScNavigatorUpdateAll ) );
            bFirstActivate = false;

            // ReadExtOptions (view settings from Excel import) must also be done
            // after the ctor, because of the potential calls to Window::Show.
            // Even after a bugfix (Window::Show no longer notifies the access
            // bridge, it's done in ImplSetReallyVisible), there are problems if Window::Show
            // is called during the ViewShell ctor and reschedules asynchronous calls
            // (for example from the FmFormShell ctor).
            ScExtDocOptions* pExtOpt = GetViewData().GetDocument().GetExtDocOptions();
            if ( pExtOpt && pExtOpt->IsChanged() )
            {
                GetViewData().ReadExtOptions(*pExtOpt);        // Excel view settings
                SetTabNo( GetViewData().CurrentTabForData(), true );
                pExtOpt->SetChanged( false );
            }
        }

        pScActiveViewShell = this;

        ScInputHandler* pHdl = pScMod->GetInputHdl(this);
        if (pHdl)
        {
            pHdl->SetRefScale( GetViewData().GetZoomX(), GetViewData().GetZoomY() );
        }

        // update change dialog

        if ( rThisFrame.HasChildWindow(FID_CHG_ACCEPT) )
        {
            SfxChildWindow* pChild = rThisFrame.GetChildWindow(FID_CHG_ACCEPT);
            if (pChild)
            {
                static_cast<ScAcceptChgDlgWrapper*>(pChild)->ReInitDlg();
            }
        }

        if(pScMod->IsRefDialogOpen())
        {
            sal_uInt16 nModRefDlgId=pScMod->GetCurRefDlgId();
            SfxChildWindow* pChildWnd = rThisFrame.GetChildWindow( nModRefDlgId );
            if ( pChildWnd )
            {
                if (auto pController = pChildWnd->GetController())
                {
                    IAnyRefDialog* pRefDlg = dynamic_cast<IAnyRefDialog*>(pController.get());
                    if (pRefDlg)
                        pRefDlg->ViewShellChanged();
                }
            }
        }
    }

    //  don't call CheckSelectionTransfer here - activating a view should not change the
    //  primary selection (may be happening just because the mouse was moved over the window)

    if (!inChartOrMathContext(this))
    {
        ContextChangeEventMultiplexer::NotifyContextChange(
            GetController(),
            vcl::EnumContext::Context::Default);
    }
}

void ScTabViewShell::Deactivate(bool bMDI)
{
    HideTip();

    ScDocument& rDoc = GetViewData().GetDocument();

    ScChangeTrack* pChanges = rDoc.GetChangeTrack();

    if(pChanges!=nullptr)
    {
        Link<ScChangeTrack&,void> aLink;
        pChanges->SetModifiedLink(aLink);
    }

    SfxViewShell::Deactivate(bMDI);
    bIsActive = false;
    ScInputHandler* pHdl = ScModule::get()->GetInputHdl(this);

    if( bMDI && !comphelper::COKit::isActive())
    {
        //  during shell deactivation, shells must not be switched, or the loop
        //  through the shell stack (in SfxDispatcher::DoDeactivate_Impl) will not work
        bool bOldDontSwitch = bDontSwitch;
        bDontSwitch = true;

        ActivateView( false, false );

        if ( GetViewFrame().GetFrame().IsInPlace() ) // inplace
            GetViewData().GetDocShell()->UpdateOle(GetViewData(), true);

        if ( pHdl )
            pHdl->NotifyChange( nullptr, true ); // timer-delayed due to document switching

        if (pScActiveViewShell == this)
            pScActiveViewShell = nullptr;

        bDontSwitch = bOldDontSwitch;
    }
    else
    {
        HideNoteOverlay();           // note marker

        // in COKit case this could be triggered on every action from other view (doc_setView)
        // we don't want to hide tooltip only because other view did some action
        if ( pHdl && !comphelper::COKit::isActive() )
            pHdl->HideTip();        // Hide formula auto input tip
    }
}

void ScTabViewShell::SetActive()
{
    // SFX-View would like to activate itself, since then magical things would happen
    // (eg else the designer may crash)
    ActiveGrabFocus();
}

bool ScTabViewShell::PrepareClose(bool bUI)
{
    comphelper::FlagRestorationGuard aFlagGuard(bInPrepareClose, true);

    // Call EnterHandler even in formula mode here,
    // so a formula change in an embedded object isn't lost
    // (ScDocShell::PrepareClose isn't called then).
    ScInputHandler* pHdl = ScModule::get()->GetInputHdl(this);
    if ( pHdl && pHdl->IsInputMode() )
    {
        pHdl->EnterHandler();
    }

    // draw text edit mode must be closed
    FuPoor* pPoor = GetDrawFuncPtr();
    if (pPoor && IsDrawTextShell())
    {
        // "clean" end of text edit, including note handling, subshells and draw func switching,
        // as in FuDraw and ScTabView::DrawDeselectAll
        GetViewData().GetDispatcher().Execute( pPoor->GetSlotID(), SfxCallMode::SLOT | SfxCallMode::RECORD );
    }
    ScDrawView* pDrView = GetScDrawView();
    if ( pDrView )
    {
        // force end of text edit, to be safe
        // ScEndTextEdit must always be used, to ensure correct UndoManager
        pDrView->ScEndTextEdit();
    }

    if ( pFormShell )
    {
        bool bRet = pFormShell->PrepareClose(bUI);
        if (!bRet)
            return bRet;
    }
    return SfxViewShell::PrepareClose(bUI);
}

// calculate zoom for in-place
// from the ratio of VisArea and window size of GridWin

void ScTabViewShell::UpdateOleZoom()
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    if ( pDocSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
    {
        //TODO/LATER: is there a difference between the two GetVisArea methods?
        Size aObjSize = static_cast<const SfxObjectShell*>(pDocSh)->GetVisArea().GetSize();
        if ( !aObjSize.IsEmpty() )
        {
            vcl::Window* pWin = GetActiveWin();
            Size aWinHMM = pWin->PixelToLogic(pWin->GetOutputSizePixel(), MapMode(MapUnit::Map100thMM));
            SetZoomFactor( double(aWinHMM.Width()) / aObjSize.Width(),
                           double(aWinHMM.Height()) / aObjSize.Height() );
        }
    }
}

IMPL_LINK(ScTabViewShell, SearchDialogHdl, SfxRequest&, rReq, void)
{
    // Get the parameter from the request
    const SfxItemSet* pArgs = rReq.GetArgs();
    bool bInitialFocusOnReplace = false;

    if (pArgs)
    {
        const SfxBoolItem* pBoolItem = pArgs->GetItemIfSet(SID_SEARCH_DLG, false);
        if (pBoolItem)
            bInitialFocusOnReplace = pBoolItem->GetValue();
    }

    // Get existing search item to preserve settings
    std::unique_ptr<SvxSearchItem> pSearchItem;
    std::unique_ptr<SvxSearchItem> pExistingItem;
    if (GetViewData().GetBindings().QueryState(SID_SEARCH_ITEM, pExistingItem) >= SfxItemState::DEFAULT && pExistingItem)
    {
        pSearchItem.reset(pExistingItem->Clone());
    }
    else
    {
        pSearchItem = std::make_unique<SvxSearchItem>(SID_SEARCH_ITEM);
    }

    // Only set the focus parameter, preserving all other settings
    pSearchItem->SetInitialFocusOnReplace(bInitialFocusOnReplace);

    // Execute the search dialog with the configured item
    const SfxPoolItem* ppArgs[] = { pSearchItem.get(), nullptr };
    GetViewData().GetDispatcher().Execute(SID_SEARCH_ITEM, SfxCallMode::SYNCHRON, ppArgs);
    GetViewFrame().ToggleChildWindow(SvxSearchDialogWrapper::GetChildWindowId());
}

void ScTabViewShell::InnerResizePixel( const Point &rOfs, const Size &rSize, bool inplaceEditModeChange )
{
    Size aNewSize( rSize );
    if ( GetViewFrame().GetFrame().IsInPlace() )
    {
        SvBorder aBorder;
        GetBorderSize( aBorder, rSize );
        SetBorderPixel( aBorder );

        Size aObjSize = GetObjectShell()->GetVisArea().GetSize();

        Size aSize( rSize );
        aSize.AdjustWidth( -(aBorder.Left() + aBorder.Right()) );
        aSize.AdjustHeight( -(aBorder.Top() + aBorder.Bottom()) );

        if ( !aObjSize.IsEmpty() )
        {
            Size aLogicSize = GetWindow()->PixelToLogic(aSize, MapMode(MapUnit::Map100thMM));
            SfxViewShell::SetZoomFactor( double(aLogicSize.Width()) / aObjSize.Width(),
                                         double(aLogicSize.Height()) / aObjSize.Height() );
        }

        Point aPos( rOfs );
        aPos.AdjustX(aBorder.Left() );
        aPos.AdjustY(aBorder.Top() );
        GetWindow()->SetPosSizePixel( aPos, aSize );
    }
    else
    {
        SvBorder aBorder;
        GetBorderSize( aBorder, rSize );
        SetBorderPixel( aBorder );
        aNewSize.AdjustWidth(aBorder.Left() + aBorder.Right() );
        aNewSize.AdjustHeight(aBorder.Top() + aBorder.Bottom() );
    }

    DoResize( rOfs, aNewSize, true );                   // rSize = size of gridwin

    UpdateOleZoom();                                    // calculate zoom for in-place

    if (!inplaceEditModeChange)
    {
        GetViewData().GetDocShell()->SetDocumentModified();
    }
}

void ScTabViewShell::OuterResizePixel( const Point &rOfs, const Size &rSize )
{
    SvBorder aBorder;
    GetBorderSize( aBorder, rSize );
    SetBorderPixel( aBorder );

    DoResize( rOfs, rSize );                    // position and size of tabview as passed

    // ForceMove as replacement for Sfx-Move mechanism
    // (aWinPos must be kept current, so that ForceMove works for Ole deactivation)

    ForceMove();
}

void ScTabViewShell::SetZoomFactor( double fZoomX, double fZoomY )
{
    // for OLE...

    double fFrac20( 1.0 / 5 );
    double fFrac400( 4.0 );

    double fNewX( fZoomX );
    if ( fNewX < fFrac20 )
        fNewX = fFrac20;
    if ( fNewX > fFrac400 )
        fNewX = fFrac400;
    double fNewY( fZoomY );
    if ( fNewY < fFrac20 )
        fNewY = fFrac20;
    if ( fNewY > fFrac400 )
        fNewY = fFrac400;

    GetViewData().UpdateScreenZoom( fNewX, fNewY );
    SetZoom( fNewX, fNewY, true );

    PaintGrid();
    PaintTop();
    PaintLeft();

    SfxViewShell::SetZoomFactor( fZoomX, fZoomY );
}

void ScTabViewShell::QueryObjAreaPixel( tools::Rectangle& rRect ) const
{
    // adjust to entire cells (in 1/100 mm)

    Size aPixelSize = rRect.GetSize();
    vcl::Window* pWin = const_cast<ScTabViewShell*>(this)->GetActiveWin();
    Size aLogicSize = pWin->PixelToLogic( aPixelSize );

    const ScViewData& rViewData = GetViewData();
    ScDocument& rDoc = rViewData.GetDocument();
    ScSplitPos ePos = rViewData.GetActivePart();
    SCCOL nCol = rViewData.GetPosX(WhichH(ePos));
    SCROW nRow = rViewData.GetPosY(WhichV(ePos));
    SCTAB nTab = rViewData.CurrentTabForData();
    bool bNegativePage = rDoc.IsNegativePage( nTab );

    tools::Rectangle aLogicRect = rDoc.GetMMRect( nCol, nRow, nCol, nRow, nTab );
    if ( bNegativePage )
    {
        // use right edge of aLogicRect, and aLogicSize
        aLogicRect.SetLeft( aLogicRect.Right() - aLogicSize.Width() + 1 );    // Right() is set below
    }
    aLogicRect.SetSize( aLogicSize );

    rViewData.GetDocShell()->SnapVisArea( aLogicRect );

    rRect.SetSize( pWin->LogicToPixel( aLogicRect.GetSize() ) );
}

void ScTabViewShell::Move()
{
    Point aNewPos = GetViewFrame().GetWindow().OutputToScreenPixel(Point());

    if (aNewPos != aWinPos)
    {
        StopMarking();
        aWinPos = aNewPos;
    }
}

void ScTabViewShell::ShowCursor(bool /* bOn */)
{
/*!!!   ShowCursor is not called as a pair as in gridwin.
        here the CursorLockCount for Gridwin must be set directly to 0

    if (bOn)
        ShowAllCursors();
    else
        HideAllCursors();
*/
}

void ScTabViewShell::WriteUserData(OUString& rData, bool /* bBrowse */)
{
    GetViewData().WriteUserData(rData);
}

void ScTabViewShell::WriteUserDataSequence (cpo::uno::Sequence < beans::PropertyValue >& rSettings )
{
    GetViewData().WriteUserDataSequence(rSettings);
}

void ScTabViewShell::ReadUserData(const OUString& rData, bool /* bBrowse */)
{
    if ( !GetViewData().GetDocShell()->IsPreview() )
        DoReadUserData( rData );
}

void ScTabViewShell::ReadUserDataSequence (const cpo::uno::Sequence < beans::PropertyValue >& rSettings )
{
    if ( !GetViewData().GetDocShell()->IsPreview() )
        DoReadUserDataSequence( rSettings );
}

void ScTabViewShell::DoReadUserDataSequence( const cpo::uno::Sequence < beans::PropertyValue >& rSettings )
{
    vcl::Window* pOldWin = GetActiveWin();
    bool bFocus = pOldWin && pOldWin->HasFocus();

    GetViewData().ReadUserDataSequence(rSettings);
    SetTabNo( GetViewData().CurrentTabForData(), true );

    if ( GetViewData().IsPagebreakMode() )
        SetCurSubShell( GetCurObjectSelectionType(), true );

    vcl::Window* pNewWin = GetActiveWin();
    if (pNewWin && pNewWin != pOldWin)
    {
        SetWindow( pNewWin );       //! is this ViewShell always active???
        if (bFocus)
            pNewWin->GrabFocus();
        WindowChanged();            // drawing layer (for instance #56771#)
    }

    if (GetViewData().GetHSplitMode() == SC_SPLIT_FIX ||
        GetViewData().GetVSplitMode() == SC_SPLIT_FIX)
    {
        InvalidateSplit();
    }

    ZoomChanged();

    TestHintWindow();

    //! if ViewData has more tables than document, remove tables in ViewData
}

// DoReadUserData is also called from ctor when switching from print preview

void ScTabViewShell::DoReadUserData( std::u16string_view rData )
{
    vcl::Window* pOldWin = GetActiveWin();
    bool bFocus = pOldWin && pOldWin->HasFocus();

    GetViewData().ReadUserData(rData);
    SetTabNo( GetViewData().CurrentTabForData(), true );

    if ( GetViewData().IsPagebreakMode() )
        SetCurSubShell( GetCurObjectSelectionType(), true );

    vcl::Window* pNewWin = GetActiveWin();
    if (pNewWin && pNewWin != pOldWin)
    {
        SetWindow( pNewWin );       //! is this ViewShell always active???
        if (bFocus)
            pNewWin->GrabFocus();
        WindowChanged();            // drawing layer (for instance #56771#)
    }

    if (GetViewData().GetHSplitMode() == SC_SPLIT_FIX ||
        GetViewData().GetVSplitMode() == SC_SPLIT_FIX)
    {
        InvalidateSplit();
    }

    ZoomChanged();

    TestHintWindow();

    //! if ViewData has more tables than document, remove tables in ViewData
}

void ScTabViewShell::UpdateDrawShell()
{
    // Called after user interaction that may delete the selected drawing object.
    // Remove DrawShell if nothing is selected.

    SdrView* pDrView = GetScDrawView();
    if ( pDrView && pDrView->GetMarkedObjectList().GetMarkCount() == 0 && !IsDrawSelMode() )
        SetDrawShell( false );
}

void ScTabViewShell::SetDrawShellOrSub()
{
    bActiveDrawSh = true;

    if(bActiveDrawFormSh)
    {
        SetCurSubShell(OST_DrawForm);
    }
    else if(bActiveGraphicSh)
    {
        SetCurSubShell(OST_Graphic);
    }
    else if(bActiveMediaSh)
    {
        SetCurSubShell(OST_Media);
    }
    else if(bActiveChartSh)
    {
        SetCurSubShell(OST_Chart);
    }
    else if(bActiveOleObjectSh)
    {
        SetCurSubShell(OST_OleObject);
    }
    else
    {
        SetCurSubShell(OST_Drawing, true /* force: different toolbars are
                                            visible concerning shape type
                                            and shape state */);
    }
}

void ScTabViewShell::SetDrawShell( bool bActive )
{
    if(bActive)
    {
        SetCurSubShell(OST_Drawing, true /* force: different toolbars are
                                            visible concerning shape type
                                            and shape state */);
    }
    else
    {
        if(bActiveDrawFormSh || bActiveDrawSh ||
            bActiveGraphicSh || bActiveMediaSh || bActiveOleObjectSh||
            bActiveChartSh || bActiveDrawTextSh)
        {
            SetCurSubShell(OST_Cell);
        }
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
    }

    bool bWasDraw = bActiveDrawSh || bActiveDrawTextSh;

    bActiveDrawSh = bActive;
    bActiveDrawTextSh = false;

    if ( !bActive )
    {
        ResetDrawDragMode();        // switch off Mirror / Rotate

        if (bWasDraw && (GetViewData().GetHSplitMode() == SC_SPLIT_FIX ||
                         GetViewData().GetVSplitMode() == SC_SPLIT_FIX))
        {
            // adjust active part to cursor, etc.
            MoveCursorAbs( GetViewData().GetCurX(), GetViewData().GetCurY(),
                            SC_FOLLOW_NONE, false, false, true );
        }
    }
}

void ScTabViewShell::SetDrawTextShell( bool bActive )
{
    bActiveDrawTextSh = bActive;
    if ( bActive )
    {
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
        bActiveDrawSh = false;
        SetCurSubShell(OST_DrawText);
    }
    else
        SetCurSubShell(OST_Cell);

}

void ScTabViewShell::SetPivotShell( bool bActive )
{
    //  SetPivotShell is called from CursorPosChanged every time
    //  -> don't change anything except switching between cell and pivot shell

    if (eCurOST != OST_Pivot && eCurOST != OST_Cell && eCurOST != OST_Sparkline && eCurOST != OST_Table
        && eCurOST != OST_Editing)
        return;

    if ( bActive )
    {
        bActiveDrawTextSh = bActiveDrawSh = false;
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
        SetCurSubShell(OST_Pivot);
    }
    else
        SetCurSubShell(OST_Cell);
}

void ScTabViewShell::SetSparklineShell(bool bActive)
{
    if (eCurOST != OST_Sparkline && eCurOST != OST_Cell && eCurOST != OST_Pivot && eCurOST != OST_Table
        && eCurOST != OST_Editing)
        return;

    if (bActive)
    {
        bActiveDrawTextSh = bActiveDrawSh = false;
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
        SetCurSubShell(OST_Sparkline);
    }
    else
        SetCurSubShell(OST_Cell);
}

void ScTabViewShell::SetTableShell(bool bActive)
{
    if (eCurOST != OST_Table && eCurOST != OST_Cell && eCurOST != OST_Pivot && eCurOST != OST_Sparkline
        && eCurOST != OST_Editing)
        return;

    if (bActive)
    {
        bActiveDrawTextSh = bActiveDrawSh = false;
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
        SetCurSubShell(OST_Table);
    }
    else
        SetCurSubShell(OST_Cell);
}

void ScTabViewShell::UpdateContextShells()
{
    ScDocument& rDoc = GetViewData().GetDocument();
    const ScAddress rAddr = GetViewData().GetCurPos();
    if (rDoc.HasDataPilotAtPosition(rAddr))
    {
        SetPivotShell(true);
        // TODO: multiple context subshells at the same time (pivot and sparkline)
        if (rDoc.HasSparkline(rAddr))
            SetSparklineShell(true);
    }
    else if (rDoc.GetTableDBAtCursor(rAddr.Col(), rAddr.Row(), rAddr.Tab(),
                                     ScDBDataPortion::AREA))
    {
        SetTableShell(true);
        // TODO: multiple context subshells at the same time (table and sparkline)
        if (rDoc.HasSparkline(rAddr))
            SetSparklineShell(true);
    }
    else if (rDoc.HasSparkline(rAddr))
    {
        SetSparklineShell(true);
    }
    else
    {
        // Fallback to SetCurSubShell(OST_Cell);
        SetSparklineShell(false);
    }
}

void ScTabViewShell::SetAuditShell( bool bActive )
{
    if ( bActive )
    {
        bActiveDrawTextSh = bActiveDrawSh = false;
        bActiveDrawFormSh=false;
        bActiveGraphicSh=false;
        bActiveMediaSh=false;
        bActiveOleObjectSh=false;
        bActiveChartSh=false;
        SetCurSubShell(OST_Auditing);
    }
    else
        SetCurSubShell(OST_Cell);
}

void ScTabViewShell::SetDrawFormShell( bool bActive )
{
    bActiveDrawFormSh = bActive;

    if(bActiveDrawFormSh)
        SetCurSubShell(OST_DrawForm);
}
void ScTabViewShell::SetChartShell( bool bActive )
{
    bActiveChartSh = bActive;

    if(bActiveChartSh)
        SetCurSubShell(OST_Chart);
}

void ScTabViewShell::SetGraphicShell( bool bActive )
{
    bActiveGraphicSh = bActive;

    if(bActiveGraphicSh)
        SetCurSubShell(OST_Graphic);
}

void ScTabViewShell::SetMediaShell( bool bActive )
{
    bActiveMediaSh = bActive;

    if(bActiveMediaSh)
        SetCurSubShell(OST_Media);
}

void ScTabViewShell::SetOleObjectShell( bool bActive )
{
    bActiveOleObjectSh = bActive;

    if(bActiveOleObjectSh)
        SetCurSubShell(OST_OleObject);
    else
        SetCurSubShell(OST_Cell);
}

void ScTabViewShell::SetEditShell(EditView* pView, bool bActive )
{
    if(bActive)
    {
        if (pEditShell)
            pEditShell->SetEditView( pView );
        else
            pEditShell.reset( new ScEditShell(pView, GetViewData()) );

        SetCurSubShell(OST_Editing);
    }
    else if(bActiveEditSh)
    {
        // Restore the surrounding context (Pivot/Table/Sparkline/Cell) instead
        // of always falling back to OST_Cell, so editing a cell inside a Table
        // area does not produce a stray Cell broadcast on edit-exit.
        UpdateContextShells();
        GetViewData().SetEditHighlight(false);
    }
    bActiveEditSh = bActive;
}

void ScTabViewShell::SetCurSubShell(ObjectSelectionType eOST, bool bForce)
{
    ScViewData& rViewData   = GetViewData();
    ScDocShell* pDocSh      = rViewData.GetDocShell();

    if(bDontSwitch) return;

    if(!pCellShell) // is anyway always used
    {
        pCellShell.reset(new ScCellShell(GetViewData(), GetFrameWin()));
        pCellShell->SetRepeatTarget( &aTarget );
    }

    bool bPgBrk = rViewData.IsPagebreakMode();

    if(bPgBrk && !pPageBreakShell)
    {
        pPageBreakShell.reset( new ScPageBreakShell( this ) );
        pPageBreakShell->SetRepeatTarget( &aTarget );
    }

    if ( !(eOST!=eCurOST || bForce) )
        return;

    bool bCellBrush = false;    // "format paint brush" allowed for cells
    bool bDrawBrush = false;    // "format paint brush" allowed for drawing objects

    // Suppress the stray "Cell" broadcast emitted by AddSubShell(*pCellShell)
    // when pCellShell is pushed as the base of a multi-shell stack on the way
    // to a non-Cell target (Editing/Table/Sparkline/Pivot/Auditing all push
    // pCellShell first, then their real shell on top).
    const bool bWasCellBroadcasterEnabled =
        pCellShell->SetContextBroadcasterEnabled(eOST == OST_Cell);
    comphelper::ScopeGuard aCellBroadcasterGuard(
        [&] { pCellShell->SetContextBroadcasterEnabled(bWasCellBroadcasterEnabled); });

    if(eCurOST!=OST_NONE) RemoveSubShell();

    if (pFormShell && !bFormShellAtTop)
        AddSubShell(*pFormShell);               // add below own subshells

    switch(eOST)
    {
        case    OST_Cell:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);
            bCellBrush = true;
        }
        break;
        case    OST_Editing:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);

            if(pEditShell)
            {
                AddSubShell(*pEditShell);
            }
        }
        break;
        case    OST_DrawText:
        {
            if ( !pDrawTextShell )
            {
                pDocSh->MakeDrawLayer();
                pDrawTextShell.reset( new ScDrawTextObjectBar(GetViewData()) );
            }
            AddSubShell(*pDrawTextShell);
        }
        break;
        case    OST_Drawing:
        {
            if (svx::checkForSelectedCustomShapes(
                        GetScDrawView(), true /* bOnlyExtruded */ )) {
                if (pExtrusionBarShell == nullptr)
                    pExtrusionBarShell.reset( new svx::ExtrusionBar(this) );
                AddSubShell( *pExtrusionBarShell );
            }

            if (svx::checkForSelectedFontWork(
                        GetScDrawView() )) {
                if (pFontworkBarShell == nullptr)
                    pFontworkBarShell.reset( new svx::FontworkBar(this) );
                AddSubShell( *pFontworkBarShell );
            }

            if ( !pDrawShell )
            {
                pDocSh->MakeDrawLayer();
                pDrawShell.reset(new ScDrawShell(GetViewData()));
                pDrawShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pDrawShell);
            bDrawBrush = true;
        }
        break;

        case    OST_DrawForm:
        {
            if ( !pDrawFormShell )
            {
                pDocSh->MakeDrawLayer();
                pDrawFormShell.reset( new ScDrawFormShell(GetViewData()) );
                pDrawFormShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pDrawFormShell);
            bDrawBrush = true;
        }
        break;

        case    OST_Chart:
        {
            if ( !pChartShell )
            {
                pDocSh->MakeDrawLayer();
                pChartShell.reset( new ScChartShell(GetViewData()) );
                pChartShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pChartShell);
            bDrawBrush = true;
        }
        break;

        case    OST_OleObject:
        {
            if ( !pOleObjectShell )
            {
                pDocSh->MakeDrawLayer();
                pOleObjectShell.reset( new ScOleObjectShell(GetViewData()) );
                pOleObjectShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pOleObjectShell);
            bDrawBrush = true;
        }
        break;

        case    OST_Graphic:
        {
            if ( !pGraphicShell)
            {
                pDocSh->MakeDrawLayer();
                pGraphicShell.reset( new ScGraphicShell(GetViewData()) );
                pGraphicShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pGraphicShell);
            bDrawBrush = true;
        }
        break;

        case    OST_Media:
        {
            if ( !pMediaShell)
            {
                pDocSh->MakeDrawLayer();
                pMediaShell.reset( new ScMediaShell(GetViewData()) );
                pMediaShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pMediaShell);
        }
        break;

        case    OST_Pivot:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);

            if ( !pPivotShell )
            {
                pPivotShell.reset( new ScPivotShell( this ) );
                pPivotShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pPivotShell);
            bCellBrush = true;
        }
        break;
        case    OST_Auditing:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);

            if ( !pAuditingShell )
            {
                pDocSh->MakeDrawLayer();    // the waiting time rather now as on the click

                pAuditingShell.reset( new ScAuditingShell(GetViewData()) );
                pAuditingShell->SetRepeatTarget( &aTarget );
            }
            AddSubShell(*pAuditingShell);
            bCellBrush = true;
        }
        break;
        case OST_Sparkline:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);

            if (!m_pSparklineShell)
            {
                m_pSparklineShell.reset(new sc::SparklineShell(this));
                m_pSparklineShell->SetRepeatTarget(&aTarget);
            }
            AddSubShell(*m_pSparklineShell);
            bCellBrush = true;
        }
        break;
        case OST_Table:
        {
            AddSubShell(*pCellShell);
            if(bPgBrk) AddSubShell(*pPageBreakShell);

            if (!m_pTableShell)
            {
                m_pTableShell.reset(new ScTableShell(this));
                m_pTableShell->SetRepeatTarget(&aTarget);
            }
            AddSubShell(*m_pTableShell);
            bCellBrush = true;
        }
        break;
        default:
        OSL_FAIL("wrong shell requested");
        break;
    }

    if (pFormShell && bFormShellAtTop)
        AddSubShell(*pFormShell);               // add on top of own subshells

    eCurOST=eOST;

    // abort "format paint brush" when switching to an incompatible shell
    if ( ( GetBrushDocument() && !bCellBrush ) || ( GetDrawBrushSet() && !bDrawBrush ) )
        ResetBrushDocument();
}

void ScTabViewShell::SetFormShellAtTop( bool bSet )
{
    if ( pFormShell && !bSet )
        pFormShell->ForgetActiveControl();      // let the FormShell know it no longer has the focus

    if ( bFormShellAtTop != bSet )
    {
        bFormShellAtTop = bSet;
        SetCurSubShell( GetCurObjectSelectionType(), true );
    }
}

IMPL_LINK_NOARG(ScTabViewShell, FormControlActivated, LinkParamNone*, void)
{
    // a form control got the focus, so the form shell has to be on top
    SetFormShellAtTop( true );
}

// GetMySubShell / SetMySubShell: simulate old behavior,
// so that there is only one SubShell (only within the 5 own SubShells)

SfxShell* ScTabViewShell::GetMySubShell() const
{
    //  GetSubShell() was const before, and GetSubShell(sal_uInt16) should also be const...

    sal_uInt16 nPos = 0;
    SfxShell* pSub = const_cast<ScTabViewShell*>(this)->GetSubShell(nPos);
    while (pSub)
    {
        if  (pSub == pDrawShell.get()  || pSub == pDrawTextShell.get() || pSub == pEditShell.get() ||
             pSub == pPivotShell.get() || pSub == pAuditingShell.get() || pSub == pDrawFormShell.get() ||
             pSub == pCellShell.get()  || pSub == pOleObjectShell.get() || pSub == pChartShell.get() ||
             pSub == pGraphicShell.get() || pSub == pMediaShell.get() || pSub == pPageBreakShell.get() ||
             pSub == m_pSparklineShell.get() || pSub == m_pTableShell.get())
        {
            return pSub;    // found
        }

        pSub = const_cast<ScTabViewShell*>(this)->GetSubShell(++nPos);
    }
    return nullptr;        // none from mine present
}

bool ScTabViewShell::IsDrawTextShell() const
{
    return ( pDrawTextShell && ( GetMySubShell() == pDrawTextShell.get() ) );
}

bool ScTabViewShell::IsAuditShell() const
{
    return ( pAuditingShell && ( GetMySubShell() == pAuditingShell.get() ) );
}

void ScTabViewShell::SetDrawTextUndo( SfxUndoManager* pNewUndoMgr )
{
    // Default: undo manager for DocShell
    if (!pNewUndoMgr)
        pNewUndoMgr = GetViewData().GetDocShell()->GetUndoManager();

    if (pDrawTextShell)
    {
        pDrawTextShell->SetUndoManager(pNewUndoMgr);
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        if ( pNewUndoMgr == pDocSh->GetUndoManager() &&
             !pDocSh->GetDocument().IsUndoEnabled() )
        {
            pNewUndoMgr->SetMaxUndoActionCount( 0 );
        }
    }
    else
    {
        OSL_FAIL("SetDrawTextUndo without DrawTextShell");
    }
}

ScTabViewShell* ScTabViewShell::GetActiveViewShell()
{
    return dynamic_cast< ScTabViewShell *>( SfxViewShell::Current() );
}

SfxPrinter* ScTabViewShell::GetPrinter( bool bCreate )
{
    // printer is always present (is created for the FontList already on start-up)
    return GetViewData().GetDocShell()->GetPrinter(bCreate);
}

sal_uInt16 ScTabViewShell::SetPrinter( SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags )
{
    return GetViewData().GetDocShell()->SetPrinter( pNewPrinter, nDiffFlags );
}

bool ScTabViewShell::HasPrintOptionsPage() const
{
    return true;
}

std::unique_ptr<SfxTabPage> ScTabViewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet &rOptions )
{
    ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
    ::CreateTabPage ScTpPrintOptionsCreate = pFact->GetTabPageCreatorFunc(RID_SC_TP_PRINT);
    if ( ScTpPrintOptionsCreate )
        return ScTpPrintOptionsCreate(pPage, pController, &rOptions);
    return nullptr;
}

void ScTabViewShell::StopEditShell()
{
    if ( pEditShell != nullptr && !bDontSwitch )
        SetEditShell(nullptr, false );
}

// close handler to ensure function of dialog:

IMPL_LINK_NOARG(ScTabViewShell, SimpleRefClose, const OUString*, void)
{
    SfxInPlaceClient* pClient = GetIPClient();
    if ( pClient && pClient->IsObjectInPlaceActive() )
    {
        // If range selection was started with an active embedded object,
        // switch back to original sheet (while the dialog is still open).

        SetTabNo( GetViewData().GetRefTabNo() );
    }

    ScSimpleRefDlgWrapper::SetAutoReOpen( true );
}

// handlers to call UNO listeners:

static ScTabViewObj* lcl_GetViewObj( const ScTabViewShell& rShell )
{
    ScTabViewObj* pRet = nullptr;
    SfxViewFrame& rViewFrame = rShell.GetViewFrame();
    SfxFrame& rFrame = rViewFrame.GetFrame();
    uno::Reference<frame::XController> xController = rFrame.GetController();
    if (xController.is())
        pRet = dynamic_cast<ScTabViewObj*>( xController.get() );
    return pRet;
}

IMPL_LINK( ScTabViewShell, SimpleRefDone, const OUString&, aResult, void )
{
    ScTabViewObj* pImpObj = lcl_GetViewObj( *this );
    if ( pImpObj )
        pImpObj->RangeSelDone( aResult );
}

IMPL_LINK( ScTabViewShell, SimpleRefAborted, const OUString&, rResult, void )
{
    ScTabViewObj* pImpObj = lcl_GetViewObj( *this );
    if ( pImpObj )
        pImpObj->RangeSelAborted( rResult );
}

IMPL_LINK( ScTabViewShell, SimpleRefChange, const OUString&, rResult, void )
{
    ScTabViewObj* pImpObj = lcl_GetViewObj( *this );
    if ( pImpObj )
        pImpObj->RangeSelChanged( rResult );
}

void ScTabViewShell::StartSimpleRefDialog(
            const OUString& rTitle, const OUString& rInitVal,
            bool bCloseOnButtonUp, bool bSingleCell, bool bMultiSelection )
{
    SfxViewFrame& rViewFrm = GetViewFrame();

    if ( GetActiveViewShell() != this )
    {
        // #i18833# / #i34499# The API method can be called for a view that's not active.
        // Then the view has to be activated first, the same way as in Execute for SID_CURRENTDOC.
        // Can't use GrabFocus here, because it needs to take effect immediately.

        rViewFrm.GetFrame().Appear();
    }

    sal_uInt16 nId = ScSimpleRefDlgWrapper::GetChildWindowId();

    ScModule::get()->SetRefDialog(nId, true, &rViewFrm);

    ScSimpleRefDlgWrapper* pWnd = static_cast<ScSimpleRefDlgWrapper*>(rViewFrm.GetChildWindow( nId ));
    if (!pWnd)
        return;

    pWnd->SetCloseHdl( LINK( this, ScTabViewShell, SimpleRefClose ) );
    pWnd->SetUnoLinks( LINK( this, ScTabViewShell, SimpleRefDone ),
                       LINK( this, ScTabViewShell, SimpleRefAborted ),
                       LINK( this, ScTabViewShell, SimpleRefChange ) );
    pWnd->SetRefString( rInitVal );
    pWnd->SetFlags( bCloseOnButtonUp, bSingleCell, bMultiSelection );
    ScSimpleRefDlgWrapper::SetAutoReOpen( false );
    if (auto xWin = pWnd->GetController())
        xWin->set_title(rTitle);
    pWnd->StartRefInput();
}

void ScTabViewShell::StopSimpleRefDialog()
{
    SfxViewFrame& rViewFrm = GetViewFrame();
    sal_uInt16 nId = ScSimpleRefDlgWrapper::GetChildWindowId();

    ScSimpleRefDlgWrapper* pWnd = static_cast<ScSimpleRefDlgWrapper*>(rViewFrm.GetChildWindow( nId ));
    if (pWnd)
    {
        if (auto pWin = pWnd->GetController())
            pWin->response(RET_CLOSE);
    }
}

bool ScTabViewShell::TabKeyInput(const KeyEvent& rKEvt)
{
    ScModule* pScMod = ScModule::get();

    SfxViewFrame& rThisFrame = GetViewFrame();
    if ( rThisFrame.GetChildWindow( SID_OPENDLG_FUNCTION ) )
        return false;

    vcl::KeyCode aCode = rKEvt.GetKeyCode();
    bool bShift     = aCode.IsShift();
    bool bControl   = aCode.IsMod1();
    bool bAlt       = aCode.IsMod2();
    sal_uInt16 nCode    = aCode.GetCode();
    bool bUsed      = false;
    bool bInPlace   = pScMod->IsEditMode();     // Editengine gets all
    bool bAnyEdit   = pScMod->IsInputMode();    // only characters & backspace
    bool bDraw      = IsDrawTextEdit();

    HideNoteOverlay();   // note marker

    // don't do extra HideCursor/ShowCursor calls if EnterHandler will switch to a different sheet
    bool bOnRefSheet = ( GetViewData().GetRefTabNo() == GetViewData().GetTabNumber() );
    bool bHideCursor = ( ( nCode == KEY_RETURN && bInPlace ) || nCode == KEY_TAB ) && bOnRefSheet;

    if (bHideCursor)
        HideAllCursors();

    ScDocument& rDoc = GetViewData().GetDocument();
    rDoc.KeyInput();    // TimerDelays etc.

    if( bInPlace )
    {
        bUsed = pScMod->InputKeyEvent( rKEvt );         // input
        if( !bUsed )
            bUsed = SfxViewShell::KeyInput( rKEvt );    // accelerators
    }
    else if( bAnyEdit )
    {
        bool bIsType = false;
        sal_uInt16 nModi = aCode.GetModifier();
        sal_uInt16 nGroup = aCode.GetGroup();

        if ( nGroup == KEYGROUP_NUM || nGroup == KEYGROUP_ALPHA || nGroup == 0 )
            if ( !bControl && !bAlt )
                bIsType = true;

        if ( nGroup == KEYGROUP_MISC )
            switch ( nCode )
            {
                case KEY_RETURN:
                    bIsType = bControl && !bAlt;        // Control, Shift-Control-Return
                    if ( !bIsType && nModi == 0 )
                    {
                        // Does the Input Handler also want a simple Return?

                        ScInputHandler* pHdl = pScMod->GetInputHdl(this);
                        bIsType = pHdl && pHdl->TakesReturn();
                    }
                    break;
                case KEY_SPACE:
                    bIsType = !bControl && !bAlt;       // without modifier or Shift-Space
                    break;
                case KEY_ESCAPE:
                    bIsType = (nModi == 0); // only without modifier
                    break;
                default:
                    bIsType = true;
            }
        else if (nCode == KEY_RIGHT && !bControl && !bShift && !bAlt)
        {
            ScInputHandler* pHdl = pScMod->GetInputHdl(this);
            bIsType = pHdl && pHdl->HasPartialComplete();
        }

        if( bIsType )
            bUsed = pScMod->InputKeyEvent( rKEvt );     // input

        if( !bUsed )
            bUsed = SfxViewShell::KeyInput( rKEvt );    // accelerators

        if ( !bUsed && !bIsType && nCode != KEY_RETURN )    // input once again afterwards
            bUsed = pScMod->InputKeyEvent( rKEvt );
    }
    else
    {
        // special case: copy/cut for multiselect  -> error message
        //  (Slot is disabled, so SfxViewShell::KeyInput would be swallowed without a comment)
        KeyFuncType eFunc = aCode.GetFunction();
        if ( eFunc == KeyFuncType::CUT )
        {
            ScRange aDummy;
            ScMarkType eMarkType = GetViewData().GetSimpleArea( aDummy );
            if (eMarkType != SC_MARK_SIMPLE)
            {
                ErrorMessage(STR_NOMULTISELECT);
                bUsed = true;
            }
        }
        if (!bUsed)
            bUsed = SfxViewShell::KeyInput( rKEvt );    // accelerators

        //  during inplace editing, some slots are handled by the
        //  container app and are executed during Window::KeyInput.
        //  -> don't pass keys to input handler that would be used there
        //  but should call slots instead.
        bool bParent = ( GetViewFrame().GetFrame().IsInPlace() && eFunc != KeyFuncType::DONTKNOW );

        if( !bUsed && !bDraw && nCode != KEY_RETURN && !bParent )
            bUsed = pScMod->InputKeyEvent( rKEvt, true );       // input
    }

    if (!bInPlace && !bUsed && !bDraw)
    {
        switch (nCode)
        {
            case KEY_RETURN:
                {
                    bool bNormal = !bControl && !bAlt;
                    if ( !bAnyEdit && bNormal )
                    {
                        // Depending on options, Enter switches to edit mode.
                        const ScInputOptions& rOpt = pScMod->GetInputOptions();
                        if ( rOpt.GetEnterEdit() )
                        {
                            pScMod->SetInputMode( SC_INPUT_TABLE );
                            bUsed = true;
                        }
                    }

                    bool bEditReturn = bControl && !bShift;         // pass on to edit engine
                    if ( !bUsed && !bEditReturn )
                    {
                        if ( bOnRefSheet )
                            HideAllCursors();

                        ScEnterMode nMode = ScEnterMode::NORMAL;
                        if ( bShift && bControl )
                            nMode = ScEnterMode::MATRIX;
                        else if ( bAlt )
                            nMode = ScEnterMode::BLOCK;
                        pScMod->InputEnterHandler(nMode);

                        if (nMode == ScEnterMode::NORMAL)
                        {
                            if( bShift )
                                GetViewData().GetDispatcher().Execute( SID_CURSORENTERUP,
                                            SfxCallMode::SLOT | SfxCallMode::RECORD );
                            else
                                GetViewData().GetDispatcher().Execute( SID_CURSORENTERDOWN,
                                            SfxCallMode::SLOT | SfxCallMode::RECORD );
                        }
                        else
                            UpdateInputHandler(true);

                        if ( bOnRefSheet )
                            ShowAllCursors();

                        // here no UpdateInputHandler, since during reference input on another
                        // document this ViewShell is not the one that is used for input.

                        bUsed = true;
                    }
                }
                break;
        }
    }

    // hard-code Alt-Cursor key, since Alt is not configurable

    if ( !bUsed && bAlt && !bControl )
    {
        sal_uInt16 nSlotId = 0;
        switch (nCode)
        {
            case KEY_UP:
                ModifyCellSize( DIR_TOP, bShift );
                bUsed = true;
                break;
            case KEY_DOWN:
                ModifyCellSize( DIR_BOTTOM, bShift );
                bUsed = true;
                break;
            case KEY_LEFT:
                ModifyCellSize( DIR_LEFT, bShift );
                bUsed = true;
                break;
            case KEY_RIGHT:
                ModifyCellSize( DIR_RIGHT, bShift );
                bUsed = true;
                break;
            case KEY_PAGEUP:
                nSlotId = bShift ? SID_CURSORPAGELEFT_SEL : SID_CURSORPAGELEFT_;
                break;
            case KEY_PAGEDOWN:
                nSlotId = bShift ? SID_CURSORPAGERIGHT_SEL : SID_CURSORPAGERIGHT_;
                break;
            case KEY_EQUAL:
            {
                // #tdf39302: Use "Alt + =" for autosum
                if ( !bAnyEdit ) // Ignore shortcut if currently editing a cell
                {
                    ScInputHandler* pHdl = pScMod->GetInputHdl(this);
                    if ( pHdl )
                    {
                        ScInputWindow* pWin = pHdl->GetInputWindow();
                        if ( pWin )
                        {
                            bool bRangeFinder = false;
                            bool bSubTotal = false;
                            pWin->AutoSum( bRangeFinder, bSubTotal, ocSum );
                        }
                    }

                    bUsed = true;
                    break;
                }
            }
        }
        if ( nSlotId )
        {
            GetViewData().GetDispatcher().Execute( nSlotId, SfxCallMode::SLOT | SfxCallMode::RECORD );
            bUsed = true;
        }
    }

    // use Ctrl+Alt+Shift+arrow keys to move the cursor in cells
    // while keeping the last selection
    if ( !bUsed && bAlt && bControl && bShift)
    {
        sal_uInt16 nSlotId = 0;
        switch (nCode)
        {
            case KEY_UP:
                nSlotId = SID_CURSORUP;
                break;
            case KEY_DOWN:
                nSlotId = SID_CURSORDOWN;
                break;
            case KEY_LEFT:
                nSlotId = SID_CURSORLEFT;
                break;
            case KEY_RIGHT:
                nSlotId = SID_CURSORRIGHT;
                break;
            case KEY_PAGEUP:
                nSlotId = SID_CURSORPAGEUP;
                break;
            case KEY_PAGEDOWN:
                nSlotId = SID_CURSORPAGEDOWN;
                break;
            case KEY_HOME:
                nSlotId = SID_CURSORHOME;
                break;
            case KEY_END:
                nSlotId = SID_CURSOREND;
                break;
            default:
                nSlotId = 0;
                break;
        }
        if ( nSlotId )
        {
            sal_uInt16 nMode = GetLockedModifiers();
            LockModifiers(KEY_MOD1);
            GetViewData().GetDispatcher().Execute( nSlotId, SfxCallMode::SLOT | SfxCallMode::RECORD );
            LockModifiers(nMode);
            bUsed = true;
        }
    }
    if (bHideCursor)
        ShowAllCursors();

    return bUsed;
}

bool ScTabViewShell::SfxKeyInput(const KeyEvent& rKeyEvent)
{
    return SfxViewShell::KeyInput( rKeyEvent );
}

bool ScTabViewShell::KeyInput( const KeyEvent &rKeyEvent )
{
    return TabKeyInput( rKeyEvent );
}

void ScTabViewShell::Construct( TriState nForceDesignMode )
{
    SfxApplication* pSfxApp  = SfxGetpApp();
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    bReadOnly = pDocSh->IsReadOnly();
    bIsActive = false;

    EnableAutoSpell(ScModule::GetAutoSpellProperty());

    SetName(u"View"_ustr); // for SBX
    Color aColBlack( COL_BLACK );
    ScModule* mod = ScModule::get();
    SetPool(&mod->GetPool());
    SetWindow( GetActiveWin() );

    pCurFrameLine.reset( new ::editeng::SvxBorderLine(&aColBlack, 20, SvxBorderLineStyle::SOLID) );
    StartListening(*GetViewData().GetDocShell(), DuplicateHandling::Prevent);
    StartListening(GetViewFrame(), DuplicateHandling::Prevent);
    StartListening(*pSfxApp, DuplicateHandling::Prevent); // #i62045# #i62046# application is needed for Calc's own hints

    SfxViewFrame* pFirst = SfxViewFrame::GetFirst(pDocSh);
    bool bFirstView = !pFirst
          || (pFirst == &GetViewFrame() && !SfxViewFrame::GetNext(*pFirst,pDocSh));

    if ( pDocSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
    {
        //TODO/LATER: is there a difference between the two GetVisArea methods?
        tools::Rectangle aVisArea = static_cast<const SfxObjectShell*>(pDocSh)->GetVisArea();

        SCTAB nVisTab = rDoc.GetVisibleTab();
        if (!rDoc.HasTable(nVisTab))
        {
            nVisTab = 0;
            rDoc.SetVisibleTab(nVisTab);
        }
        SetTabNo( nVisTab );
        bool bNegativePage = rDoc.IsNegativePage( nVisTab );
        // show the right cells
        GetViewData().SetScreenPos( bNegativePage ? aVisArea.TopRight() : aVisArea.TopLeft() );

        if ( GetViewFrame().GetFrame().IsInPlace() )                         // inplace
        {
            pDocSh->SetInplace( true );             // already initiated like this
            if (rDoc.IsEmbedded())
                rDoc.ResetEmbedded();              // no blue mark
        }
        else if ( bFirstView )
        {
            pDocSh->SetInplace( false );
            GetViewData().RefreshZoom();           // recalculate PPT
            if (!rDoc.IsEmbedded())
                rDoc.SetEmbedded( rDoc.GetVisibleTab(), aVisArea );                  // mark VisArea
        }
    }

    // ViewInputHandler
    // Each task now has its own InputWindow,
    // therefore either should each task get its own InputHandler,
    // or the InputWindow should create its own InputHandler
    // (then always search via InputWindow and only if not found
    // use the InputHandler of the App).
    // As an intermediate solution each View gets its own InputHandler,
    // which only yields problems if two Views are in one task window.
    mpInputHandler.reset(new ScInputHandler);

    // old version:
    //  if ( !GetViewFrame().ISA(SfxTopViewFrame) )        // OLE or Plug-In
    //      pInputHandler = new ScInputHandler;

            // create FormShell before MakeDrawView, so that DrawView can be registered at the
            // FormShell in every case
            // the FormShell is pushed in the first activate
    pFormShell.reset( new FmFormShell(this) );
    pFormShell->SetControlActivationHandler( LINK( this, ScTabViewShell, FormControlActivated ) );

            // DrawView must not be created in TabView - ctor,
            // if the ViewShell is not yet constructed...
    if (rDoc.GetDrawLayer())
        MakeDrawView( nForceDesignMode );
    ViewOptionsHasChanged(false, false);   // possibly also creates DrawView

    SfxUndoManager* pMgr = pDocSh->GetUndoManager();
    SetUndoManager( pMgr );
    pFormShell->SetUndoManager( pMgr );
    if ( !rDoc.IsUndoEnabled() )
    {
        pMgr->SetMaxUndoActionCount( 0 );
    }
    SetRepeatTarget( &aTarget );
    pFormShell->SetRepeatTarget( &aTarget );

    if ( bFirstView )   // first view?
    {
        rDoc.SetDocVisible( true );        // used when creating new sheets
        if ( pDocSh->IsEmpty() )
        {
            // set first sheet's RTL flag (following will already be initialized because of SetDocVisible)
            rDoc.SetLayoutRTL( 0, ScGlobal::IsSystemRTL() );

            // append additional sheets (not for OLE object)
            if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::EMBEDDED )
            {
                // Get the customized initial tab count
                const ScDefaultsOptions& rOpt = mod->GetDefaultsOptions();
                SCTAB nInitTabCount = rOpt.GetInitTabCount();

                for (SCTAB i=1; i<nInitTabCount; i++)
                    rDoc.MakeTable(i,false);
            }

            pDocSh->SetEmpty( false );          // #i6232# make sure this is done only once
        }

        // ReadExtOptions is now in Activate

        // link update no nesting
        if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::INTERNAL &&
             pDocSh->IsUpdateEnabled() )  // #105575#; update only in the first creation of the ViewShell
        {
            if (pDocSh->HasExternalLinks())
            {
                if ( !pFirst )
                    pFirst = &GetViewFrame();

                if(mod->GetCurRefDlgId()==0)
                {
                        pFirst->GetDispatcher()->Execute( SID_UPDATETABLINKS,
                                            SfxCallMode::ASYNCHRON | SfxCallMode::RECORD );
                }
            }
            else
            {
                // No links yet, but loading an existing document may have
                // disabled link update but there's no "Allow updating" infobar
                // that could enable it again. So in order to enable the user
                // to add formulas with external references allow link updates
                // again.
                pDocSh->AllowLinkUpdate();
            }

            bool bReImport = false;                             // update imported data
            ScDBCollection* pDBColl = rDoc.GetDBCollection();
            if ( pDBColl )
            {
                const ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs();
                bReImport = std::any_of(rDBs.begin(), rDBs.end(),
                    [](const std::unique_ptr<ScDBData>& rxDB) { return rxDB->IsStripData() && rxDB->HasImportParam() && !rxDB->HasImportSelection(); });
            }
            if (bReImport)
            {
                if ( !pFirst )
                    pFirst = &GetViewFrame();
                if(mod->GetCurRefDlgId()==0)
                {
                    pFirst->GetDispatcher()->Execute( SID_REIMPORT_AFTER_LOAD,
                                            SfxCallMode::ASYNCHRON | SfxCallMode::RECORD );
                }
            }
        }
    }

    UpdateAutoFillMark();

    // ScDispatchProviderInterceptor registers itself in ctor
    xDisProvInterceptor = new ScDispatchProviderInterceptor( this );

    bFirstActivate = true; // delay NavigatorUpdate until Activate()

    // #105575#; update only in the first creation of the ViewShell
    pDocSh->SetUpdateEnabled(false);

    if ( GetViewFrame().GetFrame().IsInPlace() )
        UpdateHeaderWidth(); // The inplace activation requires headers to be calculated

    SvBorder aBorder;
    GetBorderSize( aBorder, Size() );
    SetBorderPixel( aBorder );
}

class ScViewOptiChangesListener : public cppu::WeakImplHelper<util::XChangesListener>
{
public:
    ScViewOptiChangesListener(ScTabViewShell&);
    void stopListening();

    virtual void SAL_CALL changesOccurred(const util::ChangesEvent& Event) override;
    virtual void SAL_CALL disposing(const lang::EventObject& rEvent) override;

private:
    ScTabViewShell& mrViewShell;
    uno::Reference<util::XChangesNotifier> m_xViewChangesNotifier;
    uno::Reference<util::XChangesNotifier> m_xColorSchemeChangesNotifier;
};

void ScViewOptiChangesListener::stopListening()
{
    if (m_xViewChangesNotifier)
        m_xViewChangesNotifier->removeChangesListener(this);
    if (m_xColorSchemeChangesNotifier)
        m_xColorSchemeChangesNotifier->removeChangesListener(this);
}

// virtual
void SAL_CALL ScViewOptiChangesListener::changesOccurred(const util::ChangesEvent& rEvent)
{
    for (const auto& change : rEvent.Changes)
    {
        OUString sChangedEntry;
        if (change.Accessor >>= sChangedEntry)
        {
            if (sChangedEntry == u"ColumnRowHighlighting"_ustr)
            {
                mrViewShell.HighlightOverlay();
            }
            else if (sChangedEntry == u"ColorSchemes/org.openoffice.Office.UI:ColorScheme['COLOR_SCHEME_LIBREOFFICE_AUTOMATIC']/CalcCellFocus/Color"_ustr)
            {
                mrViewShell.GetActiveWin()->UpdateCursorOverlay();
                mrViewShell.GetActiveWin()->UpdateAutoFillOverlay();
                mrViewShell.GetActiveWin()->UpdateHighlightOverlay();
            }
            else if (sChangedEntry == u"ColorSchemes/org.openoffice.Office.UI:ColorScheme['COLOR_SCHEME_LIBREOFFICE_AUTOMATIC']/CalcDBFocus/Color"_ustr)
            {
                mrViewShell.GetActiveWin()->UpdateDatabaseOverlay();
            }
        }
    }
}

// virtual
void SAL_CALL ScViewOptiChangesListener::disposing(const lang::EventObject& /* rEvent */)
{
    m_xViewChangesNotifier.clear();
    m_xColorSchemeChangesNotifier.clear();
}

ScViewOptiChangesListener::ScViewOptiChangesListener(ScTabViewShell& rViewShell)
    : mrViewShell(rViewShell)
{
    // add a listener for configuration changes (ColumnRowHighlighting Checkbox)
    uno::Reference<lang::XMultiServiceFactory> xConfigurationProvider(
        configuration::theDefaultProvider::get(comphelper::getProcessComponentContext()));

    beans::NamedValue aViewProperty{ u"nodepath"_ustr,
                                 cpo::uno::Any(u"/org.openoffice.Office.Calc/Content/Display"_ustr) };

    beans::NamedValue aColorSchemeProperty{ u"nodepath"_ustr,
                                 cpo::uno::Any(u"/org.openoffice.Office.UI/ColorScheme"_ustr) };

    uno::Reference<uno::XInterface> xViewConfigurationAccess
        = xConfigurationProvider->createInstanceWithArguments(
            u"com.sun.star.configuration.ConfigurationAccess"_ustr, { cpo::uno::Any(aViewProperty) });

    uno::Reference<uno::XInterface> xColorSchemeConfigurationAccess
        = xConfigurationProvider->createInstanceWithArguments(
            u"com.sun.star.configuration.ConfigurationAccess"_ustr, { cpo::uno::Any(aColorSchemeProperty) });

    m_xViewChangesNotifier.set(xViewConfigurationAccess, uno::UNO_QUERY);
    m_xColorSchemeChangesNotifier.set(xColorSchemeConfigurationAccess, uno::UNO_QUERY);

    if (m_xViewChangesNotifier)
        m_xViewChangesNotifier->addChangesListener(this);

    if (m_xColorSchemeChangesNotifier)
        m_xColorSchemeChangesNotifier->addChangesListener(this);
}

/*  For rows I am passing reference to already existing sequence, and comparing the required
 *  columns.
 */
static bool lcl_CheckInArrayRows(std::vector<cpo::uno::Sequence<cpo::uno::Any>>& nUniqueRecords,
                             const cpo::uno::Sequence<cpo::uno::Any>& nCurrentRecord,
                             const std::vector<int>& rSelectedEntries)
{
    for (size_t m = 0; m < nUniqueRecords.size(); ++m)
    {
        bool bIsDuplicate = true;
        for (size_t n = 0; n < rSelectedEntries.size(); ++n)
        {
            // when the first different element is found
            int nColumn = rSelectedEntries[n];
            if (nUniqueRecords[m][nColumn] != nCurrentRecord[rSelectedEntries[n]])
            {
                bIsDuplicate = false;
                break;
            }
        }

        if (bIsDuplicate)
            return true;
    }
    return false;
}
/*  For columns, I am creating a sequence for each, with only the checked entries
 *  in the dialog.
 */
static bool lcl_CheckInArrayCols(std::vector<cpo::uno::Sequence<cpo::uno::Any>>& nUniqueRecords,
                             const cpo::uno::Sequence<cpo::uno::Any>& nCurrentRecord,
                             const std::vector<int>& rSelectedEntries)
{
    for (size_t m = 0; m < nUniqueRecords.size(); ++m)
    {
        bool bIsDuplicate = true;
        for (size_t n = 0; n < rSelectedEntries.size(); ++n)
        {
            // when the first different element is found
            int nColumn = n;
            if (nUniqueRecords[m][nColumn] != nCurrentRecord[n])
            {
                bIsDuplicate = false;
                break;
            }
        }

        if (bIsDuplicate)
            return true;
    }
    return false;
}

rtl::Reference<ScTableSheetObj> ScTabViewShell::GetRangeWithSheet(css::table::CellRangeAddress& rRangeData, bool& bHasData, bool bHasUnoArguments)
{
    // get spreadsheet document model & controller
    ScModelObj* pModel(GetViewData().GetDocShell()->GetModel());
    uno::Reference<frame::XController> xController(pModel->getCurrentController());

    // spreadsheet's extension of com.sun.star.frame.Controller service
    ScTabViewObj* pSpreadsheetDocument = dynamic_cast<ScTabViewObj*>(xController.get());
    assert(pSpreadsheetDocument);
    rtl::Reference<ScTableSheetObj> ActiveSheet = pSpreadsheetDocument->getActiveScSheet();

    if (!bHasUnoArguments)
    {
        // get the selection supplier, extract selection in XSheetCellRange
        cpo::uno::Any Selection = pSpreadsheetDocument->getSelection();
        uno::Reference<sheet::XSheetCellRange> SelectedCellRange;
        Selection >>= SelectedCellRange;

        // Get the Selected Range Address.
        uno::Reference<sheet::XCellRangeAddressable> xAddressable( SelectedCellRange, uno::UNO_QUERY);
        if (xAddressable.is())
            rRangeData = xAddressable->getRangeAddress();
        else
        {
            bHasData = false;
            return ActiveSheet;
        }
    }

    SCCOL nStartColumn = rRangeData.StartColumn;
    SCCOL nEndColumn = rRangeData.EndColumn;
    SCROW nStartRow = rRangeData.StartRow;
    SCROW nEndRow = rRangeData.EndRow;

    // shrink to intersection of data and selection. If no intersection ==> return
    bHasData = GetViewData().GetDocument().ShrinkToDataArea(rRangeData.Sheet, nStartColumn, nStartRow, nEndColumn, nEndRow);

    rRangeData.StartColumn = nStartColumn;
    rRangeData.StartRow = nStartRow;
    rRangeData.EndColumn = nEndColumn;
    rRangeData.EndRow = nEndRow;

    return ActiveSheet;
}

void ScTabViewShell::ExtendSingleSelection(css::table::CellRangeAddress& rRangeData)
{
    SCCOL aStartCol(rRangeData.StartColumn);
    SCCOL aEndCol(rRangeData.EndColumn);
    SCROW aStartRow(rRangeData.StartRow);
    SCROW aEndRow(rRangeData.EndRow);

    GetViewData().GetDocument().GetDataArea(rRangeData.Sheet, aStartCol, aStartRow, aEndCol,
                                            aEndRow, true, false);
    MarkRange(ScRange(ScAddress(aStartCol, aStartRow, rRangeData.Sheet),
                      ScAddress(aEndCol, aEndRow, rRangeData.Sheet)));

    rRangeData.StartRow = aStartRow;
    rRangeData.StartColumn = aStartCol;
    rRangeData.EndRow = aEndRow;
    rRangeData.EndColumn = aEndCol;
}

void ScTabViewShell::HandleDuplicateRecordsHighlight(const rtl::Reference<ScTableSheetObj>& ActiveSheet,
                                const css::table::CellRangeAddress& aRange,
                                bool bIncludesHeaders, bool bDuplicateRows,
                                const std::vector<int>& rSelectedEntries)
{
    if (rSelectedEntries.size() == 0)
    {
        Unmark();
        return;
    }

    ScModelObj* pModel(GetViewData().GetDocShell()->GetModel());
    rtl::Reference<ScCellRangeObj> xSheetRange(
            ActiveSheet->getScCellRangeByPosition(aRange.StartColumn, aRange.StartRow, aRange.EndColumn, aRange.EndRow));

    cpo::uno::Sequence<cpo::uno::Sequence<cpo::uno::Any>> aDataArray = xSheetRange->getDataArray();

    ScDocument& rDoc = GetViewData().GetDocShell()->GetDocument();

    comphelper::ScopeGuard aUndoContextGuard(
        [pModel, &rDoc] {
        pModel->getUndoManager()->leaveUndoContext();
        pModel->removeActionLock();
        if (pModel->hasControllersLocked())
            pModel->unlockControllers();
        rDoc.UnlockAdjustHeight();
    });

    rDoc.LockAdjustHeight();
    pModel->lockControllers();
    pModel->addActionLock();
    pModel->getUndoManager()->enterUndoContext(u"HandleDuplicateRecords"_ustr);

    bool nModifier = false;         // modifier key pressed?
    bool bNoDuplicatesForSelection = true;

    if (bDuplicateRows)
    {
        std::vector<cpo::uno::Sequence<cpo::uno::Any>> aUnionArray;
        sal_uInt32 nRow = bIncludesHeaders ? 1 : 0;
        sal_uInt32 lRows = aDataArray.getLength();

        while (nRow < lRows)
        {
            if (lcl_CheckInArrayRows(aUnionArray, aDataArray[nRow], rSelectedEntries))
            {
                for (int nCol = aRange.StartColumn; nCol <= aRange.EndColumn; ++nCol)
                {
                    bNoDuplicatesForSelection = false;
                    DoneBlockMode( nModifier );
                    nModifier = true;
                    InitBlockMode( nCol, aRange.StartRow + nRow, aRange.Sheet, false, false);
                }
            }
            else
            {
                aUnionArray.push_back(aDataArray[nRow]);
            }
            ++nRow;
        }
    }
    else
    {
        std::vector<cpo::uno::Sequence<cpo::uno::Any>> aUnionArray;
        sal_uInt32 nColumn = bIncludesHeaders ? 1 : 0;
        sal_uInt32 lColumns = aDataArray[0].getLength();

        while (nColumn < lColumns)
        {
            cpo::uno::Sequence<cpo::uno::Any> aSeq;
            aSeq.realloc(rSelectedEntries.size());
            for (size_t i = 0; i < rSelectedEntries.size(); ++i)
                aSeq.getArray()[i] = aDataArray[rSelectedEntries[i]][nColumn];

            if (lcl_CheckInArrayCols(aUnionArray, aSeq, rSelectedEntries))
            {
                for (int nRow = aRange.StartRow; nRow <= aRange.EndRow; ++nRow)
                {
                    bNoDuplicatesForSelection = false;
                    DoneBlockMode( nModifier );
                    nModifier = true;
                    InitBlockMode( aRange.StartColumn + nColumn, nRow, aRange.Sheet, false, false);
                }
            }
            else
            {
                aUnionArray.push_back(aSeq);
            }
            ++nColumn;
        }

    }

    if (bNoDuplicatesForSelection)
        Unmark();
}

void ScTabViewShell::HandleDuplicateRecordsRemove(const rtl::Reference<ScTableSheetObj>& ActiveSheet,
                                const css::table::CellRangeAddress& aRange,
                                bool bIncludesHeaders, bool bDuplicateRows,
                                const std::vector<int>& rSelectedEntries)
{
    if (rSelectedEntries.size() == 0)
    {
        Unmark();
        return;
    }

    ScModelObj* pModel(GetViewData().GetDocShell()->GetModel());
    rtl::Reference<ScCellRangeObj> xSheetRange(
            ActiveSheet->getScCellRangeByPosition(aRange.StartColumn, aRange.StartRow, aRange.EndColumn, aRange.EndRow));

    cpo::uno::Sequence<cpo::uno::Sequence<cpo::uno::Any>> aDataArray = xSheetRange->getDataArray();

    bool bAutoCalc = pModel->isAutomaticCalculationEnabled();
    ScDocument& rDoc = GetViewData().GetDocShell()->GetDocument();

    comphelper::ScopeGuard aUndoContextGuard(
        [pModel, &bAutoCalc, &rDoc] {
        pModel->getUndoManager()->leaveUndoContext();
        pModel->enableAutomaticCalculation(bAutoCalc);
        pModel->removeActionLock();
        if (pModel->hasControllersLocked())
            pModel->unlockControllers();
        rDoc.UnlockAdjustHeight();
    });

    rDoc.LockAdjustHeight();
    pModel->lockControllers();
    pModel->addActionLock();
    pModel->enableAutomaticCalculation(true);
    pModel->getUndoManager()->enterUndoContext(u"HandleDuplicateRecords"_ustr);

    if (bDuplicateRows)
    {
        std::vector<cpo::uno::Sequence<cpo::uno::Any>> aUnionArray;
        SCROW nRow = bIncludesHeaders ? 1 : 0;
        SCROW lRows = aDataArray.getLength();
        sal_uInt32 nDeleteCount = 0;
        SCROW nPrevRowDeleted = -1;
        std::vector<table::CellRangeAddress> aDelRanges;

        while (nRow < lRows)
        {
            if (lcl_CheckInArrayRows(aUnionArray, aDataArray[nRow], rSelectedEntries))
            {
                if (nPrevRowDeleted + 1 == nRow)
                    aDelRanges.back().EndRow++;
                else
                {
                    table::CellRangeAddress aCellRange(aRange.Sheet, aRange.StartColumn,
                                                aRange.StartRow + nRow - nDeleteCount,
                                                aRange.EndColumn, aRange.StartRow + nRow - nDeleteCount);
                    aDelRanges.push_back(aCellRange);
                }
                ++nDeleteCount;
                nPrevRowDeleted = nRow;
            }
            else
            {
                aUnionArray.push_back(aDataArray[nRow]);
            }
            ++nRow;
        }
        for (const table::CellRangeAddress & rRange : aDelRanges)
            ActiveSheet->removeRange(rRange, sheet::CellDeleteMode_UP);
    }
    else
    {
        std::vector<cpo::uno::Sequence<cpo::uno::Any>> aUnionArray;
        sal_uInt32 nDeleteCount = 0;
        SCCOL nColumn = bIncludesHeaders ? 1 : 0;
        SCCOL lColumns = aDataArray[0].getLength();
        SCCOL nPrevColDeleted = -1;
        std::vector<table::CellRangeAddress> aDelRanges;

        while (nColumn < lColumns)
        {
            cpo::uno::Sequence<cpo::uno::Any> aSeq;
            aSeq.realloc(rSelectedEntries.size());
            for (size_t i = 0; i < rSelectedEntries.size(); ++i)
                aSeq.getArray()[i] = aDataArray[rSelectedEntries[i]][nColumn];

            if (lcl_CheckInArrayCols(aUnionArray, aSeq, rSelectedEntries))
            {
                if (nPrevColDeleted + 1 == nColumn)
                    aDelRanges.back().EndColumn++;
                else
                {
                    table::CellRangeAddress aCellRange(aRange.Sheet,
                                aRange.StartColumn + nColumn - nDeleteCount, aRange.StartRow,
                                aRange.StartColumn + nColumn - nDeleteCount, aRange.EndRow);
                    aDelRanges.push_back(aCellRange);
                }
                nPrevColDeleted = nColumn;
                ++nDeleteCount;
            }
            else
            {
                aUnionArray.push_back(aSeq);
            }
            ++nColumn;
        }
        for (const table::CellRangeAddress & rRange : aDelRanges)
            ActiveSheet->removeRange(rRange, sheet::CellDeleteMode_LEFT);
    }
}

ScTabViewShell::ScTabViewShell( SfxViewFrame& rViewFrame,
                                SfxViewShell* pOldSh ) :
    SfxViewShell(rViewFrame, SfxViewShellFlags::HAS_PRINTOPTIONS),
    ScDBFunc( &rViewFrame.GetWindow(), static_cast<ScDocShell&>(*rViewFrame.GetObjectShell()), this ),
    eCurOST(OST_NONE),
    nDrawSfxId(0),
    aTarget(*this),
    bActiveDrawSh(false),
    bActiveDrawTextSh(false),
    bActiveDrawFormSh(false),
    bActiveOleObjectSh(false),
    bActiveChartSh(false),
    bActiveGraphicSh(false),
    bActiveMediaSh(false),
    bFormShellAtTop(false),
    bDontSwitch(false),
    bInFormatDialog(false),
    bReadOnly(false),
    bForceFocusOnCurCell(false),
    bInPrepareClose(false),
    bInDispose(false),
    bMoveKeepEdit(false),
    nCurRefDlgId(0),
    bIsTabChangeInProgress(false),
    mbInSwitch(false),
    m_pDragData(new ScDragData),
    m_pScCondFormatDlgData()
{
    const ScAppOptions& rAppOpt = ScModule::get()->GetAppOptions();

    //  if switching back from print preview,
    //  restore the view settings that were active when creating the preview
    //  ReadUserData must not happen from ctor, because the view's edit window
    //  has to be shown by the sfx. ReadUserData is deferred until the first Activate call.
    //  old DesignMode state from form layer must be restored, too

    TriState nForceDesignMode = TRISTATE_INDET;
    if ( auto pPreviewShell = dynamic_cast<ScPreviewShell*>( pOldSh) )
    {
        nForceDesignMode = pPreviewShell->GetSourceDesignMode();
        ScPreview* p = pPreviewShell->GetPreview();
        if (p)
            GetViewData().GetMarkData().SetSelectedTabs(p->GetSelectedTabs());
    }

    Construct( nForceDesignMode );

    // make Controller known to SFX
    new ScTabViewObj( this );

    // Resolves: tdf#53899 if there is no controller, register the above
    // ScTabViewObj as the current controller for the duration of the first
    // round of calculations triggered here by SetZoom. That way any StarBasic
    // macros triggered while the document is loading have a CurrentController
    // available to them.
    bool bInstalledScTabViewObjAsTempController = false;
    uno::Reference<frame::XController> xCurrentController(GetViewData().GetDocShell()->GetModel()->getCurrentController());
    if (!xCurrentController)
    {
        //GetController here returns the ScTabViewObj above
        GetViewData().GetDocShell()->GetModel()->setCurrentController(GetController());
        bInstalledScTabViewObjAsTempController = true;
    }
    xCurrentController.clear();

    if ( GetViewData().GetDocShell()->IsPreview() )
    {
        //  preview for template dialog: always show whole page
        SetZoomType( SvxZoomType::WHOLEPAGE, true );    // zoom value is recalculated at next Resize
    }
    else
    {
        double fFract = double(rAppOpt.GetZoom()) / 100;
        SetZoom( fFract, fFract, true );
        SetZoomType( rAppOpt.GetZoomType(), true );
    }

    SetCurSubShell(OST_Cell);
    SvBorder aBorder;
    GetBorderSize( aBorder, Size() );
    SetBorderPixel( aBorder );

    MakeDrawLayer();

    //put things back as we found them
    if (bInstalledScTabViewObjAsTempController)
        GetViewData().GetDocShell()->GetModel()->setCurrentController(nullptr);

    mChangesListener.set(new ScViewOptiChangesListener(*this));

    // formula mode in online is not usable in collaborative mode,
    // this is a workaround for disabling formula mode in online
    // when there is more than a single view
    if (!comphelper::COKit::isActive())
        return;

    {
        // Set the default locale/language of a new view to the document
        // language.
        ScDocShell* pDocShell = GetViewData().GetDocShell();
        ScDocument& rDoc = pDocShell->GetDocument();
        LanguageType eLatin, eCjk, eCtl;
        rDoc.GetLanguage(eLatin, eCjk, eCtl);
        OUString aLang = LanguageTag(eLatin).getBcp47();
        SetKitLanguageAndLocale(aLang);
    }

    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    // have we already one view ?
    if (!pViewShell)
        return;

    // this view is not yet visible at this stage, so we look for not visible views, too, for this same document
    SfxViewShell* pViewShell2 = pViewShell;
    do
    {
        pViewShell2 = SfxViewShell::GetNext(*pViewShell2, /*only visible shells*/ false);
    } while (pViewShell2 && pViewShell2->GetDocId() != pViewShell->GetDocId());
    // if the second view is not this one, it means that there is
    // already more than one active view and so the formula mode
    // has already been disabled
    if (pViewShell2 && pViewShell2 == this)
    {
        ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
        if (pTabViewShell)
        {
            ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler();
            if (pInputHdl && pInputHdl->IsFormulaMode())
            {
                pInputHdl->SetMode(SC_INPUT_NONE);
            }
        }
    }

    if (comphelper::COKit::isActive())
    {
        ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(GetCurrentDocument());
        KitHelper::notifyViewRenderState(this, pModel);
    }
}

ScTabViewShell::~ScTabViewShell()
{
    bInDispose = true;
    mChangesListener->stopListening();
    mChangesListener.clear();

    // Notify other COKit views that we are going away.
    KitHelper::notifyOtherViews(this, COKitCallbackType::VIEW_CURSOR_VISIBLE, "visible", "false"_ostr);
    KitHelper::notifyOtherViews(this, COKitCallbackType::TEXT_VIEW_SELECTION, "selection", ""_ostr);
    KitHelper::notifyOtherViews(this, COKitCallbackType::GRAPHIC_VIEW_SELECTION, "selection", "EMPTY"_ostr);
    KitHelper::notifyOtherViews(this, COKitCallbackType::CELL_VIEW_CURSOR, "rectangle", "EMPTY"_ostr);

    // all to NULL, in case the TabView-dtor tries to access them
    //! (should not really! ??!?!)
    if (mpInputHandler)
    {
        mpInputHandler->SetDocumentDisposing(true);
    }

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    EndListening(*pDocSh);
    EndListening(GetViewFrame());
    EndListening(*SfxGetpApp());           // #i62045# #i62046# needed now - SfxViewShell no longer does it

    ScModule::get()->ViewShellGone(this);

    RemoveSubShell();           // all
    SetWindow(nullptr);

    // need kill editview or we will touch the editengine after it has been freed by the ScInputHandler
    KillEditView(true);

    pFontworkBarShell.reset();
    pExtrusionBarShell.reset();
    pCellShell.reset();
    pPageBreakShell.reset();
    pDrawShell.reset();
    pDrawFormShell.reset();
    pOleObjectShell.reset();
    pChartShell.reset();
    pGraphicShell.reset();
    pMediaShell.reset();
    pDrawTextShell.reset();
    pEditShell.reset();
    pPivotShell.reset();
    m_pSparklineShell.reset();
    m_pTableShell.reset();
    pAuditingShell.reset();
    pCurFrameLine.reset();
    mpFormEditData.reset();
    mpInputHandler.reset();
    pDialogDPObject.reset();
    pNavSettings.reset();

    pFormShell.reset();
    pAccessibilityBroadcaster.reset();
}

void ScTabViewShell::SetDialogDPObject( std::unique_ptr<ScDPObject> pObj )
{
    pDialogDPObject = std::move(pObj);
}

void ScTabViewShell::FillFieldData( ScHeaderFieldData& rData )
{
    ScDocShell* pDocShell = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocShell->GetDocument();
    SCTAB nTab = GetViewData().GetTabNumber();
    OUString aTmp;
    rDoc.GetName(nTab, aTmp);
    rData.aTabName = aTmp;

    if( pDocShell->getDocProperties()->getTitle().getLength() != 0 )
        rData.aTitle = pDocShell->getDocProperties()->getTitle();
    else
        rData.aTitle = pDocShell->GetTitle();

    rData.SetDocNames(pDocShell->GetMedium()->GetURLObject());

    rData.nPageNo       = 1;
    rData.nTotalPages   = 99;

    // eNumType is known by the dialog
}

ScNavigatorSettings* ScTabViewShell::GetNavigatorSettings()
{
    if( !pNavSettings )
        pNavSettings.reset(new ScNavigatorSettings);
    return pNavSettings.get();
}

tools::Rectangle ScTabViewShell::getKitVisibleArea() const
{
    return GetViewData().getKitVisibleArea();
}

void ScTabViewShell::SetDragObject(ScTransferObj* pCellObj, ScDrawTransferObj* pDrawObj)
{
    ResetDragObject();
    m_pDragData->pCellTransfer = pCellObj;
    m_pDragData->pDrawTransfer = pDrawObj;
}

void ScTabViewShell::ResetDragObject()
{
    m_pDragData->pCellTransfer = nullptr;
    m_pDragData->pDrawTransfer = nullptr;
    m_pDragData->pJumpLocalDoc = nullptr;
    m_pDragData->aLinkDoc.clear();
    m_pDragData->aLinkTable.clear();
    m_pDragData->aLinkArea.clear();
    m_pDragData->aJumpTarget.clear();
    m_pDragData->aJumpText.clear();
}

void ScTabViewShell::SetDragLink(const OUString& rDoc, const OUString& rTab, const OUString& rArea)
{
    ResetDragObject();
    m_pDragData->aLinkDoc   = rDoc;
    m_pDragData->aLinkTable = rTab;
    m_pDragData->aLinkArea  = rArea;
}

void ScTabViewShell::SetDragJump(ScDocument* pLocalDoc, const OUString& rTarget, const OUString& rText)
{
    ResetDragObject();
    m_pDragData->pJumpLocalDoc = pLocalDoc;
    m_pDragData->aJumpTarget = rTarget;
    m_pDragData->aJumpText = rText;
}

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