/* -*- 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 <memory>
#include <algorithm>
#include <string_view>
 
#include <editeng/eeitem.hxx>
 
#include <sfx2/app.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/editview.hxx>
#include <editeng/editstat.hxx>
#include <editeng/lspcitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/langitem.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/event.hxx>
#include <editeng/scriptspaceitem.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/cursor.hxx>
#include <vcl/help.hxx>
#include <vcl/settings.hxx>
#include <svl/stritem.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weldutils.hxx>
#include <unotools/charclass.hxx>
 
#include <inputwin.hxx>
#include <scmod.hxx>
#include <global.hxx>
#include <scresid.hxx>
#include <strings.hrc>
#include <globstr.hrc>
#include <bitmaps.hlst>
#include <reffact.hxx>
#include <editutil.hxx>
#include <inputhdl.hxx>
#include <tabvwsh.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <appoptio.hxx>
#include <rangenam.hxx>
#include <rangeutl.hxx>
#include <docfunc.hxx>
#include <funcdesc.hxx>
#include <editeng/fontitem.hxx>
#include <AccessibleEditObject.hxx>
#include <AccessibleText.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <com/sun/star/frame/XLayoutManager.hpp>
#include <helpids.h>
#include <output.hxx>
 
namespace com::sun::star::accessibility { class XAccessible; }
 
const tools::Long THESIZE = 1000000;            // Should be more than enough!
const tools::Long INPUTLINE_INSET_MARGIN = 2;   // Space between border and interior widgets of input line
const tools::Long LEFT_OFFSET = 5;              // Left offset of input line
//TODO const long BUTTON_OFFSET = 2;            // Space between input line and button to expand/collapse
const tools::Long INPUTWIN_MULTILINES = 6;      // Initial number of lines within multiline dropdown
const tools::Long TOOLBOX_WINDOW_HEIGHT = 22;   // Height of toolbox window in pixels - TODO: The same on all systems?
const tools::Long POSITION_COMBOBOX_WIDTH = 18; // Width of position combobox in characters
const int RESIZE_HOTSPOT_HEIGHT = 4;
 
using com::sun::star::uno::Reference;
using com::sun::star::uno::UNO_QUERY;
 
using com::sun::star::frame::XLayoutManager;
using com::sun::star::beans::XPropertySet;
 
namespace {
 
constexpr ToolBoxItemId SID_INPUT_FUNCTION (SC_VIEW_START + 47);
constexpr ToolBoxItemId SID_INPUT_SUM     (SC_VIEW_START + 48);
constexpr ToolBoxItemId SID_INPUT_EQUAL   (SC_VIEW_START + 49);
constexpr ToolBoxItemId SID_INPUT_CANCEL  (SC_VIEW_START + 50);
constexpr ToolBoxItemId SID_INPUT_OK      (SC_VIEW_START + 51);
 
enum ScNameInputType
{
    SC_NAME_INPUT_CELL,
    SC_NAME_INPUT_RANGE,
    SC_NAME_INPUT_NAMEDRANGE_LOCAL,
    SC_NAME_INPUT_NAMEDRANGE_GLOBAL,
    SC_NAME_INPUT_DATABASE,
    SC_NAME_INPUT_ROW,
    SC_NAME_INPUT_SHEET,
    SC_NAME_INPUT_DEFINE,
    SC_NAME_INPUT_BAD_NAME,
    SC_NAME_INPUT_BAD_SELECTION,
    SC_MANAGE_NAMES
};
 
}
 
SFX_IMPL_CHILDWINDOW_WITHID(ScInputWindowWrapper,FID_INPUTLINE_STATUS)
 
ScInputWindowWrapper::ScInputWindowWrapper( vcl::Window*          pParentP,
                                            sal_uInt16           nId,
                                            SfxBindings*     pBindings,
                                            SfxChildWinInfo* /* pInfo */ )
    :   SfxChildWindow( pParentP, nId )
{
    VclPtr<ScInputWindow> pWin = VclPtr<ScInputWindow>::Create( pParentP, pBindings );
    SetWindow( pWin );
 
    pWin->Show();
 
    pWin->SetSizePixel( pWin->CalcWindowSizePixel() );
 
    SetAlignment(SfxChildAlignment::LOWESTTOP);
    pBindings->Invalidate( FID_TOGGLEINPUTLINE );
}
 
/**
 * GetInfo is disposed of if there's a SFX_IMPL_TOOLBOX!
 */
SfxChildWinInfo ScInputWindowWrapper::GetInfo() const
{
    SfxChildWinInfo aInfo = SfxChildWindow::GetInfo();
    return aInfo;
}
 
 
static VclPtr<ScInputBarGroup> lcl_chooseRuntimeImpl( vcl::Window* pParent, const SfxBindings* pBind )
{
    ScTabViewShell* pViewSh = nullptr;
    SfxDispatcher* pDisp = pBind->GetDispatcher();
    if ( pDisp )
    {
        SfxViewFrame* pViewFrm = pDisp->GetFrame();
        if ( pViewFrm )
            pViewSh = dynamic_cast<ScTabViewShell*>( pViewFrm->GetViewShell()  );
    }
 
    return VclPtr<ScInputBarGroup>::Create( pParent, pViewSh );
}
 
ScInputWindow::ScInputWindow( vcl::Window* pParent, const SfxBindings* pBind ) :
        // With WB_CLIPCHILDREN otherwise we get flickering
        ToolBox         ( pParent, WinBits(WB_CLIPCHILDREN | WB_BORDER | WB_NOSHADOW) ),
        aWndPos         ( !comphelper::LibreOfficeKit::isActive() ? VclPtr<ScPosWnd>::Create(this) : nullptr ),
        mxTextWindow    ( lcl_chooseRuntimeImpl( this, pBind ) ),
        pInputHdl       ( nullptr ),
        mpViewShell     ( nullptr ),
        mnMaxY          (0),
        mnStandardItemHeight(0),
        bIsOkCancelMode ( false ),
        bInResize       ( false )
{
    // #i73615# don't rely on SfxViewShell::Current while constructing the input line
    // (also for GetInputHdl below)
    ScTabViewShell* pViewSh = nullptr;
    SfxDispatcher* pDisp = pBind->GetDispatcher();
    if ( pDisp )
    {
        SfxViewFrame* pViewFrm = pDisp->GetFrame();
        if ( pViewFrm )
            pViewSh = dynamic_cast<ScTabViewShell*>( pViewFrm->GetViewShell()  );
    }
    OSL_ENSURE( pViewSh, "no view shell for input window" );
 
    mpViewShell = pViewSh;
 
    // Position window, 3 buttons, input window
    if (!comphelper::LibreOfficeKit::isActive())
    {
        InsertWindow    (ToolBoxItemId(1), aWndPos.get(), ToolBoxItemBits::NONE, 0);
        InsertSeparator (1);
        InsertItem      (SID_INPUT_FUNCTION, Image(StockImage::Yes, RID_BMP_INPUT_FUNCTION), ToolBoxItemBits::NONE, 2);
    }
 
    const bool bIsLOKMobilePhone = mpViewShell && mpViewShell->isLOKMobilePhone();
 
    // sigma and equal buttons
    if (!bIsLOKMobilePhone)
    {
        InsertItem      (SID_INPUT_SUM,      Image(StockImage::Yes, RID_BMP_INPUT_SUM), ToolBoxItemBits::DROPDOWN, 3);
        InsertItem      (SID_INPUT_EQUAL,    Image(StockImage::Yes, RID_BMP_INPUT_EQUAL), ToolBoxItemBits::NONE, 4);
        InsertItem      (SID_INPUT_CANCEL,   Image(StockImage::Yes, RID_BMP_INPUT_CANCEL), ToolBoxItemBits::NONE, 5);
        InsertItem      (SID_INPUT_OK,       Image(StockImage::Yes, RID_BMP_INPUT_OK), ToolBoxItemBits::NONE, 6);
    }
 
    InsertWindow    (ToolBoxItemId(7), mxTextWindow.get(), ToolBoxItemBits::NONE, 7);
    SetDropdownClickHdl( LINK( this, ScInputWindow, DropdownClickHdl ));
 
    if (!comphelper::LibreOfficeKit::isActive())
    {
        aWndPos   ->SetQuickHelpText(ScResId(SCSTR_QHELP_POSWND));
        aWndPos   ->SetHelpId       (HID_INSWIN_POS);
 
        mxTextWindow->SetQuickHelpText(ScResId(SCSTR_QHELP_INPUTWND));
        mxTextWindow->SetHelpId       (HID_INSWIN_INPUT);
 
        // No SetHelpText: the helptexts come from the Help
        SetItemText (SID_INPUT_FUNCTION, ScResId(SCSTR_QHELP_BTNCALC));
        SetHelpId   (SID_INPUT_FUNCTION, HID_INSWIN_CALC);
    }
 
    // sigma and equal buttons
    if (!bIsLOKMobilePhone)
    {
        SetHelpId   (SID_INPUT_SUM, HID_INSWIN_SUMME);
        SetHelpId   (SID_INPUT_EQUAL, HID_INSWIN_FUNC);
        SetHelpId   (SID_INPUT_CANCEL, HID_INSWIN_CANCEL);
        SetHelpId   (SID_INPUT_OK, HID_INSWIN_OK);
 
        if (!comphelper::LibreOfficeKit::isActive())
        {
            SetItemText ( SID_INPUT_SUM, ScResId( SCSTR_QHELP_BTNSUM ) );
            SetItemText ( SID_INPUT_EQUAL, ScResId( SCSTR_QHELP_BTNEQUAL ) );
            SetItemText ( SID_INPUT_CANCEL, ScResId( SCSTR_QHELP_BTNCANCEL ) );
            SetItemText ( SID_INPUT_OK, ScResId( SCSTR_QHELP_BTNOK ) );
        }
 
        EnableItem( SID_INPUT_CANCEL, false );
        EnableItem( SID_INPUT_OK, false );
 
        HideItem( SID_INPUT_CANCEL );
        HideItem( SID_INPUT_OK );
 
        mnStandardItemHeight = GetItemRect(SID_INPUT_SUM).GetHeight();
    }
 
    SetHelpId( HID_SC_INPUTWIN ); // For the whole input row
 
    if (!comphelper::LibreOfficeKit::isActive())
        aWndPos   ->Show();
    mxTextWindow->Show();
 
    pInputHdl = SC_MOD()->GetInputHdl( pViewSh, false ); // use own handler even if ref-handler is set
    if (pInputHdl)
        pInputHdl->SetInputWindow( this );
 
    if (pInputHdl && !pInputHdl->GetFormString().isEmpty())
    {
        // Switch over while the Function AutoPilot is active
        // -> show content of the Function AutoPilot again
        // Also show selection (remember at the InputHdl)
        mxTextWindow->SetTextString(pInputHdl->GetFormString(), true);
    }
    else if (pInputHdl && pInputHdl->IsInputMode())
    {
        // If the input row was hidden while editing (e.g. when editing a formula
        // and then switching to another document or the help), display the text
        // we just edited from the InputHandler
        mxTextWindow->SetTextString(pInputHdl->GetEditString(), true); // Display text
        if ( pInputHdl->IsTopMode() )
            pInputHdl->SetMode( SC_INPUT_TABLE ); // Focus ends up at the bottom anyways
    }
    else if (pViewSh)
    {
        // Don't stop editing in LOK a remote user might be editing.
        const bool bStopEditing = !comphelper::LibreOfficeKit::isActive();
        pViewSh->UpdateInputHandler(true, bStopEditing); // Absolutely necessary update
    }
 
    SetToolbarLayoutMode( ToolBoxLayoutMode::Locked );
 
    SetAccessibleName(ScResId(STR_ACC_TOOLBAR_FORMULA));
}
 
ScInputWindow::~ScInputWindow()
{
    disposeOnce();
}
 
void ScInputWindow::dispose()
{
    bool bDown = !ScGlobal::oSysLocale; // after Clear?
 
    //  if any view's input handler has a pointer to this input window, reset it
    //  (may be several ones, #74522#)
    //  member pInputHdl is not used here
 
    if ( !bDown )
    {
        SfxViewShell* pSh = SfxViewShell::GetFirst( true, checkSfxViewShell<ScTabViewShell> );
        while ( pSh )
        {
            ScInputHandler* pHdl = static_cast<ScTabViewShell*>(pSh)->GetInputHandler();
            if ( pHdl && pHdl->GetInputWindow() == this )
            {
                pHdl->SetInputWindow( nullptr );
                pHdl->StopInputWinEngine( false );  // reset pTopView pointer
            }
            pSh = SfxViewShell::GetNext( *pSh, true, checkSfxViewShell<ScTabViewShell> );
        }
    }
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        if (GetLOKNotifier())
            ReleaseLOKNotifier();
    }
 
    mxTextWindow.disposeAndClear();
    aWndPos.disposeAndClear();
 
    ToolBox::dispose();
}
 
void ScInputWindow::SetInputHandler( ScInputHandler* pNew )
{
    //  Is called in the Activate of the View ...
    if ( pNew != pInputHdl )
    {
        // On Reload (last version) the pInputHdl is the InputHandler of the old, deleted
        // ViewShell: so don't touch it here!
        pInputHdl = pNew;
        if (pInputHdl)
            pInputHdl->SetInputWindow( this );
    }
}
 
void ScInputWindow::Select()
{
    ScModule* pScMod = SC_MOD();
    ToolBox::Select();
 
    ToolBoxItemId curItemId = GetCurItemId();
    if (curItemId == SID_INPUT_FUNCTION)
    {
        //! new method at ScModule to query if function autopilot is open
        SfxViewFrame* pViewFrm = SfxViewFrame::Current();
        if ( pViewFrm && ( comphelper::LibreOfficeKit::isActive() || !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ) )
        {
            pViewFrm->GetDispatcher()->Execute( SID_OPENDLG_FUNCTION,
                                        SfxCallMode::SYNCHRON | SfxCallMode::RECORD );
 
            // The Toolbox will be disabled anyways, so we don't need to switch here,
            // regardless whether it succeeded or not!
//                  SetOkCancelMode();
        }
    }
    else if (curItemId == SID_INPUT_CANCEL)
    {
        pScMod->InputCancelHandler();
        SetSumAssignMode();
    }
    else if (curItemId == SID_INPUT_OK)
    {
        pScMod->InputEnterHandler();
        SetSumAssignMode();
        mxTextWindow->Invalidate(); // Or else the Selection remains
    }
    else if (curItemId == SID_INPUT_SUM)
    {
        bool bRangeFinder = false;
        bool bSubTotal = false;
        AutoSum(bRangeFinder, bSubTotal, ocSum);
    }
    else if (curItemId == SID_INPUT_EQUAL)
    {
        StartFormula();
    }
}
 
void ScInputWindow::StartFormula()
{
    ScModule* pScMod = SC_MOD();
    mxTextWindow->StartEditEngine();
    if ( pScMod->IsEditMode() ) // Isn't if e.g. protected
    {
        mxTextWindow->StartEditEngine();
 
        sal_Int32 nStartPos = 1;
        sal_Int32 nEndPos = 1;
 
        ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current()  );
        if ( pViewSh )
        {
            const OUString& rString = mxTextWindow->GetTextString();
            const sal_Int32 nLen = rString.getLength();
 
            ScDocument& rDoc = pViewSh->GetViewData().GetDocument();
            CellType eCellType = rDoc.GetCellType( pViewSh->GetViewData().GetCurPos() );
            switch ( eCellType )
            {
                case CELLTYPE_VALUE:
                {
                    nEndPos = nLen + 1;
                    mxTextWindow->SetTextString("=" +  rString, true);
                    break;
                }
                case CELLTYPE_STRING:
                case CELLTYPE_EDIT:
                    nStartPos = 0;
                    nEndPos = nLen;
                    break;
                case CELLTYPE_FORMULA:
                    nEndPos = nLen;
                    break;
                default:
                    mxTextWindow->SetTextString(u"="_ustr, true);
                    break;
            }
        }
 
        EditView* pView = mxTextWindow->GetEditView();
        if (pView)
        {
            sal_Int32 nStartPara = 0, nEndPara = 0;
            if (comphelper::LibreOfficeKit::isActive())
            {
                TextGrabFocus();
                if (pViewSh && !pViewSh->isLOKDesktop())
                {
                    nStartPara = nEndPara = pView->getEditEngine().GetParagraphCount() ?
                        (pView->getEditEngine().GetParagraphCount() - 1) : 0;
                    nStartPos = nEndPos = pView->getEditEngine().GetTextLen(nStartPara);
                }
            }
            pView->SetSelection(ESelection(nStartPara, nStartPos, nEndPara, nEndPos));
            pScMod->InputChanged(pView);
            SetOkCancelMode();
            pView->SetEditEngineUpdateLayout(true);
        }
    }
}
 
void ScInputWindow::PixelInvalidate(const tools::Rectangle* pRectangle)
{
    if (comphelper::LibreOfficeKit::isDialogPainting() || !comphelper::LibreOfficeKit::isActive())
        return;
 
    if (pRectangle)
    {
        tools::Rectangle aRect(*pRectangle);
        aRect.Move(-GetOutOffXPixel(), -GetOutOffYPixel());
        Window::PixelInvalidate(&aRect);
    }
    else
    {
        Window::PixelInvalidate(nullptr);
    }
}
 
void ScInputWindow::SetSizePixel( const Size& rNewSize )
{
    const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier();
    if (pNotifier)
    {
        if (vcl::Window* pFrameWindowImpl = GetParent())
        {
            if (vcl::Window* pWorkWindow = pFrameWindowImpl->GetParent())
            {
                if (vcl::Window* pImplBorderWindow = pWorkWindow->GetParent())
                {
                    Size aSize = pImplBorderWindow->GetSizePixel();
                    aSize.setWidth(rNewSize.getWidth());
                    pImplBorderWindow->SetSizePixel(aSize);
                }
            }
        }
    }
 
    ToolBox::SetSizePixel(rNewSize);
}
 
void ScInputWindow::Resize()
{
    ToolBox::Resize();
 
    Size aStartSize = GetSizePixel();
    Size aSize = aStartSize;
 
    auto nLines = mxTextWindow->GetNumLines();
    //(-10) to allow margin between sidebar and formulabar
    tools::Long margin = (comphelper::LibreOfficeKit::isActive()) ? 10 : 0;
    Size aTextWindowSize(aSize.Width() - mxTextWindow->GetPosPixel().X() - LEFT_OFFSET - margin,
                         mxTextWindow->GetPixelHeightForLines(nLines));
    mxTextWindow->SetSizePixel(aTextWindowSize);
 
    int nTopOffset = 0;
    if (nLines > 1)
    {
        // Initially there is 1 line and the edit is vertically centered in the toolbar
        // Later, if expanded then the vertical position of the edit will remain at
        // that initial position, so when calculating the overall size of the expanded
        // toolbar we have to include that initial offset in order to not make
        // the edit overlap the RESIZE_HOTSPOT_HEIGHT area so that dragging to resize
        // is still possible.
        int nNormalHeight = mxTextWindow->GetPixelHeightForLines(1);
        int nInitialTopMargin = (mnStandardItemHeight - nNormalHeight) / 2;
        if (nInitialTopMargin > 0)
            nTopOffset = nInitialTopMargin;
    }
 
    // add empty space of RESIZE_HOTSPOT_HEIGHT so resize is possible when hovering there
    aSize.setHeight(CalcWindowSizePixel().Height() + RESIZE_HOTSPOT_HEIGHT + nTopOffset);
 
    if (aStartSize != aSize)
        SetSizePixel(aSize);
 
    Invalidate();
}
 
void ScInputWindow::NotifyLOKClient()
{
    if (comphelper::LibreOfficeKit::isActive() && !GetLOKNotifier() && mpViewShell)
        SetLOKNotifier(mpViewShell);
}
 
void ScInputWindow::SetFuncString( const OUString& rString, bool bDoEdit )
{
    //! new method at ScModule to query if function autopilot is open
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) );
    mxTextWindow->StartEditEngine();
 
    ScModule* pScMod = SC_MOD();
    if ( !pScMod->IsEditMode() )
        return;
 
    if ( bDoEdit )
        mxTextWindow->TextGrabFocus();
    mxTextWindow->SetTextString(rString, true);
    EditView* pView = mxTextWindow->GetEditView();
    if (!pView)
        return;
 
    sal_Int32 nLen = rString.getLength();
 
    if ( nLen > 0 )
    {
        nLen--;
        pView->SetSelection( ESelection( 0, nLen, 0, nLen ) );
    }
 
    pScMod->InputChanged(pView);
    if ( bDoEdit )
        SetOkCancelMode(); // Not the case if immediately followed by Enter/Cancel
 
    pView->SetEditEngineUpdateLayout(true);
}
 
void ScInputWindow::SetPosString( const OUString& rStr )
{
    if (!comphelper::LibreOfficeKit::isActive())
        aWndPos->SetPos( rStr );
}
 
void ScInputWindow::SetTextString( const OUString& rString, bool bKitUpdate )
{
    if (rString.getLength() <= 32767)
        mxTextWindow->SetTextString(rString, bKitUpdate);
    else
        mxTextWindow->SetTextString(rString.copy(0, 32767), bKitUpdate);
}
 
void ScInputWindow::SetOkCancelMode()
{
    //! new method at ScModule to query if function autopilot is open
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) );
 
    if (bIsOkCancelMode)
        return;
 
    EnableItem  ( SID_INPUT_SUM,   false );
    EnableItem  ( SID_INPUT_EQUAL, false );
    HideItem    ( SID_INPUT_SUM );
    HideItem    ( SID_INPUT_EQUAL );
 
    ShowItem    ( SID_INPUT_CANCEL, true );
    ShowItem    ( SID_INPUT_OK,     true );
    EnableItem  ( SID_INPUT_CANCEL, true );
    EnableItem  ( SID_INPUT_OK,     true );
 
    bIsOkCancelMode = true;
}
 
void ScInputWindow::SetSumAssignMode()
{
    //! new method at ScModule to query if function autopilot is open
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) );
 
    if (!bIsOkCancelMode)
        return;
 
    EnableItem  ( SID_INPUT_CANCEL, false );
    EnableItem  ( SID_INPUT_OK,     false );
    HideItem    ( SID_INPUT_CANCEL );
    HideItem    ( SID_INPUT_OK );
 
    ShowItem    ( SID_INPUT_SUM,    true );
    ShowItem    ( SID_INPUT_EQUAL,  true );
    EnableItem  ( SID_INPUT_SUM,    true );
    EnableItem  ( SID_INPUT_EQUAL,  true );
 
    bIsOkCancelMode = false;
 
    SetFormulaMode(false); // No editing -> no formula
}
 
void ScInputWindow::SetFormulaMode( bool bSet )
{
    if (!comphelper::LibreOfficeKit::isActive())
        aWndPos->SetFormulaMode(bSet);
    mxTextWindow->SetFormulaMode(bSet);
}
 
bool ScInputWindow::IsInputActive()
{
    return mxTextWindow->IsInputActive();
}
 
EditView* ScInputWindow::GetEditView()
{
    return mxTextWindow->GetEditView();
}
 
vcl::Window* ScInputWindow::GetEditWindow()
{
    return mxTextWindow;
}
 
Point ScInputWindow::GetCursorScreenPixelPos(bool bBelow)
{
    return mxTextWindow->GetCursorScreenPixelPos(bBelow);
}
 
void ScInputWindow::MakeDialogEditView()
{
    mxTextWindow->MakeDialogEditView();
}
 
void ScInputWindow::StopEditEngine( bool bAll )
{
    mxTextWindow->StopEditEngine( bAll );
}
 
void ScInputWindow::TextGrabFocus()
{
    mxTextWindow->TextGrabFocus();
}
 
void ScInputWindow::TextInvalidate()
{
    mxTextWindow->Invalidate();
}
 
void ScInputWindow::SwitchToTextWin()
{
    // used for shift-ctrl-F2
 
    mxTextWindow->StartEditEngine();
    if ( SC_MOD()->IsEditMode() )
    {
        mxTextWindow->TextGrabFocus();
        EditView* pView = mxTextWindow->GetEditView();
        if (pView)
        {
            sal_Int32 nPara =  pView->getEditEngine().GetParagraphCount() ? ( pView->getEditEngine().GetParagraphCount() - 1 ) : 0;
            sal_Int32 nLen = pView->getEditEngine().GetTextLen( nPara );
            ESelection aSel( nPara, nLen, nPara, nLen );
            pView->SetSelection( aSel ); // set cursor to end of text
        }
    }
}
 
void ScInputWindow::PosGrabFocus()
{
    if (!comphelper::LibreOfficeKit::isActive())
        aWndPos->GrabFocus();
}
 
void ScInputWindow::EnableButtons( bool bEnable )
{
    //  when enabling buttons, always also enable the input window itself
    if ( bEnable && !IsEnabled() )
        Enable();
 
    EnableItem( SID_INPUT_FUNCTION,                                   bEnable );
    EnableItem( bIsOkCancelMode ? SID_INPUT_CANCEL : SID_INPUT_SUM,   bEnable );
    EnableItem( bIsOkCancelMode ? SID_INPUT_OK     : SID_INPUT_EQUAL, bEnable );
//  Invalidate();
}
 
void ScInputWindow::NumLinesChanged()
{
    mxTextWindow->NumLinesChanged();
}
 
void ScInputWindow::StateChanged( StateChangedType nType )
{
    ToolBox::StateChanged( nType );
 
    if ( nType == StateChangedType::InitShow ) Resize();
}
 
void ScInputWindow::DataChanged( const DataChangedEvent& rDCEvt )
{
    if ( rDCEvt.GetType() == DataChangedEventType::SETTINGS && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
    {
        //  update item images
        SetItemImage(SID_INPUT_FUNCTION, Image(StockImage::Yes, RID_BMP_INPUT_FUNCTION));
        if ( bIsOkCancelMode )
        {
            SetItemImage(SID_INPUT_CANCEL, Image(StockImage::Yes, RID_BMP_INPUT_CANCEL));
            SetItemImage(SID_INPUT_OK,     Image(StockImage::Yes, RID_BMP_INPUT_OK));
        }
        else
        {
            SetItemImage(SID_INPUT_SUM,   Image(StockImage::Yes, RID_BMP_INPUT_SUM));
            SetItemImage(SID_INPUT_EQUAL, Image(StockImage::Yes, RID_BMP_INPUT_EQUAL));
        }
    }
 
    ToolBox::DataChanged( rDCEvt );
}
 
bool ScInputWindow::IsPointerAtResizePos()
{
    return GetOutputSizePixel().Height() - GetPointerPosPixel().Y() <= RESIZE_HOTSPOT_HEIGHT;
}
 
void ScInputWindow::MouseMove( const MouseEvent& rMEvt )
{
    Point aPosPixel = GetPointerPosPixel();
 
    ScInputBarGroup* pGroupBar = mxTextWindow.get();
 
    if (bInResize || IsPointerAtResizePos())
        SetPointer(PointerStyle::WindowSSize);
    else
        SetPointer(PointerStyle::Arrow);
 
    if (bInResize)
    {
        // detect direction
        tools::Long nResizeThreshold = tools::Long(TOOLBOX_WINDOW_HEIGHT * 0.7);
        bool bResetPointerPos = false;
 
        // Detect attempt to expand toolbar too much
        if (aPosPixel.Y() >= mnMaxY)
        {
            bResetPointerPos = true;
            aPosPixel.setY( mnMaxY );
        } // or expanding down
        else if (GetOutputSizePixel().Height() - aPosPixel.Y() < -nResizeThreshold)
        {
            pGroupBar->IncrementVerticalSize();
            bResetPointerPos = true;
        } // or shrinking up
        else if ((GetOutputSizePixel().Height() - aPosPixel.Y()) > nResizeThreshold)
        {
            bResetPointerPos = true;
            pGroupBar->DecrementVerticalSize();
        }
 
        if (bResetPointerPos)
        {
            aPosPixel.setY(  GetOutputSizePixel().Height() );
            SetPointerPosPixel(aPosPixel);
        }
    }
 
    ToolBox::MouseMove(rMEvt);
}
 
void ScInputWindow::MouseButtonDown( const MouseEvent& rMEvt )
{
    if (rMEvt.IsLeft())
    {
        if (IsPointerAtResizePos())
        {
            // Don't leave the mouse pointer leave *this* window
            CaptureMouse();
            bInResize = true;
 
            // find the height of the gridwin, we don't want to be
            // able to expand the toolbar too far so we need to
            // calculate an upper limit
            // I'd prefer to leave at least a single column header and a
            // row but I don't know how to get that value in pixels.
            // Use TOOLBOX_WINDOW_HEIGHT for the moment
            if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
            {
                mnMaxY = GetOutputSizePixel().Height() + (pViewSh->GetGridHeight(SC_SPLIT_TOP)
                       + pViewSh->GetGridHeight(SC_SPLIT_BOTTOM)) - TOOLBOX_WINDOW_HEIGHT;
            }
        }
    }
 
    ToolBox::MouseButtonDown( rMEvt );
}
void ScInputWindow::MouseButtonUp( const MouseEvent& rMEvt )
{
    ReleaseMouse();
    if ( rMEvt.IsLeft() )
    {
        bInResize = false;
        mnMaxY = 0;
    }
 
    ToolBox::MouseButtonUp( rMEvt );
}
 
void ScInputWindow::AutoSum( bool& bRangeFinder, bool& bSubTotal, OpCode eCode )
{
    ScModule* pScMod = SC_MOD();
    ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current()  );
    if ( !pViewSh )
        return;
 
    const OUString aFormula = pViewSh->DoAutoSum(bRangeFinder, bSubTotal, eCode);
    if ( aFormula.isEmpty() )
        return;
 
    SetFuncString( aFormula );
    const sal_Int32 aOpen = aFormula.indexOf('(');
    const sal_Int32 aLen  = aFormula.getLength();
    if (!(bRangeFinder && pScMod->IsEditMode()))
        return;
 
    ScInputHandler* pHdl = pScMod->GetInputHdl( pViewSh );
    if ( !pHdl )
        return;
 
    pHdl->InitRangeFinder( aFormula );
 
    //! SetSelection at the InputHandler?
    //! Set bSelIsRef?
    if ( aOpen != -1 && aLen > aOpen )
    {
        ESelection aSel( 0, aOpen + (bSubTotal ? 3 : 1), 0, aLen-1 );
        EditView* pTableView = pHdl->GetTableView();
        if ( pTableView )
            pTableView->SetSelection( aSel );
        EditView* pTopView = pHdl->GetTopView();
        if ( pTopView )
            pTopView->SetSelection( aSel );
    }
}
 
ScInputBarGroup::ScInputBarGroup(vcl::Window* pParent, ScTabViewShell* pViewSh)
    : InterimItemWindow(pParent, u"modules/scalc/ui/inputbar.ui"_ustr, u"InputBar"_ustr, true, reinterpret_cast<sal_uInt64>(pViewSh))
    , mxBackground(m_xBuilder->weld_container(u"background"_ustr))
    , mxTextWndGroup(new ScTextWndGroup(*this, pViewSh))
    , mxButtonUp(m_xBuilder->weld_button(u"up"_ustr))
    , mxButtonDown(m_xBuilder->weld_button(u"down"_ustr))
{
    InitControlBase(m_xContainer.get());
 
    SetPaintTransparent(false);
    SetBackgrounds();
 
    mxButtonUp->connect_clicked(LINK(this, ScInputBarGroup, ClickHdl));
    mxButtonDown->connect_clicked(LINK(this, ScInputBarGroup, ClickHdl));
 
    if (!comphelper::LibreOfficeKit::isActive())
    {
        mxButtonUp->set_tooltip_text(ScResId( SCSTR_QHELP_COLLAPSE_FORMULA));
        mxButtonDown->set_tooltip_text(ScResId(SCSTR_QHELP_EXPAND_FORMULA));
    }
 
    int nHeight = mxTextWndGroup->GetPixelHeightForLines(1);
    mxButtonUp->set_size_request(-1, nHeight);
    mxButtonDown->set_size_request(-1, nHeight);
 
    // disable the multiline toggle on the mobile phones
    const SfxViewShell* pViewShell = SfxViewShell::Current();
    if (!comphelper::LibreOfficeKit::isActive() || !(pViewShell && pViewShell->isLOKMobilePhone()))
        mxButtonDown->show();
 
    // tdf#154042 Use an initial height of one row so the Toolbar positions
    // this in the same place regardless of how many rows it eventually shows
    Size aSize(GetSizePixel().Width(), nHeight);
    SetSizePixel(aSize);
}
 
void ScInputBarGroup::SetBackgrounds()
{
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    SetBackground(rStyleSettings.GetFaceColor());
    // match to bg used in ScTextWnd::SetDrawingArea to the margin area is drawn with the
    // same desired bg
    mxBackground->set_background(rStyleSettings.GetFieldColor());
}
 
void ScInputBarGroup::DataChanged(const DataChangedEvent& rDCEvt)
{
    InterimItemWindow::DataChanged(rDCEvt);
    if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
    {
        SetBackgrounds();
        Invalidate();
    }
}
 
Point ScInputBarGroup::GetCursorScreenPixelPos(bool bBelow)
{
    return mxTextWndGroup->GetCursorScreenPixelPos(bBelow);
}
 
ScInputBarGroup::~ScInputBarGroup()
{
    disposeOnce();
}
 
void ScInputBarGroup::dispose()
{
    mxTextWndGroup.reset();
    mxButtonUp.reset();
    mxButtonDown.reset();
    mxBackground.reset();
    InterimItemWindow::dispose();
}
 
void ScInputBarGroup::InsertAccessibleTextData( ScAccessibleEditLineTextData& rTextData )
{
    mxTextWndGroup->InsertAccessibleTextData(rTextData);
}
 
void ScInputBarGroup::RemoveAccessibleTextData( ScAccessibleEditLineTextData& rTextData )
{
    mxTextWndGroup->RemoveAccessibleTextData(rTextData);
}
 
const OUString& ScInputBarGroup::GetTextString() const
{
    return mxTextWndGroup->GetTextString();
}
 
void ScInputBarGroup::SetTextString(const OUString& rString, bool bKitUpdate)
{
    mxTextWndGroup->SetTextString(rString, bKitUpdate);
}
 
void ScInputBarGroup::Resize()
{
    mxTextWndGroup->SetScrollPolicy();
    InterimItemWindow::Resize();
}
 
void ScInputBarGroup::StopEditEngine(bool bAll)
{
    mxTextWndGroup->StopEditEngine(bAll);
}
 
void ScInputBarGroup::StartEditEngine()
{
    mxTextWndGroup->StartEditEngine();
}
 
void ScInputBarGroup::MakeDialogEditView()
{
    mxTextWndGroup->MakeDialogEditView();
}
 
EditView* ScInputBarGroup::GetEditView() const
{
    return mxTextWndGroup->GetEditView();
}
 
bool ScInputBarGroup::HasEditView() const
{
    return mxTextWndGroup->HasEditView();
}
 
bool ScInputBarGroup::IsInputActive()
{
    return mxTextWndGroup->IsInputActive();
}
 
void ScInputBarGroup::SetFormulaMode(bool bSet)
{
    mxTextWndGroup->SetFormulaMode(bSet);
}
 
void ScInputBarGroup::IncrementVerticalSize()
{
    mxTextWndGroup->SetNumLines(mxTextWndGroup->GetNumLines() + 1);
    TriggerToolboxLayout();
}
 
void ScInputBarGroup::DecrementVerticalSize()
{
    if (mxTextWndGroup->GetNumLines() > 1)
    {
        mxTextWndGroup->SetNumLines(mxTextWndGroup->GetNumLines() - 1);
        TriggerToolboxLayout();
    }
}
 
void ScInputWindow::MenuHdl(std::u16string_view command)
{
    if (command.empty())
        return;
 
    bool bSubTotal = false;
    bool bRangeFinder = false;
    OpCode eCode = ocSum;
    if ( command ==  u"sum" )
    {
        eCode = ocSum;
    }
    else if ( command == u"average" )
    {
        eCode = ocAverage;
    }
    else if ( command == u"max" )
    {
        eCode = ocMax;
    }
    else if ( command == u"min" )
    {
        eCode = ocMin;
    }
    else if ( command == u"count" )
    {
        eCode = ocCount;
    }
    else if ( command == u"counta" )
    {
        eCode = ocCount2;
    }
    else if ( command == u"product" )
    {
        eCode = ocProduct;
    }
    else if (command == u"stdev")
    {
        eCode = ocStDev;
    }
    else if (command == u"stdevp")
    {
        eCode = ocStDevP;
    }
    else if (command == u"var")
    {
        eCode = ocVar;
    }
    else if (command == u"varp")
    {
        eCode = ocVarP;
    }
 
    AutoSum( bRangeFinder, bSubTotal, eCode );
}
 
IMPL_LINK_NOARG(ScInputWindow, DropdownClickHdl, ToolBox *, void)
{
    ToolBoxItemId nCurID = GetCurItemId();
    EndSelection();
 
    if (nCurID == SID_INPUT_SUM)
    {
        tools::Rectangle aRect(GetItemRect(SID_INPUT_SUM));
        weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect);
        std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, u"modules/scalc/ui/autosum.ui"_ustr));
        std::unique_ptr<weld::Menu> xPopMenu(xBuilder->weld_menu(u"menu"_ustr));
        MenuHdl(xPopMenu->popup_at_rect(pPopupParent, aRect));
    }
}
 
IMPL_LINK_NOARG(ScInputBarGroup, ClickHdl, weld::Button&, void)
{
    if (mxTextWndGroup->GetNumLines() > 1)
        mxTextWndGroup->SetNumLines(1);
    else
        mxTextWndGroup->SetNumLines(mxTextWndGroup->GetLastNumExpandedLines());
 
    NumLinesChanged();
}
 
void ScInputBarGroup::NumLinesChanged()
{
    if (mxTextWndGroup->GetNumLines() > 1)
    {
        mxButtonDown->hide();
        mxButtonUp->show();
        mxTextWndGroup->SetLastNumExpandedLines(mxTextWndGroup->GetNumLines());
    }
    else
    {
        mxButtonUp->hide();
        mxButtonDown->show();
    }
    TriggerToolboxLayout();
 
    // Restore focus to input line(s) if necessary
    ScInputHandler* pHdl = SC_MOD()->GetInputHdl();
    if ( pHdl && pHdl->IsTopMode() )
        mxTextWndGroup->TextGrabFocus();
}
 
void ScInputBarGroup::TriggerToolboxLayout()
{
    // layout changes are expensive and un-necessary.
    if (comphelper::LibreOfficeKit::isActive())
        return;
 
    vcl::Window *w=GetParent();
    ScInputWindow &rParent = dynamic_cast<ScInputWindow&>(*w);
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
 
    if ( !pViewFrm )
        return;
 
    Reference< css::beans::XPropertySet > xPropSet( pViewFrm->GetFrame().GetFrameInterface(), UNO_QUERY );
    Reference< css::frame::XLayoutManager > xLayoutManager;
 
    if ( xPropSet.is() )
    {
        css::uno::Any aValue = xPropSet->getPropertyValue(u"LayoutManager"_ustr);
        aValue >>= xLayoutManager;
    }
 
    if ( !xLayoutManager.is() )
        return;
 
    xLayoutManager->lock();
    DataChangedEvent aFakeUpdate( DataChangedEventType::SETTINGS, nullptr,  AllSettingsFlags::STYLE );
 
    // this basically will trigger the repositioning of the
    // items in the toolbar from ImplFormat ( which is controlled by
    // mnWinHeight ) which in turn is updated in ImplCalcItem which is
    // controlled by mbCalc. Additionally the ImplFormat above is
    // controlled via mbFormat. It seems the easiest way to get these
    // booleans set is to send in the fake event below.
    rParent.DataChanged( aFakeUpdate);
 
    // highest item in toolbar will have been calculated via the
    // event above. Call resize on InputBar to pick up the height
    // change
    rParent.Resize();
 
    // unlock relayouts the toolbars in the 4 quadrants
    xLayoutManager->unlock();
}
 
void ScInputBarGroup::TextGrabFocus()
{
    mxTextWndGroup->TextGrabFocus();
}
 
constexpr tools::Long gnBorderWidth = (INPUTLINE_INSET_MARGIN + 1) * 2;
constexpr tools::Long gnBorderHeight = INPUTLINE_INSET_MARGIN + 1;
 
ScTextWndGroup::ScTextWndGroup(ScInputBarGroup& rParent, ScTabViewShell* pViewSh)
    : mxTextWnd(new ScTextWnd(*this, pViewSh))
    , mxScrollWin(rParent.GetBuilder().weld_scrolled_window(u"scrolledwindow"_ustr, true))
    , mxTextWndWin(new weld::CustomWeld(rParent.GetBuilder(), u"sc_input_window"_ustr, *mxTextWnd))
    , mrParent(rParent)
{
    mxScrollWin->connect_vadjustment_changed(LINK(this, ScTextWndGroup, Impl_ScrollHdl));
    if (ScTabViewShell* pActiveViewShell = comphelper::LibreOfficeKit::isActive() ?
            dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()) : nullptr)
    {
        pActiveViewShell->LOKSendFormulabarUpdate(nullptr, u""_ustr, ESelection());
    }
}
 
Point ScTextWndGroup::GetCursorScreenPixelPos(bool bBelow)
{
    Point aPos;
    if (!HasEditView())
        return aPos;
    EditView* pEditView = GetEditView();
    vcl::Cursor* pCur = pEditView->GetCursor();
    if (!pCur)
        return aPos;
    Point aLogicPos = pCur->GetPos();
    if (bBelow)
        aLogicPos.AdjustY(pCur->GetHeight());
    aPos = GetEditViewDevice().LogicToPixel(aLogicPos);
    bool bRTL = mrParent.IsRTLEnabled();
    if (bRTL)
        aPos.setX(mxTextWnd->GetOutputSizePixel().Width() - aPos.X() + gnBorderWidth);
    else
        aPos.AdjustX(gnBorderWidth + 1);
 
    return mrParent.OutputToScreenPixel(aPos);
}
 
ScTextWndGroup::~ScTextWndGroup()
{
}
 
void ScTextWndGroup::InsertAccessibleTextData(ScAccessibleEditLineTextData& rTextData)
{
    mxTextWnd->InsertAccessibleTextData(rTextData);
}
 
EditView* ScTextWndGroup::GetEditView() const
{
    return mxTextWnd->GetEditView();
}
 
const OutputDevice& ScTextWndGroup::GetEditViewDevice() const
{
    return mxTextWnd->GetEditViewDevice();
}
 
tools::Long ScTextWndGroup::GetLastNumExpandedLines() const
{
    return mxTextWnd->GetLastNumExpandedLines();
}
 
void ScTextWndGroup::SetLastNumExpandedLines(tools::Long nLastExpandedLines)
{
    mxTextWnd->SetLastNumExpandedLines(nLastExpandedLines);
}
 
tools::Long ScTextWndGroup::GetNumLines() const
{
    return mxTextWnd->GetNumLines();
}
 
int ScTextWndGroup::GetPixelHeightForLines(tools::Long nLines)
{
    return mxTextWnd->GetPixelHeightForLines(nLines) + 2 * gnBorderHeight;
}
 
weld::ScrolledWindow& ScTextWndGroup::GetScrollWin()
{
    return *mxScrollWin;
}
 
const OUString& ScTextWndGroup::GetTextString() const
{
    return mxTextWnd->GetTextString();
}
 
bool ScTextWndGroup::HasEditView() const
{
    return mxTextWnd->HasEditView();
}
 
bool ScTextWndGroup::IsInputActive()
{
    return mxTextWnd->IsInputActive();
}
 
void ScTextWndGroup::MakeDialogEditView()
{
    mxTextWnd->MakeDialogEditView();
}
 
void ScTextWndGroup::RemoveAccessibleTextData(ScAccessibleEditLineTextData& rTextData)
{
    mxTextWnd->RemoveAccessibleTextData(rTextData);
}
 
void ScTextWndGroup::SetScrollPolicy()
{
    if (mxTextWnd->GetNumLines() > 2)
        mxScrollWin->set_vpolicy(VclPolicyType::ALWAYS);
    else
        mxScrollWin->set_vpolicy(VclPolicyType::NEVER);
}
 
void ScTextWndGroup::SetNumLines(tools::Long nLines)
{
    mxTextWnd->SetNumLines(nLines);
}
 
void ScTextWndGroup::SetFormulaMode(bool bSet)
{
    mxTextWnd->SetFormulaMode(bSet);
}
 
void ScTextWndGroup::SetTextString(const OUString& rString, bool bKitUpdate)
{
    mxTextWnd->SetTextString(rString, bKitUpdate);
}
 
void ScTextWndGroup::StartEditEngine()
{
    mxTextWnd->StartEditEngine();
}
 
void ScTextWndGroup::StopEditEngine(bool bAll)
{
    mxTextWnd->StopEditEngine( bAll );
}
 
void ScTextWndGroup::TextGrabFocus()
{
    mxTextWnd->TextGrabFocus();
}
 
IMPL_LINK_NOARG(ScTextWndGroup, Impl_ScrollHdl, weld::ScrolledWindow&, void)
{
    mxTextWnd->DoScroll();
}
 
void ScTextWnd::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect )
{
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    Color aBgColor = rStyleSettings.GetFieldColor();
    rRenderContext.SetBackground(aBgColor);
 
    // tdf#137713 we rely on GetEditView creating it if it doesn't already exist so
    // GetEditView() must be called unconditionally
    if (EditView* pView = GetEditView())
    {
        if (mbInvalidate)
        {
            pView->Invalidate();
            mbInvalidate = false;
        }
    }
 
    if (comphelper::LibreOfficeKit::isActive() && m_xEditEngine)
    {
        // EditEngine/EditView works in twips logical coordinates, so set the device map-mode to twips before painting
        // and use twips version of the painting area 'rRect'.
        // Document zoom should not be included in this conversion.
        tools::Rectangle aLogicRect = OutputDevice::LogicToLogic(rRect, MapMode(MapUnit::MapPixel), MapMode(MapUnit::MapTwip));
        MapMode aOriginalMode = rRenderContext.GetMapMode();
        rRenderContext.SetMapMode(MapMode(MapUnit::MapTwip));
        WeldEditView::Paint(rRenderContext, aLogicRect);
        rRenderContext.SetMapMode(aOriginalMode);
    }
    else
        WeldEditView::Paint(rRenderContext, rRect);
}
 
EditView* ScTextWnd::GetEditView() const
{
    if ( !m_xEditView )
        const_cast<ScTextWnd&>(*this).InitEditEngine();
    return m_xEditView.get();
}
 
bool ScTextWnd::HasEditView() const { return m_xEditView != nullptr; }
 
const OutputDevice& ScTextWnd::GetEditViewDevice() const
{
    return EditViewOutputDevice();
}
 
int ScTextWnd::GetPixelHeightForLines(tools::Long nLines)
{
    OutputDevice& rDevice = GetDrawingArea()->get_ref_device();
    return rDevice.LogicToPixel(Size(0, nLines * rDevice.GetTextHeight())).Height() + 1;
}
 
tools::Long ScTextWnd::GetNumLines() const
{
    ScViewData& rViewData = mpViewShell->GetViewData();
    return rViewData.GetFormulaBarLines();
}
 
void ScTextWnd::SetNumLines(tools::Long nLines)
{
    ScViewData& rViewData = mpViewShell->GetViewData();
    rViewData.SetFormulaBarLines(nLines);
    if ( nLines > 1 )
    {
        // SetFormulaBarLines sanitizes the height, so get the sanitized value
        mnLastExpandedLines = rViewData.GetFormulaBarLines();
        Resize();
    }
}
 
void ScTextWnd::Resize()
{
    if (m_xEditView)
    {
        Size aOutputSize = GetOutputSizePixel();
        OutputDevice& rDevice = GetDrawingArea()->get_ref_device();
        tools::Rectangle aOutputArea = rDevice.PixelToLogic( tools::Rectangle( Point(), aOutputSize ));
        m_xEditView->SetOutputArea( aOutputArea );
 
        // Don't leave an empty area at the bottom if we can move the text down.
        tools::Long nMaxVisAreaTop = m_xEditEngine->GetTextHeight() - aOutputArea.GetHeight();
        if (m_xEditView->GetVisArea().Top() > nMaxVisAreaTop)
        {
            m_xEditView->Scroll(0, m_xEditView->GetVisArea().Top() - nMaxVisAreaTop);
        }
 
        m_xEditEngine->SetPaperSize( rDevice.PixelToLogic( Size( aOutputSize.Width(), 10000 ) ) );
    }
 
    // skip WeldEditView's Resize();
    weld::CustomWidgetController::Resize();
 
    SetScrollBarRange();
}
 
int ScTextWnd::GetEditEngTxtHeight() const
{
    return m_xEditView ? m_xEditView->getEditEngine().GetTextHeight() : 0;
}
 
void ScTextWnd::SetScrollBarRange()
{
    if (!m_xEditView)
        return;
 
    OutputDevice& rDevice = GetDrawingArea()->get_ref_device();
    Size aOutputSize = rDevice.GetOutputSize();
 
    int nUpper = GetEditEngTxtHeight();
    int nCurrentDocPos = m_xEditView->GetVisArea().Top();
    int nStepIncrement = GetTextHeight();
    int nPageIncrement = aOutputSize.Height();
    int nPageSize = aOutputSize.Height();
 
    /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has
       effectively...
 
       lower = gtk_adjustment_get_lower
       upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size
 
       and requires that upper > lower or the deceleration animation never ends
    */
    nPageSize = std::min(nPageSize, nUpper);
 
    weld::ScrolledWindow& rVBar = mrGroupBar.GetScrollWin();
    rVBar.vadjustment_configure(nCurrentDocPos, 0, nUpper,
                                nStepIncrement, nPageIncrement, nPageSize);
}
 
void ScTextWnd::DoScroll()
{
    if (m_xEditView)
    {
        weld::ScrolledWindow& rVBar = mrGroupBar.GetScrollWin();
        auto currentDocPos = m_xEditView->GetVisArea().Top();
        auto nDiff = currentDocPos - rVBar.vadjustment_get_value();
        // we expect SetScrollBarRange callback to be triggered by Scroll
        // to set where we ended up
        m_xEditView->Scroll(0, nDiff);
    }
}
 
void ScTextWnd::StartEditEngine()
{
    // Don't activate if we're a modal dialog ourselves (Doc-modal dialog)
    SfxObjectShell* pObjSh = SfxObjectShell::Current();
    if ( pObjSh && pObjSh->IsInModalMode() )
        return;
 
    if ( !m_xEditView || !m_xEditEngine )
    {
        InitEditEngine();
    }
 
    ScInputHandler* pHdl = mpViewShell->GetInputHandler();
    if (pHdl)
        pHdl->SetMode(SC_INPUT_TOP, nullptr, static_cast<ScEditEngineDefaulter*>(m_xEditEngine.get()));
 
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    if (pViewFrm)
        pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
}
 
static void lcl_ExtendEditFontAttribs( SfxItemSet& rSet )
{
    const SfxPoolItem& rFontItem = rSet.Get( EE_CHAR_FONTINFO );
    std::unique_ptr<SfxPoolItem> pNewItem(rFontItem.Clone());
    pNewItem->SetWhich(EE_CHAR_FONTINFO_CJK);
    rSet.Put( *pNewItem );
    pNewItem->SetWhich(EE_CHAR_FONTINFO_CTL);
    rSet.Put( *pNewItem );
    const SfxPoolItem& rHeightItem = rSet.Get( EE_CHAR_FONTHEIGHT );
    pNewItem.reset(rHeightItem.Clone());
    pNewItem->SetWhich(EE_CHAR_FONTHEIGHT_CJK);
    rSet.Put( *pNewItem );
    pNewItem->SetWhich(EE_CHAR_FONTHEIGHT_CTL);
    rSet.Put( *pNewItem );
    const SfxPoolItem& rWeightItem = rSet.Get( EE_CHAR_WEIGHT );
    pNewItem.reset(rWeightItem.Clone());
    pNewItem->SetWhich(EE_CHAR_WEIGHT_CJK);
    rSet.Put( *pNewItem );
    pNewItem->SetWhich(EE_CHAR_WEIGHT_CTL);
    rSet.Put( *pNewItem );
    const SfxPoolItem& rItalicItem = rSet.Get( EE_CHAR_ITALIC );
    pNewItem.reset(rItalicItem.Clone());
    pNewItem->SetWhich(EE_CHAR_ITALIC_CJK);
    rSet.Put( *pNewItem );
    pNewItem->SetWhich(EE_CHAR_ITALIC_CTL);
    rSet.Put( *pNewItem );
    const SfxPoolItem& rLangItem = rSet.Get( EE_CHAR_LANGUAGE );
    pNewItem.reset(rLangItem.Clone());
    pNewItem->SetWhich(EE_CHAR_LANGUAGE_CJK);
    rSet.Put( *pNewItem );
    pNewItem->SetWhich(EE_CHAR_LANGUAGE_CTL);
    rSet.Put( *pNewItem );
}
 
static void lcl_ModifyRTLDefaults( SfxItemSet& rSet )
{
    rSet.Put( SvxAdjustItem( SvxAdjust::Right, EE_PARA_JUST ) );
 
    // always using rtl writing direction would break formulas
    //rSet.Put( SvxFrameDirectionItem( SvxFrameDirection::Horizontal_RL_TB, EE_PARA_WRITINGDIR ) );
 
    // PaperSize width is limited to USHRT_MAX in RTL mode (because of EditEngine's
    // sal_uInt16 values in EditLine), so the text may be wrapped and line spacing must be
    // increased to not see the beginning of the next line.
    SvxLineSpacingItem aItem( LINE_SPACE_DEFAULT_HEIGHT, EE_PARA_SBL );
    aItem.SetPropLineSpace( 200 );
    rSet.Put( aItem );
}
 
static void lcl_ModifyRTLVisArea( EditView* pEditView )
{
    tools::Rectangle aVisArea = pEditView->GetVisArea();
    Size aPaper = pEditView->getEditEngine().GetPaperSize();
    tools::Long nDiff = aPaper.Width() - aVisArea.Right();
    aVisArea.AdjustLeft(nDiff );
    aVisArea.AdjustRight(nDiff );
    pEditView->SetVisArea(aVisArea);
}
 
void ScTextWnd::InitEditEngine()
{
    std::unique_ptr<ScFieldEditEngine> pNew;
    ScDocShell* pDocSh = nullptr;
    if ( mpViewShell )
    {
        pDocSh = mpViewShell->GetViewData().GetDocShell();
        ScDocument& rDoc = mpViewShell->GetViewData().GetDocument();
        pNew = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
    }
    else
        pNew = std::make_unique<ScFieldEditEngine>(nullptr, EditEngine::CreatePool().get(), nullptr, true);
    pNew->SetExecuteURL( false );
    m_xEditEngine = std::move(pNew);
 
    Size barSize = GetOutputSizePixel();
    m_xEditEngine->SetUpdateLayout( false );
    m_xEditEngine->SetPaperSize( GetDrawingArea()->get_ref_device().PixelToLogic(Size(barSize.Width(),10000)) );
    m_xEditEngine->SetWordDelimiters(
                    ScEditUtil::ModifyDelimiters( m_xEditEngine->GetWordDelimiters() ) );
    m_xEditEngine->SetReplaceLeadingSingleQuotationMark( false );
 
    UpdateAutoCorrFlag();
 
    {
        auto pSet = std::make_unique<SfxItemSet>( m_xEditEngine->GetEmptyItemSet() );
        EditEngine::SetFontInfoInItemSet( *pSet, aTextFont );
        lcl_ExtendEditFontAttribs( *pSet );
        // turn off script spacing to match DrawText output
        pSet->Put( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) );
        if ( bIsRTL )
            lcl_ModifyRTLDefaults( *pSet );
        static_cast<ScEditEngineDefaulter*>(m_xEditEngine.get())->SetDefaults( std::move(pSet) );
    }
 
    // If the Cell contains URLFields, they need to be taken over into the entry row,
    // or else the position is not correct anymore
    bool bFilled = false;
    ScInputHandler* pHdl = SC_MOD()->GetInputHdl();
    if ( pHdl ) //! Test if it's the right InputHdl?
        bFilled = pHdl->GetTextAndFields(static_cast<ScEditEngineDefaulter&>(*m_xEditEngine));
 
    m_xEditEngine->SetUpdateLayout( true );
 
    // aString is the truth ...
    if (bFilled && m_xEditEngine->GetText() == aString)
        Invalidate(); // Repaint for (filled) Field
    else
        static_cast<ScEditEngineDefaulter*>(m_xEditEngine.get())->SetTextCurrentDefaults(aString); // At least the right text then
 
    m_xEditView = std::make_unique<EditView>(m_xEditEngine.get(), nullptr);
 
    // we get cursor, selection etc. messages from the VCL/window layer
    // otherwise these are injected into the document causing confusion.
    m_xEditView->SuppressLOKMessages(true);
 
    m_xEditView->setEditViewCallbacks(this);
    m_xEditView->SetInsertMode(bIsInsertMode);
 
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    Color aBgColor = rStyleSettings.GetFieldColor();
    m_xEditView->SetBackgroundColor(aBgColor);
 
    if (pAcc)
    {
        pAcc->InitAcc(nullptr, m_xEditView.get(),
                      ScResId(STR_ACC_EDITLINE_NAME),
                      ScResId(STR_ACC_EDITLINE_DESCR));
    }
 
    if (comphelper::LibreOfficeKit::isActive())
        m_xEditView->RegisterViewShell(mpViewShell);
 
    // Text from Clipboard is taken over as ASCII in a single row
    EVControlBits n = m_xEditView->GetControlWord();
    m_xEditView->SetControlWord( n | EVControlBits::SINGLELINEPASTE );
 
    m_xEditEngine->InsertView( m_xEditView.get(), EE_APPEND );
 
    Resize();
 
    if ( bIsRTL )
        lcl_ModifyRTLVisArea( m_xEditView.get() );
 
    m_xEditEngine->SetModifyHdl(LINK(this, ScTextWnd, ModifyHdl));
    m_xEditEngine->SetStatusEventHdl(LINK(this, ScTextWnd, EditStatusHdl));
 
    if (!maAccTextDatas.empty())
        maAccTextDatas.back()->StartEdit();
 
    // as long as EditEngine and DrawText sometimes differ for CTL text,
    // repaint now to have the EditEngine's version visible
    if (pDocSh)
    {
        ScDocument& rDoc = pDocSh->GetDocument(); // any document
        SvtScriptType nScript = rDoc.GetStringScriptType( aString );
        if ( nScript & SvtScriptType::COMPLEX )
            Invalidate();
    }
}
 
ScTextWnd::ScTextWnd(ScTextWndGroup& rParent, ScTabViewShell* pViewSh) :
        bIsRTL(AllSettings::GetLayoutRTL()),
        bIsInsertMode(true),
        bFormulaMode (false),
        bInputMode   (false),
        mpViewShell(pViewSh),
        mrGroupBar(rParent),
        mnLastExpandedLines(INPUTWIN_MULTILINES),
        mbInvalidate(false)
{
}
 
ScTextWnd::~ScTextWnd()
{
    while (!maAccTextDatas.empty()) {
        maAccTextDatas.back()->Dispose();
    }
}
 
bool ScTextWnd::MouseMove( const MouseEvent& rMEvt )
{
    return m_xEditView && m_xEditView->MouseMove(rMEvt);
}
 
bool ScTextWnd::CanFocus() const
{
    return SC_MOD()->IsEditMode();
}
 
void ScTextWnd::UpdateFocus()
{
    if (!HasFocus())
    {
        StartEditEngine();
        if (CanFocus())
            TextGrabFocus();
    }
}
 
bool ScTextWnd::MouseButtonDown( const MouseEvent& rMEvt )
{
    UpdateFocus();
 
    bool bClickOnSelection = false;
    if (m_xEditView)
    {
        m_xEditView->SetEditEngineUpdateLayout( true );
        bClickOnSelection = m_xEditView->IsSelectionAtPoint(rMEvt.GetPosPixel());
    }
    if (!bClickOnSelection)
    {
        rtl::Reference<TransferDataContainer> xTransferable(new TransferDataContainer);
        GetDrawingArea()->enable_drag_source(xTransferable, DND_ACTION_NONE);
    }
    else
    {
        rtl::Reference<TransferDataContainer> xTransferable(m_xHelper);
        GetDrawingArea()->enable_drag_source(xTransferable, DND_ACTION_COPY);
    }
    return WeldEditView::MouseButtonDown(rMEvt);
}
 
bool ScTextWnd::MouseButtonUp( const MouseEvent& rMEvt )
{
    bool bRet = WeldEditView::MouseButtonUp(rMEvt);
    if (bRet)
    {
        if ( rMEvt.IsMiddle() &&
                 Application::GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection )
        {
            //  EditView may have pasted from selection
            SC_MOD()->InputChanged( m_xEditView.get() );
        }
        else
            SC_MOD()->InputSelection( m_xEditView.get() );
    }
    return bRet;
}
 
bool ScTextWnd::Command( const CommandEvent& rCEvt )
{
    bool bConsumed = false;
 
    bInputMode = true;
    CommandEventId nCommand = rCEvt.GetCommand();
    if (m_xEditView)
    {
        ScModule* pScMod = SC_MOD();
        ScTabViewShell* pStartViewSh = ScTabViewShell::GetActiveViewShell();
 
        // don't modify the font defaults here - the right defaults are
        // already set in StartEditEngine when the EditEngine is created
 
        // Prevent that the EditView is lost when switching between Views
        pScMod->SetInEditCommand( true );
        m_xEditView->Command( rCEvt );
        pScMod->SetInEditCommand( false );
 
        //  CommandEventId::StartDrag does not mean by far that the content was actually changed,
        //  so don't trigger an InputChanged.
        //! Detect if dragged with Move or forbid Drag&Move somehow
 
        if ( nCommand == CommandEventId::StartDrag )
        {
            // Is dragged onto another View?
            ScTabViewShell* pEndViewSh = ScTabViewShell::GetActiveViewShell();
            if ( pEndViewSh != pStartViewSh && pStartViewSh != nullptr )
            {
                ScViewData& rViewData = pStartViewSh->GetViewData();
                ScInputHandler* pHdl = pScMod->GetInputHdl( pStartViewSh );
                if ( pHdl && rViewData.HasEditView( rViewData.GetActivePart() ) )
                {
                    pHdl->CancelHandler();
                    rViewData.GetView()->ShowCursor(); // Missing for KillEditView, due to being inactive
                }
            }
        }
        else if ( nCommand == CommandEventId::EndExtTextInput )
        {
            if (bFormulaMode)
            {
                ScInputHandler* pHdl = SC_MOD()->GetInputHdl();
                if (pHdl)
                    pHdl->InputCommand(rCEvt);
            }
            SC_MOD()->InputChanged( m_xEditView.get() );
        }
        else if ( nCommand == CommandEventId::CursorPos )
        {
            //  don't call InputChanged for CommandEventId::CursorPos
        }
        else if ( nCommand == CommandEventId::InputLanguageChange )
        {
            // #i55929# Font and font size state depends on input language if nothing is selected,
            // so the slots have to be invalidated when the input language is changed.
 
            SfxViewFrame* pViewFrm = SfxViewFrame::Current();
            if (pViewFrm)
            {
                SfxBindings& rBindings = pViewFrm->GetBindings();
                rBindings.Invalidate( SID_ATTR_CHAR_FONT );
                rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT );
            }
        }
        else if ( nCommand == CommandEventId::ContextMenu )
        {
            bConsumed = true;
            SfxViewFrame* pViewFrm = SfxViewFrame::Current();
            if (pViewFrm)
            {
                Point aPos = rCEvt.GetMousePosPixel();
                if (!rCEvt.IsMouseEvent())
                {
                    Size aSize = GetOutputSizePixel();
                    aPos = Point(aSize.Width() / 2, aSize.Height() / 2);
                }
                if (IsMouseCaptured())
                    ReleaseMouse();
                UpdateFocus();
                pViewFrm->GetDispatcher()->ExecutePopup(u"formulabar"_ustr, &mrGroupBar.GetVclParent(), &aPos);
            }
        }
        else if ( nCommand == CommandEventId::Wheel )
        {
            //don't call InputChanged for CommandEventId::Wheel
        }
        else if ( nCommand == CommandEventId::GestureSwipe )
        {
            //don't call InputChanged for CommandEventId::GestureSwipe
        }
        else if ( nCommand == CommandEventId::GestureLongPress )
        {
            //don't call InputChanged for CommandEventId::GestureLongPress
        }
        else if ( nCommand == CommandEventId::ModKeyChange )
        {
            //pass alt press/release to parent impl
        }
        else
            SC_MOD()->InputChanged( m_xEditView.get() );
    }
 
    if ( comphelper::LibreOfficeKit::isActive() && nCommand == CommandEventId::CursorPos )
    {
        // LOK uses this to setup caret position because drawingarea is replaced
        // with text input field, it sends logical caret position (start, end) not pixels
 
        StartEditEngine();
        TextGrabFocus();
 
        if (!m_xEditView)
            return true;
 
        // if we focus input after "Accept Formula" command, we need to notify to get it working
        SC_MOD()->InputChanged(m_xEditView.get());
 
        // information about paragraph is in additional data
        // information about position in a paragraph in a Mouse Pos
        // see vcl/jsdialog/executor.cxx "textselection" event
        const Point* pParaPoint = static_cast<const Point*>(rCEvt.GetEventData());
        Point aSelectionStartEnd = rCEvt.GetMousePosPixel();
 
        sal_Int32 nParaStart, nParaEnd, nPosStart, nPosEnd;
 
        nParaStart = pParaPoint ? pParaPoint->X() : 0;
        nParaEnd = pParaPoint ? pParaPoint->Y() : 0;
        nPosStart = m_xEditView->GetPosNoField(nParaStart, aSelectionStartEnd.X());
        nPosEnd = m_xEditView->GetPosNoField(nParaEnd, aSelectionStartEnd.Y());
 
        m_xEditView->SetSelection(ESelection(nParaStart, nPosStart, nParaEnd, nPosEnd));
        SC_MOD()->InputSelection( m_xEditView.get() );
 
        bConsumed = true;
    }
 
    bInputMode = false;
 
    return bConsumed;
}
 
bool ScTextWnd::StartDrag()
{
    // tdf#145248 don't start a drag if actively selecting
    if (m_xEditView && !m_xEditEngine->IsInSelectionMode())
    {
        OUString sSelection = m_xEditView->GetSelected();
        m_xHelper->SetData(sSelection);
        return sSelection.isEmpty();
    }
    return true;
}
 
bool ScTextWnd::KeyInput(const KeyEvent& rKEvt)
{
    bool bUsed = true;
    bInputMode = true;
    if (!SC_MOD()->InputKeyEvent( rKEvt ))
    {
        bUsed = false;
        ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
        if ( pViewSh )
            bUsed = pViewSh->SfxKeyInput(rKEvt); // Only accelerators, no input
    }
    bInputMode = false;
    return bUsed;
}
 
void ScTextWnd::GetFocus()
{
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if ( pViewSh )
        pViewSh->SetFormShellAtTop( false ); // focus in input line -> FormShell no longer on top
    WeldEditView::GetFocus();
}
 
void ScTextWnd::SetFormulaMode( bool bSet )
{
    if ( bSet != bFormulaMode )
    {
        bFormulaMode = bSet;
        UpdateAutoCorrFlag();
    }
}
 
void ScTextWnd::UpdateAutoCorrFlag()
{
    if (m_xEditEngine)
    {
        EEControlBits nControl = m_xEditEngine->GetControlWord();
        EEControlBits nOld = nControl;
        if ( bFormulaMode )
            nControl &= ~EEControlBits::AUTOCORRECT; // No AutoCorrect in Formulas
        else
            nControl |= EEControlBits::AUTOCORRECT; // Else do enable it
 
        if ( nControl != nOld )
            m_xEditEngine->SetControlWord( nControl );
    }
}
 
void ScTextWnd::EditViewScrollStateChange()
{
    // editengine height has changed or editview scroll pos has changed
    SetScrollBarRange();
}
 
IMPL_LINK_NOARG(ScTextWnd, ModifyHdl, LinkParamNone*, void)
{
    if (m_xEditView && !bInputMode)
    {
        ScInputHandler* pHdl = SC_MOD()->GetInputHdl();
 
        //  Use the InputHandler's InOwnChange flag to prevent calling InputChanged
        //  while an InputHandler method is modifying the EditEngine content
 
        if ( pHdl && !pHdl->IsInOwnChange() )
            pHdl->InputChanged( m_xEditView.get(), true );  // #i20282# InputChanged must know if called from modify handler
    }
}
 
IMPL_LINK_NOARG(ScTextWnd, EditStatusHdl, EditStatus&, void)
{
    SetScrollBarRange();
    DoScroll();
    Invalidate();
}
 
void ScTextWnd::StopEditEngine( bool bAll )
{
    if (!m_xEditEngine)
        return;
 
    if (m_xEditView)
    {
        if (!maAccTextDatas.empty())
            maAccTextDatas.back()->EndEdit();
 
        ScModule* pScMod = SC_MOD();
 
        if (!bAll)
            pScMod->InputSelection( m_xEditView.get() );
        aString = m_xEditEngine->GetText();
        bIsInsertMode = m_xEditView->IsInsertMode();
        bool bSelection = m_xEditView->HasSelection();
        m_xEditEngine->SetStatusEventHdl(Link<EditStatus&, void>());
        m_xEditEngine->SetModifyHdl(Link<LinkParamNone*,void>());
        m_xEditView.reset();
        m_xEditEngine.reset();
 
        ScInputHandler* pHdl = mpViewShell->GetInputHandler();
 
        if (pHdl && pHdl->IsEditMode() && !bAll)
            pHdl->SetMode(SC_INPUT_TABLE);
 
        SfxViewFrame* pViewFrm = SfxViewFrame::Current();
        if (pViewFrm)
            pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
 
        if (bSelection)
            Invalidate(); // So that the Selection is not left there
    }
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        // Clear
        std::vector<ReferenceMark> aReferenceMarks;
        ScInputHandler::SendReferenceMarks( mpViewShell, aReferenceMarks );
    }
}
 
static sal_Int32 findFirstNonMatchingChar(const OUString& rStr1, const OUString& rStr2)
{
    // Search the string for unmatching chars
    const sal_Unicode*  pStr1 = rStr1.getStr();
    const sal_Unicode*  pStr2 = rStr2.getStr();
    sal_Int32      i = 0;
    while ( i < rStr1.getLength() )
    {
        // Abort on the first unmatching char
        if ( *pStr1 != *pStr2 )
            return i;
        ++pStr1;
        ++pStr2;
        ++i;
    }
 
    return i;
}
 
void ScTextWnd::SetTextString( const OUString& rNewString, bool bKitUpdate )
{
    // Ideally it would be best to create on demand the EditEngine/EditView here, but... for
    // the initialisation scenario where a cell is first clicked on we end up with the text in the
    // inputbar window scrolled to the bottom if we do that here ( because the tableview and topview
    // are synced I guess ).
    // should fix that I suppose :-/ need to look a bit further into that
    mbInvalidate = true; // ensure next Paint ( that uses editengine ) call will call Invalidate first
 
    if ( rNewString != aString )
    {
        bInputMode = true;
 
        // Find position of the change, only paint the rest
        if (!m_xEditEngine)
        {
            bool bPaintAll = GetNumLines() > 1 || bIsRTL;
            if (!bPaintAll)
            {
                //  test if CTL script type is involved
                SvtScriptType nOldScript = SvtScriptType::NONE;
                SvtScriptType nNewScript = SvtScriptType::NONE;
                SfxObjectShell* pObjSh = SfxObjectShell::Current();
                if ( auto pDocShell = dynamic_cast<ScDocShell*>( pObjSh) )
                {
                    //  any document can be used (used only for its break iterator)
                    ScDocument& rDoc = pDocShell->GetDocument();
                    nOldScript = rDoc.GetStringScriptType( aString );
                    nNewScript = rDoc.GetStringScriptType( rNewString );
                }
                bPaintAll = ( nOldScript & SvtScriptType::COMPLEX ) || ( nNewScript & SvtScriptType::COMPLEX );
            }
 
            if ( bPaintAll )
            {
                // In multiline mode, or if CTL is involved, the whole text has to be redrawn
                Invalidate();
            }
            else
            {
                tools::Long nTextSize = 0;
                sal_Int32 nDifPos;
                if (rNewString.getLength() > aString.getLength())
                    nDifPos = findFirstNonMatchingChar(rNewString, aString);
                else
                    nDifPos = findFirstNonMatchingChar(aString, rNewString);
 
                tools::Long nSize1 = GetTextWidth(aString);
                tools::Long nSize2 = GetTextWidth(rNewString);
                if ( nSize1>0 && nSize2>0 )
                    nTextSize = std::max( nSize1, nSize2 );
                else
                    nTextSize = GetOutputSizePixel().Width(); // Overflow
 
                Point aLogicStart = GetDrawingArea()->get_ref_device().PixelToLogic(Point(0,0));
                tools::Long nStartPos = aLogicStart.X();
                tools::Long nInvPos = nStartPos;
                if (nDifPos)
                    nInvPos += GetTextWidth(aString.copy(0,nDifPos));
 
                Invalidate(tools::Rectangle(nInvPos, 0, nStartPos+nTextSize, GetOutputSizePixel().Height() - 1));
            }
        }
        else
        {
            static_cast<ScEditEngineDefaulter*>(m_xEditEngine.get())->SetTextCurrentDefaults(rNewString);
        }
 
        aString = rNewString;
 
        if (!maAccTextDatas.empty())
            maAccTextDatas.back()->TextChanged();
 
        bInputMode = false;
    }
 
    if (ScTabViewShell* pActiveViewShell = bKitUpdate && comphelper::LibreOfficeKit::isActive() ?
            dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()) : nullptr)
    {
        ESelection aSel = m_xEditView ? m_xEditView->GetSelection() : ESelection();
        pActiveViewShell->LOKSendFormulabarUpdate(m_xEditView.get(), rNewString, aSel);
    }
 
    SetScrollBarRange();
    DoScroll();
}
 
const OUString& ScTextWnd::GetTextString() const
{
    return aString;
}
 
bool ScTextWnd::IsInputActive()
{
    return HasFocus();
}
 
void ScTextWnd::MakeDialogEditView()
{
    if ( m_xEditView ) return;
 
    std::unique_ptr<ScFieldEditEngine> pNew;
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if ( pViewSh )
    {
        ScDocument& rDoc = pViewSh->GetViewData().GetDocument();
        pNew = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
    }
    else
        pNew = std::make_unique<ScFieldEditEngine>(nullptr, EditEngine::CreatePool().get(), nullptr, true);
    pNew->SetExecuteURL( false );
    m_xEditEngine = std::move(pNew);
 
    const bool bPrevUpdateLayout = m_xEditEngine->SetUpdateLayout( false );
    m_xEditEngine->SetWordDelimiters( m_xEditEngine->GetWordDelimiters() + "=" );
    m_xEditEngine->SetPaperSize( Size( bIsRTL ? USHRT_MAX : THESIZE, 300 ) );
 
    auto pSet = std::make_unique<SfxItemSet>( m_xEditEngine->GetEmptyItemSet() );
    EditEngine::SetFontInfoInItemSet( *pSet, aTextFont );
    lcl_ExtendEditFontAttribs( *pSet );
    if ( bIsRTL )
        lcl_ModifyRTLDefaults( *pSet );
    static_cast<ScEditEngineDefaulter*>(m_xEditEngine.get())->SetDefaults( std::move(pSet) );
    m_xEditEngine->SetUpdateLayout( bPrevUpdateLayout );
 
    m_xEditView = std::make_unique<EditView>(m_xEditEngine.get(), nullptr);
    m_xEditView->setEditViewCallbacks(this);
 
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    Color aBgColor = rStyleSettings.GetFieldColor();
    m_xEditView->SetBackgroundColor(aBgColor);
 
    if (pAcc)
    {
        pAcc->InitAcc(nullptr, m_xEditView.get(),
                      ScResId(STR_ACC_EDITLINE_NAME),
                      ScResId(STR_ACC_EDITLINE_DESCR));
    }
 
    if (comphelper::LibreOfficeKit::isActive())
        m_xEditView->RegisterViewShell(mpViewShell);
    m_xEditEngine->InsertView( m_xEditView.get(), EE_APPEND );
 
    Resize();
 
    if ( bIsRTL )
        lcl_ModifyRTLVisArea( m_xEditView.get() );
 
    if (!maAccTextDatas.empty())
        maAccTextDatas.back()->StartEdit();
}
 
void ScTextWnd::ImplInitSettings()
{
    bIsRTL = AllSettings::GetLayoutRTL();
 
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
 
    Color aBgColor= rStyleSettings.GetFieldColor();
    Color aTxtColor= rStyleSettings.GetWindowTextColor();
 
    aTextFont.SetFillColor   ( aBgColor );
    aTextFont.SetColor       (aTxtColor);
    Invalidate();
}
 
void ScTextWnd::SetDrawingArea(weld::DrawingArea* pDrawingArea)
{
    // bypass WeldEditView::SetDrawingArea
    weld::CustomWidgetController::SetDrawingArea(pDrawingArea);
 
    // set cursor
    pDrawingArea->set_cursor(PointerStyle::Text);
 
    // initialize dnd, deliberately just a simple string so
    // we don't transfer the happenstance formatting in
    // the input line
    m_xHelper.set(new svt::OStringTransferable(OUString()));
    rtl::Reference<TransferDataContainer> xHelper(m_xHelper);
    SetDragDataTransferable(xHelper, DND_ACTION_COPY);
 
    OutputDevice& rDevice = pDrawingArea->get_ref_device();
    pDrawingArea->set_margin_start(gnBorderWidth);
    pDrawingArea->set_margin_end(gnBorderWidth);
    // leave 1 for the width of the scrolledwindow border
    pDrawingArea->set_margin_top(gnBorderHeight - 1);
    pDrawingArea->set_margin_bottom(gnBorderHeight - 1);
 
    // always use application font, so a font with cjk chars can be installed
    vcl::Font aAppFont = Application::GetSettings().GetStyleSettings().GetAppFont();
    weld::SetPointFont(rDevice, aAppFont);
 
    aTextFont = rDevice.GetFont();
    Size aFontSize = aTextFont.GetFontSize();
    aTextFont.SetFontSize(rDevice.PixelToLogic(aFontSize, MapMode(MapUnit::MapTwip)));
 
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
 
    Color aBgColor = rStyleSettings.GetFieldColor();
    Color aTxtColor = rStyleSettings.GetWindowTextColor();
 
    aTextFont.SetTransparent(true);
    aTextFont.SetFillColor(aBgColor);
    aTextFont.SetColor(aTxtColor);
    aTextFont.SetWeight(WEIGHT_NORMAL);
 
    Size aSize(1, GetPixelHeightForLines(1));
    pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
 
    rDevice.SetBackground(aBgColor);
    rDevice.SetLineColor(COL_BLACK);
    rDevice.SetMapMode(MapMode(MapUnit::MapTwip));
    rDevice.SetFont(aTextFont);
 
    EnableRTL(false); // EditEngine can't be used with VCL EnableRTL
}
 
css::uno::Reference< css::accessibility::XAccessible > ScTextWnd::CreateAccessible()
{
    pAcc = new ScAccessibleEditLineObject(this);
    return pAcc;
}
 
void ScTextWnd::InsertAccessibleTextData( ScAccessibleEditLineTextData& rTextData )
{
    OSL_ENSURE( ::std::find( maAccTextDatas.begin(), maAccTextDatas.end(), &rTextData ) == maAccTextDatas.end(),
        "ScTextWnd::InsertAccessibleTextData - passed object already registered" );
    maAccTextDatas.push_back( &rTextData );
}
 
void ScTextWnd::RemoveAccessibleTextData( ScAccessibleEditLineTextData& rTextData )
{
    AccTextDataVector::iterator aEnd = maAccTextDatas.end();
    AccTextDataVector::iterator aIt = ::std::find( maAccTextDatas.begin(), aEnd, &rTextData );
    OSL_ENSURE( aIt != aEnd, "ScTextWnd::RemoveAccessibleTextData - passed object not registered" );
    if( aIt != aEnd )
        maAccTextDatas.erase( aIt );
}
 
void ScTextWnd::StyleUpdated()
{
    ImplInitSettings();
    CustomWidgetController::Invalidate();
}
 
void ScTextWnd::TextGrabFocus()
{
    GrabFocus();
}
 
// Position window
ScPosWnd::ScPosWnd(vcl::Window* pParent)
    : InterimItemWindow(pParent, u"modules/scalc/ui/posbox.ui"_ustr, u"PosBox"_ustr)
    , m_xWidget(m_xBuilder->weld_combo_box(u"pos_window"_ustr))
    , m_nAsyncGetFocusId(nullptr)
    , nTipVisible(nullptr)
    , bFormulaMode(false)
{
    InitControlBase(m_xWidget.get());
 
    // Use calculation according to tdf#132338 to align combobox width to width of fontname combobox within formatting toolbar;
    // formatting toolbar is placed above formulabar when using multiple toolbars typically
 
    m_xWidget->set_entry_width_chars(1);
    Size aSize(LogicToPixel(Size(POSITION_COMBOBOX_WIDTH * 4, 0), MapMode(MapUnit::MapAppFont)));
    m_xWidget->set_size_request(aSize.Width(), -1);
    SetSizePixel(m_xContainer->get_preferred_size());
 
    FillRangeNames();
 
    StartListening( *SfxGetpApp() ); // For Navigator rangename updates
 
    m_xWidget->connect_key_press(LINK(this, ScPosWnd, KeyInputHdl));
    m_xWidget->connect_entry_activate(LINK(this, ScPosWnd, ActivateHdl));
    m_xWidget->connect_changed(LINK(this, ScPosWnd, ModifyHdl));
    m_xWidget->connect_focus_in(LINK(this, ScPosWnd, FocusInHdl));
    m_xWidget->connect_focus_out(LINK(this, ScPosWnd, FocusOutHdl));
}
 
ScPosWnd::~ScPosWnd()
{
    disposeOnce();
}
 
void ScPosWnd::dispose()
{
    EndListening( *SfxGetpApp() );
 
    HideTip();
 
    if (m_nAsyncGetFocusId)
    {
        Application::RemoveUserEvent(m_nAsyncGetFocusId);
        m_nAsyncGetFocusId = nullptr;
    }
    m_xWidget.reset();
 
    InterimItemWindow::dispose();
}
 
void ScPosWnd::SetFormulaMode( bool bSet )
{
    if ( bSet != bFormulaMode )
    {
        bFormulaMode = bSet;
 
        if ( bSet )
            FillFunctions();
        else
            FillRangeNames();
 
        HideTip();
    }
}
 
void ScPosWnd::SetPos( const OUString& rPosStr )
{
    if ( aPosStr != rPosStr )
    {
        aPosStr = rPosStr;
        m_xWidget->set_entry_text(aPosStr);
    }
}
 
// static
OUString ScPosWnd::createLocalRangeName(std::u16string_view rName, std::u16string_view rTableName)
{
    return OUString::Concat(rName) + " (" + rTableName + ")";
}
 
void ScPosWnd::FillRangeNames()
{
    m_xWidget->clear();
    m_xWidget->freeze();
 
    SfxObjectShell* pObjSh = SfxObjectShell::Current();
    if ( auto pDocShell = dynamic_cast<ScDocShell*>( pObjSh) )
    {
        ScDocument& rDoc = pDocShell->GetDocument();
 
        m_xWidget->append_text(ScResId(STR_MANAGE_NAMES));
        m_xWidget->append_separator(u"separator"_ustr);
 
        ScRange aDummy;
        std::set<OUString> aSet;
        ScRangeName* pRangeNames = rDoc.GetRangeName();
        for (const auto& rEntry : *pRangeNames)
        {
            if (rEntry.second->IsValidReference(aDummy))
                aSet.insert(rEntry.second->GetName());
        }
        for (SCTAB i = 0; i < rDoc.GetTableCount(); ++i)
        {
            ScRangeName* pLocalRangeName = rDoc.GetRangeName(i);
            if (pLocalRangeName && !pLocalRangeName->empty())
            {
                OUString aTableName;
                rDoc.GetName(i, aTableName);
                for (const auto& rEntry : *pLocalRangeName)
                {
                    if (rEntry.second->IsValidReference(aDummy))
                        aSet.insert(createLocalRangeName(rEntry.second->GetName(), aTableName));
                }
            }
        }
 
        for (const auto& rItem : aSet)
        {
            m_xWidget->append_text(rItem);
        }
    }
    m_xWidget->thaw();
    m_xWidget->set_entry_text(aPosStr);
}
 
void ScPosWnd::FillFunctions()
{
    m_xWidget->clear();
    m_xWidget->freeze();
 
    OUString aFirstName;
    const ScAppOptions& rOpt = SC_MOD()->GetAppOptions();
    sal_uInt16 nMRUCount = rOpt.GetLRUFuncListCount();
    const sal_uInt16* pMRUList = rOpt.GetLRUFuncList();
    if (pMRUList)
    {
        const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList();
        sal_uInt32 nListCount = pFuncList->GetCount();
        for (sal_uInt16 i=0; i<nMRUCount; i++)
        {
            sal_uInt16 nId = pMRUList[i];
            for (sal_uInt32 j=0; j<nListCount; j++)
            {
                const ScFuncDesc* pDesc = pFuncList->GetFunction( j );
                if ( pDesc->nFIndex == nId && pDesc->mxFuncName )
                {
                    m_xWidget->append_text(*pDesc->mxFuncName);
                    if (aFirstName.isEmpty())
                        aFirstName = *pDesc->mxFuncName;
                    break; // Stop searching
                }
            }
        }
    }
 
    //! Re-add entry "Other..." for Function AutoPilot if it can work with text that
    // has been entered so far
 
    //  m_xWidget->append_text(ScResId(STR_FUNCTIONLIST_MORE));
 
    m_xWidget->thaw();
    m_xWidget->set_entry_text(aFirstName);
}
 
void ScPosWnd::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    if ( bFormulaMode )
        return;
 
    const SfxHintId nHintId = rHint.GetId();
    // Does the list of range names need updating?
    if (nHintId == SfxHintId::ThisIsAnSfxEventHint)
    {
        SfxEventHintId nEventId = static_cast<const SfxEventHint&>(rHint).GetEventId();
        if ( nEventId == SfxEventHintId::ActivateDoc )
            FillRangeNames();
    }
    else
    {
        if (nHintId == SfxHintId::ScAreasChanged || nHintId == SfxHintId::ScNavigatorUpdateAll)
            FillRangeNames();
    }
}
 
void ScPosWnd::HideTip()
{
    if (nTipVisible)
    {
        Help::HidePopover(this, nTipVisible);
        nTipVisible = nullptr;
    }
}
 
static ScNameInputType lcl_GetInputType( const OUString& rText )
{
    ScNameInputType eRet = SC_NAME_INPUT_BAD_NAME;      // the more general error
 
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if ( pViewSh )
    {
        ScViewData& rViewData = pViewSh->GetViewData();
        ScDocument& rDoc = rViewData.GetDocument();
        SCTAB nTab = rViewData.GetTabNo();
        ScAddress::Details aDetails( rDoc.GetAddressConvention());
 
        // test in same order as in SID_CURRENTCELL execute
 
        ScRange aRange;
        ScAddress aAddress;
        SCTAB nNameTab;
        sal_Int32 nNumeric;
 
        // From the context we know that when testing for a range name
        // sheet-local scope names have " (sheetname)" appended and global
        // names don't and can't contain ')', so we can force one or the other.
        const RutlNameScope eNameScope =
            ((!rText.isEmpty() && rText[rText.getLength()-1] == ')') ? RUTL_NAMES_LOCAL : RUTL_NAMES_GLOBAL);
 
        if (rText == ScResId(STR_MANAGE_NAMES))
            eRet = SC_MANAGE_NAMES;
        else if ( aRange.Parse( rText, rDoc, aDetails ) & ScRefFlags::VALID )
            eRet = SC_NAME_INPUT_RANGE;
        else if ( aAddress.Parse( rText, rDoc, aDetails ) & ScRefFlags::VALID )
            eRet = SC_NAME_INPUT_CELL;
        else if ( ScRangeUtil::MakeRangeFromName( rText, rDoc, nTab, aRange, eNameScope, aDetails ) )
        {
            eRet = ((eNameScope == RUTL_NAMES_LOCAL) ? SC_NAME_INPUT_NAMEDRANGE_LOCAL :
                    SC_NAME_INPUT_NAMEDRANGE_GLOBAL);
        }
        else if ( ScRangeUtil::MakeRangeFromName( rText, rDoc, nTab, aRange, RUTL_DBASE, aDetails ) )
            eRet = SC_NAME_INPUT_DATABASE;
        else if ( comphelper::string::isdigitAsciiString( rText ) &&
                  ( nNumeric = rText.toInt32() ) > 0 && nNumeric <= rDoc.MaxRow()+1 )
            eRet = SC_NAME_INPUT_ROW;
        else if ( rDoc.GetTable( rText, nNameTab ) )
            eRet = SC_NAME_INPUT_SHEET;
        else if (ScRangeData::IsNameValid(rText, rDoc)
                 == ScRangeData::IsNameValidType::NAME_VALID) // nothing found, create new range?
        {
            if ( rViewData.GetSimpleArea( aRange ) == SC_MARK_SIMPLE )
                eRet = SC_NAME_INPUT_DEFINE;
            else
                eRet = SC_NAME_INPUT_BAD_SELECTION;
        }
        else
            eRet = SC_NAME_INPUT_BAD_NAME;
    }
 
    return eRet;
}
 
IMPL_LINK_NOARG(ScPosWnd, ModifyHdl, weld::ComboBox&, void)
{
    HideTip();
 
    if (m_xWidget->changed_by_direct_pick())
    {
        DoEnter();
        return;
    }
 
    if (bFormulaMode)
        return;
 
    // determine the action that would be taken for the current input
 
    ScNameInputType eType = lcl_GetInputType(m_xWidget->get_active_text());      // uses current view
    TranslateId pStrId;
    switch ( eType )
    {
        case SC_NAME_INPUT_CELL:
            pStrId = STR_NAME_INPUT_CELL;
            break;
        case SC_NAME_INPUT_RANGE:
        case SC_NAME_INPUT_NAMEDRANGE_LOCAL:
        case SC_NAME_INPUT_NAMEDRANGE_GLOBAL:
            pStrId = STR_NAME_INPUT_RANGE;      // named range or range reference
            break;
        case SC_NAME_INPUT_DATABASE:
            pStrId = STR_NAME_INPUT_DBRANGE;
            break;
        case SC_NAME_INPUT_ROW:
            pStrId = STR_NAME_INPUT_ROW;
            break;
        case SC_NAME_INPUT_SHEET:
            pStrId = STR_NAME_INPUT_SHEET;
            break;
        case SC_NAME_INPUT_DEFINE:
            pStrId = STR_NAME_INPUT_DEFINE;
            break;
        default:
            // other cases (error): no tip help
            break;
    }
 
    if (!pStrId)
        return;
 
    // show the help tip at the text cursor position
    Point aPos;
    vcl::Cursor* pCur = GetCursor();
    if (pCur)
        aPos = LogicToPixel( pCur->GetPos() );
    aPos = OutputToScreenPixel( aPos );
    tools::Rectangle aRect( aPos, aPos );
 
    OUString aText = ScResId(pStrId);
    QuickHelpFlags const nAlign = QuickHelpFlags::Left|QuickHelpFlags::Bottom;
    nTipVisible = Help::ShowPopover(this, aRect, aText, nAlign);
}
 
void ScPosWnd::DoEnter()
{
    bool bOpenManageNamesDialog = false;
    OUString aText = m_xWidget->get_active_text();
    if ( !aText.isEmpty() )
    {
        if ( bFormulaMode )
        {
            ScModule* pScMod = SC_MOD();
            if ( aText == ScResId(STR_FUNCTIONLIST_MORE) )
            {
                // Function AutoPilot
                //! Continue working with the text entered so far
 
                //! new method at ScModule to query if function autopilot is open
                SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                if ( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) )
                    pViewFrm->GetDispatcher()->Execute( SID_OPENDLG_FUNCTION,
                                              SfxCallMode::SYNCHRON | SfxCallMode::RECORD );
            }
            else
            {
                ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current()  );
                ScInputHandler* pHdl = pScMod->GetInputHdl( pViewSh );
                if (pHdl)
                    pHdl->InsertFunction( aText );
            }
        }
        else
        {
            // depending on the input, select something or create a new named range
 
            ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
            if ( pViewSh )
            {
                ScViewData& rViewData = pViewSh->GetViewData();
                ScDocShell* pDocShell = rViewData.GetDocShell();
                ScDocument& rDoc = pDocShell->GetDocument();
 
                ScNameInputType eType = lcl_GetInputType( aText );
                if ( eType == SC_NAME_INPUT_BAD_NAME || eType == SC_NAME_INPUT_BAD_SELECTION )
                {
                    TranslateId pId = (eType == SC_NAME_INPUT_BAD_NAME) ? STR_NAME_ERROR_NAME : STR_NAME_ERROR_SELECTION;
                    pViewSh->ErrorMessage(pId);
                }
                else if ( eType == SC_NAME_INPUT_DEFINE )
                {
                    ScRangeName* pNames = rDoc.GetRangeName();
                    ScRange aSelection;
                    if ( pNames && !pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aText)) &&
                            (rViewData.GetSimpleArea( aSelection ) == SC_MARK_SIMPLE) )
                    {
                        ScRangeName aNewRanges( *pNames );
                        ScAddress aCursor( rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo() );
                        OUString aContent(aSelection.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention()));
                        ScRangeData* pNew = new ScRangeData( rDoc, aText, aContent, aCursor );
                        if ( aNewRanges.insert(pNew) )
                        {
                            pDocShell->GetDocFunc().ModifyRangeNames( aNewRanges );
                            pViewSh->UpdateInputHandler(true);
                        }
                    }
                }
                else if (eType == SC_MANAGE_NAMES)
                {
                    // dialog is only set below after calling 'ReleaseFocus_Impl' to ensure it gets focus
                    bOpenManageNamesDialog = true;
                }
                else
                {
                    bool bForceGlobalName = false;
                    // for all selection types, execute the SID_CURRENTCELL slot.
                    if (eType == SC_NAME_INPUT_CELL || eType == SC_NAME_INPUT_RANGE)
                    {
                        // Note that SID_CURRENTCELL always expects address to
                        // be in Calc A1 format.  Convert the text.
                        ScRange aRange(0,0, rViewData.GetTabNo());
                        aRange.ParseAny(aText, rDoc, rDoc.GetAddressConvention());
                        aText = aRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, ::formula::FormulaGrammar::CONV_OOO);
                    }
                    else if (eType == SC_NAME_INPUT_NAMEDRANGE_GLOBAL)
                    {
                        bForceGlobalName = true;
                    }
 
                    SfxStringItem aPosItem( SID_CURRENTCELL, aText );
                    SfxBoolItem aUnmarkItem( FN_PARAM_1, true );        // remove existing selection
                    // FN_PARAM_2 reserved for AlignToCursor
                    SfxBoolItem aForceGlobalName( FN_PARAM_3, bForceGlobalName );
 
                    pViewSh->GetViewData().GetDispatcher().ExecuteList( SID_CURRENTCELL,
                                        SfxCallMode::SYNCHRON | SfxCallMode::RECORD,
                                        { &aPosItem, &aUnmarkItem, &aForceGlobalName });
                }
            }
        }
    }
    else
        m_xWidget->set_entry_text(aPosStr);
 
    ReleaseFocus_Impl();
 
    if (bOpenManageNamesDialog)
    {
        const sal_uInt16 nId  = ScNameDlgWrapper::GetChildWindowId();
        if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
        {
            SfxViewFrame& rViewFrm = pViewSh->GetViewFrame();
            SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId );
            SC_MOD()->SetRefDialog( nId, pWnd == nullptr );
        }
    }
}
 
IMPL_LINK_NOARG(ScPosWnd, ActivateHdl, weld::ComboBox&, bool)
{
    DoEnter();
    return true;
}
 
IMPL_LINK(ScPosWnd, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = true;
 
    switch (rKEvt.GetKeyCode().GetCode())
    {
        case KEY_RETURN:
            bHandled = ActivateHdl(*m_xWidget);
            break;
        case KEY_ESCAPE:
            if (nTipVisible)
            {
                // escape when the tip help is shown: only hide the tip
                HideTip();
            }
            else
            {
                if (!bFormulaMode)
                    m_xWidget->set_entry_text(aPosStr);
                ReleaseFocus_Impl();
            }
            break;
        default:
            bHandled = false;
            break;
    }
 
    return bHandled || ChildKeyInput(rKEvt);
}
 
IMPL_LINK_NOARG(ScPosWnd, OnAsyncGetFocus, void*, void)
{
    m_nAsyncGetFocusId = nullptr;
    m_xWidget->select_entry_region(0, -1);
}
 
IMPL_LINK_NOARG(ScPosWnd, FocusInHdl, weld::Widget&, void)
{
    if (m_nAsyncGetFocusId)
        return;
    // do it async to defeat entry in combobox having its own ideas about the focus
    m_nAsyncGetFocusId = Application::PostUserEvent(LINK(this, ScPosWnd, OnAsyncGetFocus));
}
 
IMPL_LINK_NOARG(ScPosWnd, FocusOutHdl, weld::Widget&, void)
{
    if (m_nAsyncGetFocusId)
    {
        Application::RemoveUserEvent(m_nAsyncGetFocusId);
        m_nAsyncGetFocusId = nullptr;
    }
 
    HideTip();
}
 
void ScPosWnd::ReleaseFocus_Impl()
{
    HideTip();
 
    SfxViewShell* pCurSh = SfxViewShell::Current();
    ScInputHandler* pHdl = SC_MOD()->GetInputHdl( dynamic_cast<ScTabViewShell*>( pCurSh )  );
    if ( pHdl && pHdl->IsTopMode() )
    {
        // Focus back in input row?
        ScInputWindow* pInputWin = pHdl->GetInputWindow();
        if (pInputWin)
        {
            pInputWin->TextGrabFocus();
            return;
        }
    }
 
    // Set focus to active View
    if ( pCurSh )
    {
        vcl::Window* pShellWnd = pCurSh->GetWindow();
 
        if ( pShellWnd )
            pShellWnd->GrabFocus();
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

V1053 Calling the 'GetSizePixel' virtual function in the constructor may lead to unexpected result at runtime.

V1053 Calling the 'SetSizePixel' virtual function in the constructor may lead to unexpected result at runtime.

V699 Consider inspecting the 'foo = bar = baz ? .... : ....' expression. It is possible that 'foo = bar == baz ? .... : ....' should be used here instead.

V1048 The 'eCode' variable was assigned the same value.

V1048 The 'eRet' variable was assigned the same value.

V1048 The 'bHandled' variable was assigned the same value.