/* -*- 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 <inputhdl.hxx>
#include <scitems.hxx>
#include <editeng/eeitem.hxx>
 
#include <sfx2/app.hxx>
#include <editeng/acorrcfg.hxx>
#include <formula/errorcodes.hxx>
#include <svx/algitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/brushitem.hxx>
#include <svtools/colorcfg.hxx>
#include <editeng/colritem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/editstat.hxx>
#include <editeng/editview.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/forbiddencharacterstable.hxx>
#include <editeng/langitem.hxx>
#include <editeng/svxacorr.hxx>
#include <editeng/unolingu.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/justifyitem.hxx>
#include <editeng/misspellrange.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/printer.hxx>
#include <svl/zforlist.hxx>
#include <unotools/localedatawrapper.hxx>
#include <vcl/help.hxx>
#include <vcl/cursor.hxx>
#include <vcl/settings.hxx>
#include <tools/urlobj.hxx>
#include <formula/formulahelper.hxx>
#include <formula/funcvarargs.h>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <o3tl/make_unique.hxx>
 
#include <inputwin.hxx>
#include <tabvwsh.hxx>
#include <docsh.hxx>
#include <scmod.hxx>
#include <uiitems.hxx>
#include <global.hxx>
#include <sc.hrc>
#include <globstr.hrc>
#include <scresid.hxx>
#include <patattr.hxx>
#include <viewdata.hxx>
#include <document.hxx>
#include <docpool.hxx>
#include <editutil.hxx>
#include <appoptio.hxx>
#include <docoptio.hxx>
#include <validat.hxx>
#include <userlist.hxx>
#include <rfindlst.hxx>
#include <inputopt.hxx>
#include <simpleformulacalc.hxx>
#include <compiler.hxx>
#include <editable.hxx>
#include <funcdesc.hxx>
#include <markdata.hxx>
#include <tokenarray.hxx>
#include <gridwin.hxx>
 
// Maximum Ranges in RangeFinder
#define RANGEFIND_MAX   64
 
using namespace formula;
 
bool ScInputHandler::bOptLoaded = false;            // Evaluate App options
bool ScInputHandler::bAutoComplete = false;         // Is set in KeyInput
 
namespace {
 
// Formula data replacement character for a pair of parentheses at end of
// function name, to force sorting parentheses before all other characters.
// Collation may treat parentheses differently.
const sal_Unicode cParenthesesReplacement = 0x0001;
 
sal_Unicode lcl_getSheetSeparator(ScDocument* pDoc)
{
    ScCompiler aComp(pDoc, ScAddress(), pDoc->GetGrammar());
    return aComp.GetNativeAddressSymbol(ScCompiler::Convention::SHEET_SEPARATOR);
}
 
ScTypedCaseStrSet::const_iterator findText(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
    const OUString& rStart, OUString& rResult, bool bBack)
{
    if (bBack) // Backwards
    {
        ScTypedCaseStrSet::const_reverse_iterator it = rDataSet.rbegin(), itEnd = rDataSet.rend();
        if (itPos != rDataSet.end())
        {
            size_t nPos = std::distance(rDataSet.begin(), itPos);
            size_t nRPos = rDataSet.size() - 1 - nPos;
            std::advance(it, nRPos);
            ++it;
        }
 
        for (; it != itEnd; ++it)
        {
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;
 
            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;
 
            rResult = rData.GetString();
            return (++it).base(); // convert the reverse iterator back to iterator.
        }
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
        if (itPos != rDataSet.end())
        {
            it = itPos;
            ++it;
        }
 
        for (; it != itEnd; ++it)
        {
            const ScTypedStrData& rData = *it;
            if (rData.GetStringType() == ScTypedStrData::Value)
                // skip values
                continue;
 
            if (!ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()))
                // not a match
                continue;
 
            rResult = rData.GetString();
            return it;
        }
    }
 
    return rDataSet.end(); // no matching text found
}
 
OUString getExactMatch(const ScTypedCaseStrSet& rDataSet, const OUString& rString)
{
    ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
    for (; it != itEnd; ++it)
    {
        const ScTypedStrData& rData = *it;
        if (rData.GetStringType() == ScTypedStrData::Value)
            continue;
 
        if (!ScGlobal::GetpTransliteration()->isEqual(rData.GetString(), rString))
            continue;
 
        return rData.GetString();
    }
    return rString;
}
 
ScTypedCaseStrSet::const_iterator findTextAll(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack)
{
    rResultVec.clear(); // clear contents
 
    size_t nCount = 0;
    ScTypedCaseStrSet::const_iterator retit;
    if ( bBack ) // Backwards
    {
        ScTypedCaseStrSet::const_reverse_iterator it, itEnd;
        if ( itPos == rDataSet.end() )
        {
            it = rDataSet.rend();
            --it;
            itEnd = it;
        }
        else
        {
            it = rDataSet.rbegin();
            size_t nPos = std::distance(rDataSet.begin(), itPos);
            size_t nRPos = rDataSet.size() - 1 - nPos; // if itPos == rDataSet.end(), then nRPos = -1
            std::advance(it, nRPos);
            if ( it == rDataSet.rend() )
                it = rDataSet.rbegin();
            itEnd = it;
        }
        bool bFirstTime = true;
 
        while ( it != itEnd || bFirstTime )
        {
            ++it;
            if ( it == rDataSet.rend() ) // go to the first if reach the end
                it = rDataSet.rbegin();
 
            if ( bFirstTime )
                bFirstTime = false;
            const ScTypedStrData& rData = *it;
            if ( rData.GetStringType() == ScTypedStrData::Value )
                // skip values
                continue;
 
            if ( !ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()) )
                // not a match
                continue;
 
            rResultVec.push_back(rData.GetString()); // set the match data
            if ( nCount == 0 ) // convert the reverse iterator back to iterator.
            {
                // actually we want to do "retit = it;".
                retit = rDataSet.begin();
                size_t nRPos = std::distance(rDataSet.rbegin(), it);
                size_t nPos = rDataSet.size() - 1 - nRPos;
                std::advance(retit, nPos);
            }
            ++nCount;
        }
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it, itEnd;
        it = itPos;
        if ( it == rDataSet.end() )
            it = rDataSet.begin();
        itEnd = it;
        bool bFirstTime = true;
 
        while ( it != itEnd || bFirstTime )
        {
            ++it;
            if ( it == rDataSet.end() ) // go to the first if reach the end
                it = rDataSet.begin();
 
            if ( bFirstTime )
                bFirstTime = false;
            const ScTypedStrData& rData = *it;
            if ( rData.GetStringType() == ScTypedStrData::Value )
                // skip values
                continue;
 
            if ( !ScGlobal::GetpTransliteration()->isMatch(rStart, rData.GetString()) )
                // not a match
                continue;
 
            rResultVec.push_back(rData.GetString()); // set the match data
            if ( nCount == 0 )
                retit = it; // remember first match iterator
            ++nCount;
        }
    }
 
    if ( nCount > 0 ) // at least one function has matched
        return retit;
    return rDataSet.end(); // no matching text found
}
 
}
 
void ScInputHandler::InitRangeFinder( const OUString& rFormula )
{
    DeleteRangeFinder();
    if ( !pActiveViewSh || !SC_MOD()->GetInputOptions().GetRangeFinder() )
        return;
    ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    const sal_Unicode cSheetSep = lcl_getSheetSeparator(&rDoc);
 
    OUString aDelimiters = ScEditUtil::ModifyDelimiters(" !~\"");
        // delimiters (in addition to ScEditUtil): only characters that are
        // allowed in formulas next to references and the quotation mark (so
        // string constants can be skipped)
 
    sal_Int32 nColon = aDelimiters.indexOf( ':' );
    if ( nColon != -1 )
        aDelimiters = aDelimiters.replaceAt( nColon, 1, ""); // Delimiter without colon
    sal_Int32 nDot = aDelimiters.indexOf(cSheetSep);
    if ( nDot != -1 )
        aDelimiters = aDelimiters.replaceAt( nDot, 1 , ""); // Delimiter without dot
 
    const sal_Unicode* pChar = rFormula.getStr();
    sal_Int32 nLen = rFormula.getLength();
    sal_Int32 nPos = 0;
    sal_Int32 nStart = 0;
    sal_uInt16 nCount = 0;
    ScRange aRange;
    while ( nPos < nLen && nCount < RANGEFIND_MAX )
    {
        // Skip separator
        while ( nPos<nLen && ScGlobal::UnicodeStrChr( aDelimiters.getStr(), pChar[nPos] ) )
        {
            if ( pChar[nPos] == '"' )                       // String
            {
                ++nPos;
                while (nPos<nLen && pChar[nPos] != '"')     // Skip until end
                    ++nPos;
            }
            ++nPos; // Separator or closing quote
        }
 
        //  Text zwischen Trennern
        nStart = nPos;
handle_r1c1:
        while ( nPos<nLen && !ScGlobal::UnicodeStrChr( aDelimiters.getStr(), pChar[nPos] ) )
            ++nPos;
 
        // for R1C1 '-' in R[-]... or C[-]... are not delimiters
        // Nothing heroic here to ensure that there are '[]' around a negative
        // integer.  we need to clean up this code.
        if( nPos < nLen && nPos > 0 &&
            '-' == pChar[nPos] && '[' == pChar[nPos-1] &&
            formula::FormulaGrammar::CONV_XL_R1C1 == rDoc.GetAddressConvention() )
        {
            nPos++;
            goto handle_r1c1;
        }
 
        if ( nPos > nStart )
        {
            OUString aTest = rFormula.copy( nStart, nPos-nStart );
            const ScAddress::Details aAddrDetails( &rDoc, aCursorPos );
            ScRefFlags nFlags = aRange.ParseAny( aTest, &rDoc, aAddrDetails );
            if ( nFlags & ScRefFlags::VALID )
            {
                //  Set tables if not specified
                if ( (nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
                    aRange.aStart.SetTab( pActiveViewSh->GetViewData().GetTabNo() );
                if ( (nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
                    aRange.aEnd.SetTab( aRange.aStart.Tab() );
 
                if ( ( nFlags & (ScRefFlags::COL2_VALID|ScRefFlags::ROW2_VALID|ScRefFlags::TAB2_VALID) ) ==
                                 ScRefFlags::ZERO )
                {
                    // #i73766# if a single ref was parsed, set the same "abs" flags for ref2,
                    // so Format doesn't output a double ref because of different flags.
                    ScRefFlags nAbsFlags = nFlags & (ScRefFlags::COL_ABS|ScRefFlags::ROW_ABS|ScRefFlags::TAB_ABS);
                    applyStartToEndFlags(nFlags, nAbsFlags);
                }
 
                if (!nCount)
                {
                    mpEditEngine->SetUpdateMode( false );
                    pRangeFindList.reset(new ScRangeFindList( pDocSh->GetTitle() ));
                }
 
                Color nColor = pRangeFindList->Insert( ScRangeFindData( aRange, nFlags, nStart, nPos ) );
 
                ESelection aSel( 0, nStart, 0, nPos );
                SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
                aSet.Put( SvxColorItem( nColor, EE_CHAR_COLOR ) );
                mpEditEngine->QuickSetAttribs( aSet, aSel );
                ++nCount;
            }
        }
 
        // Do not skip last separator; could be a quote (?)
    }
 
    if (nCount)
    {
        mpEditEngine->SetUpdateMode( true );
 
        pDocSh->Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );
    }
}
 
void ScInputHandler::SetDocumentDisposing( bool b )
{
    mbDocumentDisposing = b;
}
 
static void lcl_Replace( EditView* pView, const OUString& rNewStr, const ESelection& rOldSel )
{
    if ( pView )
    {
        ESelection aOldSel = pView->GetSelection();
        if (aOldSel.HasRange())
            pView->SetSelection( ESelection( aOldSel.nEndPara, aOldSel.nEndPos,
                                             aOldSel.nEndPara, aOldSel.nEndPos ) );
 
        EditEngine* pEngine = pView->GetEditEngine();
        pEngine->QuickInsertText( rNewStr, rOldSel );
 
        // Dummy InsertText for Update and Paint
        // To do that we need to cancel the selection from above (before QuickInsertText)
        pView->InsertText( EMPTY_OUSTRING );
 
        sal_Int32 nLen = pEngine->GetTextLen(0);
        ESelection aSel( 0, nLen, 0, nLen );
        pView->SetSelection( aSel ); // Set cursor to the end
    }
}
 
void ScInputHandler::UpdateRange( sal_uInt16 nIndex, const ScRange& rNew )
{
    ScTabViewShell* pDocView = pRefViewSh ? pRefViewSh : pActiveViewSh;
    if ( pDocView && pRangeFindList && nIndex < pRangeFindList->Count() )
    {
        ScRangeFindData& rData = pRangeFindList->GetObject( nIndex );
        sal_Int32 nOldStart = rData.nSelStart;
        sal_Int32 nOldEnd = rData.nSelEnd;
        Color nNewColor = pRangeFindList->FindColor( rNew, nIndex );
 
        ScRange aJustified = rNew;
        aJustified.PutInOrder(); // Always display Ref in the Formula the right way
        ScDocument* pDoc = pDocView->GetViewData().GetDocument();
        const ScAddress::Details aAddrDetails( pDoc, aCursorPos );
        OUString aNewStr(aJustified.Format(rData.nFlags, pDoc, aAddrDetails));
        ESelection aOldSel( 0, nOldStart, 0, nOldEnd );
        SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
 
        DataChanging();
 
        lcl_Replace( pTopView, aNewStr, aOldSel );
        lcl_Replace( pTableView, aNewStr, aOldSel );
        aSet.Put( SvxColorItem( nNewColor, EE_CHAR_COLOR ) );
        mpEditEngine->QuickSetAttribs( aSet, aOldSel );
 
        bInRangeUpdate = true;
        DataChanged();
        bInRangeUpdate = false;
 
        long nDiff = aNewStr.getLength() - static_cast<long>(nOldEnd-nOldStart);
 
        rData.aRef = rNew;
        rData.nSelEnd = rData.nSelEnd + nDiff;
        rData.nColor = nNewColor;
 
        sal_uInt16 nCount = static_cast<sal_uInt16>(pRangeFindList->Count());
        for (sal_uInt16 i=nIndex+1; i<nCount; i++)
        {
            ScRangeFindData& rNext = pRangeFindList->GetObject( i );
            rNext.nSelStart = rNext.nSelStart + nDiff;
            rNext.nSelEnd   = rNext.nSelEnd   + nDiff;
        }
 
        EditView* pActiveView = pTopView ? pTopView : pTableView;
        pActiveView->ShowCursor( false );
    }
    else
    {
        OSL_FAIL("UpdateRange: we're missing something");
    }
}
 
void ScInputHandler::DeleteRangeFinder()
{
    ScTabViewShell* pPaintView = pRefViewSh ? pRefViewSh : pActiveViewSh;
    if ( pRangeFindList && pPaintView )
    {
        ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
        pRangeFindList->SetHidden(true);
        pDocSh->Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );  // Steal
        pRangeFindList.reset();
    }
}
 
inline OUString GetEditText(const EditEngine* pEng)
{
    return ScEditUtil::GetSpaceDelimitedString(*pEng);
}
 
static void lcl_RemoveTabs(OUString& rStr)
{
    rStr = rStr.replace('\t', ' ');
}
 
static void lcl_RemoveLineEnd(OUString& rStr)
{
    rStr = convertLineEnd(rStr, LINEEND_LF);
    rStr = rStr.replace('\n', ' ');
}
 
static sal_Int32 lcl_MatchParenthesis( const OUString& rStr, sal_Int32 nPos )
{
    int nDir;
    sal_Unicode c1, c2 = 0;
    c1 = rStr[nPos];
    switch ( c1 )
    {
    case '(' :
        c2 = ')';
        nDir = 1;
        break;
    case ')' :
        c2 = '(';
        nDir = -1;
        break;
    case '<' :
        c2 = '>';
        nDir = 1;
        break;
    case '>' :
        c2 = '<';
        nDir = -1;
        break;
    case '{' :
        c2 = '}';
        nDir = 1;
        break;
    case '}' :
        c2 = '{';
        nDir = -1;
        break;
    case '[' :
        c2 = ']';
        nDir = 1;
        break;
    case ']' :
        c2 = '[';
        nDir = -1;
        break;
    default:
        nDir = 0;
    }
    if ( !nDir )
        return -1;
    sal_Int32 nLen = rStr.getLength();
    const sal_Unicode* p0 = rStr.getStr();
    const sal_Unicode* p;
    const sal_Unicode* p1;
    sal_uInt16 nQuotes = 0;
    if ( nPos < nLen / 2 )
    {
        p = p0;
        p1 = p0 + nPos;
    }
    else
    {
        p = p0 + nPos;
        p1 = p0 + nLen;
    }
    while ( p < p1 )
    {
        if ( *p++ == '\"' )
            nQuotes++;
    }
    // Odd number of quotes that we find ourselves in a string
    bool bLookInString = ((nQuotes % 2) != 0);
    bool bInString = bLookInString;
    p = p0 + nPos;
    p1 = (nDir < 0 ? p0 : p0 + nLen) ;
    sal_uInt16 nLevel = 1;
    while ( p != p1 && nLevel )
    {
        p += nDir;
        if ( *p == '\"' )
        {
            bInString = !bInString;
            if ( bLookInString && !bInString )
                p = p1; // That's it then
        }
        else if ( bInString == bLookInString )
        {
            if ( *p == c1 )
                nLevel++;
            else if ( *p == c2 )
                nLevel--;
        }
    }
    if ( nLevel )
        return -1;
    return static_cast<sal_Int32>(p - p0);
}
 
ScInputHandler::ScInputHandler()
    :   pInputWin( nullptr ),
        pTableView( nullptr ),
        pTopView( nullptr ),
        pTipVisibleParent( nullptr ),
        nTipVisible( nullptr ),
        pTipVisibleSecParent( nullptr ),
        nTipVisibleSec( nullptr ),
        nFormSelStart( 0 ),
        nFormSelEnd( 0 ),
        nAutoPar( 0 ),
        eMode( SC_INPUT_NONE ),
        bUseTab( false ),
        bTextValid( true ),
        bModified( false ),
        bSelIsRef( false ),
        bFormulaMode( false ),
        bInRangeUpdate( false ),
        bParenthesisShown( false ),
        bCreatingFuncView( false ),
        bInEnterHandler( false ),
        bCommandErrorShown( false ),
        bInOwnChange( false ),
        bProtected( false ),
        bCellHasPercentFormat( false ),
        bLastIsSymbol( false ),
        mbDocumentDisposing(false),
        nValidation( 0 ),
        eAttrAdjust( SvxCellHorJustify::Standard ),
        aScaleX( 1,1 ),
        aScaleY( 1,1 ),
        pRefViewSh( nullptr ),
        pLastPattern( nullptr ),
        maFormulaChar()
{
    //  The InputHandler is constructed with the view, so SfxViewShell::Current
    //  doesn't have the right view yet. pActiveViewSh is updated in NotifyChange.
    pActiveViewSh = nullptr;
 
    //  Bindings (only still used for Invalidate) are retrieved if needed on demand
 
    pDelayTimer.reset( new Timer( "ScInputHandlerDelay timer" ) );
    pDelayTimer->SetTimeout( 500 ); // 500 ms delay
    pDelayTimer->SetInvokeHandler( LINK( this, ScInputHandler, DelayTimer ) );
}
 
ScInputHandler::~ScInputHandler()
{
    //  If this is the application InputHandler, the dtor is called after SfxApplication::Main,
    //  thus we can't rely on any Sfx functions
    if (!mbDocumentDisposing) // inplace
        EnterHandler(); // Finish input
 
    if (SC_MOD()->GetRefInputHdl() == this)
        SC_MOD()->SetRefInputHdl(nullptr);
 
    if ( pInputWin && pInputWin->GetInputHandler() == this )
        pInputWin->SetInputHandler( nullptr );
}
 
void ScInputHandler::SetRefScale( const Fraction& rX, const Fraction& rY )
{
    if ( rX != aScaleX || rY != aScaleY )
    {
        aScaleX = rX;
        aScaleY = rY;
        if (mpEditEngine)
        {
            MapMode aMode( MapUnit::Map100thMM, Point(), aScaleX, aScaleY );
            mpEditEngine->SetRefMapMode( aMode );
        }
    }
}
 
void ScInputHandler::UpdateRefDevice()
{
    if (!mpEditEngine)
        return;
 
    bool bTextWysiwyg = SC_MOD()->GetInputOptions().GetTextWysiwyg();
    bool bInPlace = pActiveViewSh && pActiveViewSh->GetViewFrame()->GetFrame().IsInPlace();
    EEControlBits nCtrl = mpEditEngine->GetControlWord();
    if ( bTextWysiwyg || bInPlace )
        nCtrl |= EEControlBits::FORMAT100;    // EditEngine default: always format for 100%
    else
        nCtrl &= ~EEControlBits::FORMAT100;   // when formatting for screen, use the actual MapMode
    mpEditEngine->SetControlWord( nCtrl );
    if ( bTextWysiwyg && pActiveViewSh )
        mpEditEngine->SetRefDevice( pActiveViewSh->GetViewData().GetDocument()->GetPrinter() );
    else
        mpEditEngine->SetRefDevice( nullptr );
 
    MapMode aMode( MapUnit::Map100thMM, Point(), aScaleX, aScaleY );
    mpEditEngine->SetRefMapMode( aMode );
 
    //  SetRefDevice(NULL) uses VirtualDevice, SetRefMapMode forces creation of a local VDev,
    //  so the DigitLanguage can be safely modified (might use an own VDev instead of NULL).
    if ( !( bTextWysiwyg && pActiveViewSh ) )
    {
        mpEditEngine->GetRefDevice()->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() );
    }
}
 
void ScInputHandler::ImplCreateEditEngine()
{
    if ( !mpEditEngine )
    {
        if ( pActiveViewSh )
        {
            ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
            mpEditEngine = o3tl::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
        }
        else
            mpEditEngine = o3tl::make_unique<ScFieldEditEngine>(nullptr, EditEngine::CreatePool(), nullptr, true);
 
        mpEditEngine->SetWordDelimiters( ScEditUtil::ModifyDelimiters( mpEditEngine->GetWordDelimiters() ) );
        UpdateRefDevice();      // also sets MapMode
        mpEditEngine->SetPaperSize( Size( 1000000, 1000000 ) );
        pEditDefaults.reset( new SfxItemSet( mpEditEngine->GetEmptyItemSet() ) );
 
        mpEditEngine->SetControlWord( mpEditEngine->GetControlWord() | EEControlBits::AUTOCORRECT );
        mpEditEngine->SetReplaceLeadingSingleQuotationMark( false );
        mpEditEngine->SetModifyHdl( LINK( this, ScInputHandler, ModifyHdl ) );
    }
}
 
void ScInputHandler::UpdateAutoCorrFlag()
{
    EEControlBits nCntrl = mpEditEngine->GetControlWord();
    EEControlBits nOld = nCntrl;
 
    // Don't use pLastPattern here (may be invalid because of AutoStyle)
    bool bDisable = bLastIsSymbol || bFormulaMode;
    if ( bDisable )
        nCntrl &= ~EEControlBits::AUTOCORRECT;
    else
        nCntrl |= EEControlBits::AUTOCORRECT;
 
    if ( nCntrl != nOld )
        mpEditEngine->SetControlWord(nCntrl);
}
 
void ScInputHandler::UpdateSpellSettings( bool bFromStartTab )
{
    if ( pActiveViewSh )
    {
        ScViewData& rViewData = pActiveViewSh->GetViewData();
        bool bOnlineSpell = rViewData.GetDocument()->GetDocOptions().IsAutoSpell();
 
        //  SetDefaultLanguage is independent of the language attributes,
        //  ScGlobal::GetEditDefaultLanguage is always used.
        //  It must be set every time in case the office language was changed.
 
        mpEditEngine->SetDefaultLanguage( ScGlobal::GetEditDefaultLanguage() );
 
        //  if called for changed options, update flags only if already editing
        //  if called from StartTable, always update flags
 
        if ( bFromStartTab || eMode != SC_INPUT_NONE )
        {
            EEControlBits nCntrl = mpEditEngine->GetControlWord();
            EEControlBits nOld = nCntrl;
            if( bOnlineSpell )
                nCntrl |= EEControlBits::ONLINESPELLING;
            else
                nCntrl &= ~EEControlBits::ONLINESPELLING;
            // No AutoCorrect for Symbol Font (EditEngine does no evaluate Default)
            if ( pLastPattern && pLastPattern->IsSymbolFont() )
                nCntrl &= ~EEControlBits::AUTOCORRECT;
            else
                nCntrl |= EEControlBits::AUTOCORRECT;
            if ( nCntrl != nOld )
                mpEditEngine->SetControlWord(nCntrl);
 
            ScDocument* pDoc = rViewData.GetDocument();
            pDoc->ApplyAsianEditSettings( *mpEditEngine );
            mpEditEngine->SetDefaultHorizontalTextDirection(
                pDoc->GetEditTextDirection( rViewData.GetTabNo() ) );
            mpEditEngine->SetFirstWordCapitalization( false );
        }
 
        //  Language is set separately, so the speller is needed only if online spelling is active
        if ( bOnlineSpell ) {
            css::uno::Reference<css::linguistic2::XSpellChecker1> xXSpellChecker1( LinguMgr::GetSpellChecker() );
            mpEditEngine->SetSpeller( xXSpellChecker1 );
        }
 
        bool bHyphen = pLastPattern && pLastPattern->GetItem(ATTR_HYPHENATE).GetValue();
        if ( bHyphen ) {
            css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
            mpEditEngine->SetHyphenator( xXHyphenator );
        }
    }
}
 
// Function/Range names etc. as Tip help
 
//  The other types are defined in ScDocument::GetFormulaEntries
void ScInputHandler::GetFormulaData()
{
    if ( pActiveViewSh )
    {
        ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
 
        if ( pFormulaData )
            pFormulaData->clear();
        else
        {
            pFormulaData.reset( new ScTypedCaseStrSet );
        }
 
        if( pFormulaDataPara )
            pFormulaDataPara->clear();
        else
            pFormulaDataPara.reset( new ScTypedCaseStrSet );
 
        const OUString aParenthesesReplacement( cParenthesesReplacement);
        const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList();
        sal_uInt32 nListCount = pFuncList->GetCount();
        for(sal_uInt32 i=0;i<nListCount;i++)
        {
            const ScFuncDesc* pDesc = pFuncList->GetFunction( i );
            if ( pDesc->mxFuncName )
            {
                const sal_Unicode* pName = pDesc->mxFuncName->getStr();
                const sal_Int32 nLen = pDesc->mxFuncName->getLength();
                // fdo#75264 fill maFormulaChar with all characters used in formula names
                for ( sal_Int32 j = 0; j < nLen; j++ )
                {
                    sal_Unicode c = pName[ j ];
                    maFormulaChar.insert( c );
                }
                OUString aFuncName = *pDesc->mxFuncName + aParenthesesReplacement;
                pFormulaData->insert(ScTypedStrData(aFuncName, 0.0, ScTypedStrData::Standard));
                pDesc->initArgumentInfo();
                OUString aEntry = pDesc->getSignature();
                pFormulaDataPara->insert(ScTypedStrData(aEntry, 0.0, ScTypedStrData::Standard));
            }
        }
        miAutoPosFormula = pFormulaData->end();
        rDoc.GetFormulaEntries( *pFormulaData );
        rDoc.GetFormulaEntries( *pFormulaDataPara );
    }
}
 
IMPL_LINK( ScInputHandler, ShowHideTipVisibleParentListener, VclWindowEvent&, rEvent, void )
{
    if( rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide )
        HideTip();
}
 
IMPL_LINK( ScInputHandler, ShowHideTipVisibleSecParentListener, VclWindowEvent&, rEvent, void )
{
    if( rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide )
        HideTipBelow();
}
 
void ScInputHandler::HideTip()
{
    if ( nTipVisible )
    {
        pTipVisibleParent->RemoveEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleParentListener ) );
        Help::HidePopover(pTipVisibleParent, nTipVisible );
        nTipVisible = nullptr;
        pTipVisibleParent = nullptr;
    }
    aManualTip.clear();
}
void ScInputHandler::HideTipBelow()
{
    if ( nTipVisibleSec )
    {
        pTipVisibleSecParent->RemoveEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleSecParentListener ) );
        Help::HidePopover(pTipVisibleSecParent, nTipVisibleSec);
        nTipVisibleSec = nullptr;
        pTipVisibleSecParent = nullptr;
    }
    aManualTip.clear();
}
 
namespace
{
 
bool lcl_hasSingleToken(const OUString& s, sal_Unicode c)
{
    return !s.isEmpty() && s.indexOf(c)<0;
}
 
}
 
void ScInputHandler::ShowArgumentsTip( OUString& rSelText )
{
    ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    const sal_Unicode cSheetSep = lcl_getSheetSeparator(&pDocSh->GetDocument());
    FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
    bool bFound = false;
    while( !bFound )
    {
        rSelText += ")";
        sal_Int32 nLeftParentPos = lcl_MatchParenthesis( rSelText, rSelText.getLength()-1 );
        if( nLeftParentPos != -1 )
        {
            sal_Int32 nNextFStart = aHelper.GetFunctionStart( rSelText, nLeftParentPos, true);
            const IFunctionDescription* ppFDesc;
            ::std::vector< OUString> aArgs;
            if( aHelper.GetNextFunc( rSelText, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) )
            {
                if( !ppFDesc->getFunctionName().isEmpty() )
                {
                    sal_Int32 nArgPos = aHelper.GetArgStart( rSelText, nNextFStart, 0 );
                    sal_uInt16 nArgs = static_cast<sal_uInt16>(ppFDesc->getParameterCount());
                    OUString aFuncName( ppFDesc->getFunctionName() + "(");
                    OUString aNew;
                    ScTypedCaseStrSet::const_iterator it =
                        findText(*pFormulaDataPara, pFormulaDataPara->end(), aFuncName, aNew, false);
                    if (it != pFormulaDataPara->end())
                    {
                        bool bFlag = false;
                        sal_uInt16 nActive = 0;
                        for( sal_uInt16 i=0; i < nArgs; i++ )
                        {
                            sal_Int32 nLength = aArgs[i].getLength();
                            if( nArgPos <= rSelText.getLength()-1 )
                            {
                                nActive = i+1;
                                bFlag = true;
                            }
                            nArgPos+=nLength+1;
                        }
                        if( bFlag )
                        {
                            sal_Int32 nStartPosition = 0;
                            sal_Int32 nEndPosition = 0;
 
                            if( lcl_hasSingleToken(aNew, cSep) )
                            {
                                for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
                                {
                                    sal_Unicode cNext = aNew[i];
                                    if( cNext == '(' )
                                    {
                                        nStartPosition = i+1;
                                    }
                                }
                            }
                            else if( lcl_hasSingleToken(aNew, cSheetSep) )
                            {
                                sal_uInt16 nCount = 0;
                                for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
                                {
                                    sal_Unicode cNext = aNew[i];
                                    if( cNext == '(' )
                                    {
                                        nStartPosition = i+1;
                                    }
                                    else if( cNext == cSep )
                                    {
                                        nCount ++;
                                        nEndPosition = i;
                                        if( nCount == nActive )
                                        {
                                            break;
                                        }
                                        nStartPosition = nEndPosition+1;
                                    }
                                }
                            }
                            else
                            {
                                sal_uInt16 nCount = 0;
                                for (sal_Int32 i = 0; i < aNew.getLength(); ++i)
                                {
                                    sal_Unicode cNext = aNew[i];
                                    if( cNext == '(' )
                                    {
                                        nStartPosition = i+1;
                                    }
                                    else if( cNext == cSep )
                                    {
                                        nCount ++;
                                        nEndPosition = i;
                                        if( nCount == nActive )
                                        {
                                            break;
                                        }
                                        nStartPosition = nEndPosition+1;
                                    }
                                    else if( cNext == cSheetSep )
                                    {
                                        continue;
                                    }
                                }
                            }
 
                            if (nStartPosition > 0)
                            {
                                OUStringBuffer aBuf;
                                aBuf.appendCopy(aNew, 0, nStartPosition);
                                aBuf.append(u'\x25BA');
                                aBuf.appendCopy(aNew, nStartPosition);
                                nArgs = ppFDesc->getParameterCount();
                                sal_Int16 nVarArgsSet = 0;
                                if ( nArgs >= PAIRED_VAR_ARGS )
                                {
                                    nVarArgsSet = 2;
                                    nArgs -= PAIRED_VAR_ARGS - nVarArgsSet;
                                }
                                else if ( nArgs >= VAR_ARGS )
                                {
                                    nVarArgsSet = 1;
                                    nArgs -= VAR_ARGS - nVarArgsSet;
                                }
                                if ( nVarArgsSet > 0 && nActive > nArgs )
                                    nActive = nArgs - (nActive - nArgs) % nVarArgsSet;
                                aBuf.append( " : " );
                                aBuf.append( ppFDesc->getParameterDescription(nActive-1) );
                                aNew = aBuf.makeStringAndClear();
                                ShowTipBelow( aNew );
                                bFound = true;
                            }
                        }
                        else
                        {
                            ShowTipBelow( aNew );
                            bFound = true;
                        }
                    }
                }
            }
        }
        else
        {
            break;
        }
    }
}
 
void ScInputHandler::ShowTipCursor()
{
    HideTip();
    HideTipBelow();
    EditView* pActiveView = pTopView ? pTopView : pTableView;
 
    if ( bFormulaMode && pActiveView && pFormulaDataPara && mpEditEngine->GetParagraphCount() == 1 )
    {
        OUString aParagraph = mpEditEngine->GetText( 0 );
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();
 
        if ( aParagraph.getLength() < aSel.nEndPos )
            return;
 
        if ( aSel.nEndPos > 0 )
        {
            OUString aSelText( aParagraph.copy( 0, aSel.nEndPos ));
 
            ShowArgumentsTip( aSelText );
        }
    }
}
 
void ScInputHandler::ShowTip( const OUString& rText )
{
    // aManualTip needs to be set afterwards from outside
 
    HideTip();
    HideTipBelow();
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (pActiveView)
    {
        Point aPos;
        pTipVisibleParent = pActiveView->GetWindow();
        vcl::Cursor* pCur = pActiveView->GetCursor();
        if (pCur)
            aPos = pTipVisibleParent->LogicToPixel( pCur->GetPos() );
        aPos = pTipVisibleParent->OutputToScreenPixel( aPos );
        tools::Rectangle aRect( aPos, aPos );
 
        QuickHelpFlags const nAlign = QuickHelpFlags::Left|QuickHelpFlags::Bottom;
        nTipVisible = Help::ShowPopover(pTipVisibleParent, aRect, rText, nAlign);
        pTipVisibleParent->AddEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleParentListener ) );
    }
}
 
void ScInputHandler::ShowTipBelow( const OUString& rText )
{
    HideTipBelow();
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView )
    {
        Point aPos;
        pTipVisibleSecParent = pActiveView->GetWindow();
        vcl::Cursor* pCur = pActiveView->GetCursor();
        if ( pCur )
        {
            Point aLogicPos = pCur->GetPos();
            aLogicPos.AdjustY(pCur->GetHeight() );
            aPos = pTipVisibleSecParent->LogicToPixel( aLogicPos );
        }
        aPos = pTipVisibleSecParent->OutputToScreenPixel( aPos );
        tools::Rectangle aRect( aPos, aPos );
        QuickHelpFlags const nAlign = QuickHelpFlags::Left | QuickHelpFlags::Top | QuickHelpFlags::NoEvadePointer;
        nTipVisibleSec = Help::ShowPopover(pTipVisibleSecParent, aRect, rText, nAlign);
        pTipVisibleSecParent->AddEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleSecParentListener ) );
    }
}
 
bool ScInputHandler::GetFuncName( OUString& aStart, OUString& aResult )
{
    if ( aStart.isEmpty() )
        return false;
 
    aStart = ScGlobal::pCharClass->uppercase( aStart );
    sal_Int32 nPos = aStart.getLength() - 1;
    sal_Unicode c = aStart[ nPos ];
    // fdo#75264 use maFormulaChar to check if characters are used in function names
    ::std::set< sal_Unicode >::const_iterator p = maFormulaChar.find( c );
    if ( p == maFormulaChar.end() )
        return false; // last character is not part of any function name, quit
 
    ::std::vector<sal_Unicode> aTemp;
    aTemp.push_back( c );
    for(sal_Int32 i = nPos - 1; i >= 0; --i)
    {
        c = aStart[ i ];
        p = maFormulaChar.find( c );
 
        if (p == maFormulaChar.end())
            break;
 
        aTemp.push_back( c );
    }
 
    ::std::vector<sal_Unicode>::reverse_iterator rIt = aTemp.rbegin();
    aResult = OUString( *rIt++ );
    while ( rIt != aTemp.rend() )
        aResult += OUStringLiteral1( *rIt++ );
 
    return true;
}
 
void ScInputHandler::ShowFuncList( const ::std::vector< OUString > & rFuncStrVec )
{
    OUStringBuffer aTipStr;
    OUString aFuncNameStr;
    OUString aDescFuncNameStr;
    ::std::vector<OUString>::const_iterator itStr = rFuncStrVec.begin();
    sal_Int32 nMaxFindNumber = 3;
    sal_Int32 nRemainFindNumber = nMaxFindNumber;
    for ( ; itStr != rFuncStrVec.end(); ++itStr )
    {
        const OUString& rFunc = *itStr;
        if ( rFunc[rFunc.getLength()-1] == cParenthesesReplacement )
        {
            aFuncNameStr = rFunc.copy(0, rFunc.getLength()-1);
        }
        else
        {
            aFuncNameStr = rFunc;
        }
        if ( itStr == rFuncStrVec.begin() )
        {
            aTipStr = "[";
            aDescFuncNameStr = aFuncNameStr + "()";
        }
        else
        {
            aTipStr.append(", ");
        }
        aTipStr.append(aFuncNameStr);
        if ( itStr == rFuncStrVec.begin() )
            aTipStr.append("]");
        if ( --nRemainFindNumber <= 0 )
            break;
    }
    sal_Int32 nRemainNumber = rFuncStrVec.size() - nMaxFindNumber;
    if ( nRemainFindNumber == 0 && nRemainNumber > 0 )
    {
        OUString aMessage( ScResId( STR_FUNCTIONS_FOUND ) );
        aMessage = aMessage.replaceFirst("%2", OUString::number(nRemainNumber));
        aMessage = aMessage.replaceFirst("%1", aTipStr.makeStringAndClear());
        aTipStr = aMessage;
    }
    FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
    sal_Int32 nNextFStart = 0;
    const IFunctionDescription* ppFDesc;
    ::std::vector< OUString > aArgs;
    OUString eqPlusFuncName = "=" + aDescFuncNameStr;
    if ( aHelper.GetNextFunc( eqPlusFuncName, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) )
    {
        if ( !ppFDesc->getFunctionName().isEmpty() )
        {
            aTipStr.append(" : ").append(ppFDesc->getDescription());
        }
    }
    ShowTip( aTipStr.makeStringAndClear() );
}
 
void ScInputHandler::UseFormulaData()
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
 
    // Formulas may only have 1 paragraph
    if ( pActiveView && pFormulaData && mpEditEngine->GetParagraphCount() == 1 )
    {
        OUString aParagraph = mpEditEngine->GetText( 0 );
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();
 
        // Due to differences between table and input cell (e.g clipboard with line breaks),
        // the selection may not be in line with the EditEngine anymore.
        // Just return without any indication as to why.
        if ( aSel.nEndPos > aParagraph.getLength() )
            return;
 
        if ( aParagraph.getLength() > aSel.nEndPos &&
             ( ScGlobal::pCharClass->isLetterNumeric( aParagraph, aSel.nEndPos ) ||
               aParagraph[ aSel.nEndPos ] == '_' ||
               aParagraph[ aSel.nEndPos ] == '.' ||
               aParagraph[ aSel.nEndPos ] == '$'   ) )
            return;
 
        //  Is the cursor at the end of a word?
        if ( aSel.nEndPos > 0 )
        {
            OUString aSelText( aParagraph.copy( 0, aSel.nEndPos ));
 
            OUString aText;
            if ( GetFuncName( aSelText, aText ) )
            {
                // function name is incomplete:
                // show matching functions name as tip above cell
                ::std::vector<OUString> aNewVec;
                miAutoPosFormula = pFormulaData->end();
                miAutoPosFormula = findTextAll(*pFormulaData, miAutoPosFormula, aText, aNewVec, false);
                if (miAutoPosFormula != pFormulaData->end())
                {
                    // check if partial function name is not between quotes
                    sal_Unicode cBetweenQuotes = 0;
                    for ( int n = 0; n < aSelText.getLength(); n++ )
                    {
                        if (cBetweenQuotes)
                        {
                            if (aSelText[n] == cBetweenQuotes)
                                cBetweenQuotes = 0;
                        }
                        else if ( aSelText[ n ] == '"' )
                            cBetweenQuotes = '"';
                        else if ( aSelText[ n ] == '\'' )
                            cBetweenQuotes = '\'';
                    }
                    if ( cBetweenQuotes )
                        return;  // we're between quotes
 
                    ShowFuncList(aNewVec);
                    aAutoSearch = aText;
                }
                return;
            }
 
            // function name is complete:
            // show tip below the cell with function name and arguments of function
            ShowArgumentsTip( aSelText );
        }
    }
}
 
void ScInputHandler::NextFormulaEntry( bool bBack )
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && pFormulaData )
    {
        ::std::vector<OUString> aNewVec;
        ScTypedCaseStrSet::const_iterator itNew = findTextAll(*pFormulaData, miAutoPosFormula, aAutoSearch, aNewVec, bBack);
        if (itNew != pFormulaData->end())
        {
            miAutoPosFormula = itNew;
            ShowFuncList( aNewVec );
        }
    }
 
    // For Tab we always call HideCursor first
    if (pActiveView)
        pActiveView->ShowCursor();
}
 
namespace {
 
bool needToExtendSelection(const OUString& rSelectedText, const OUString& rInsertText)
{
    return !ScGlobal::GetpTransliteration()->isMatch( rSelectedText, rInsertText);
}
 
void completeFunction( EditView* pView, const OUString& rInsert, bool& rParInserted )
{
    if (pView)
    {
        ESelection aSel = pView->GetSelection();
        --aSel.nStartPos;
        --aSel.nEndPos;
        pView->SetSelection(aSel);
        pView->SelectCurrentWord();
 
        // a dot and underscore are word separators so we need special
        // treatment for any formula containing a dot or underscore
        if(rInsert.indexOf(".") != -1 || rInsert.indexOf("_") != -1)
        {
            // need to make sure that we replace also the part before the dot
            // go through the word to find the match with the insert string
            aSel = pView->GetSelection();
            ESelection aOldSelection = aSel;
            OUString aSelectedText = pView->GetSelected();
            if ( needToExtendSelection( aSelectedText, rInsert ) )
            {
                while(needToExtendSelection(aSelectedText, rInsert))
                {
                    assert(aSel.nStartPos > 0);
                    --aSel.nStartPos;
                    aSel.nEndPos = aSel.nStartPos;
                    pView->SetSelection(aSel);
                    pView->SelectCurrentWord();
                    aSelectedText = pView->GetSelected();
                }
                aSel.nStartPos = aSel.nEndPos - ( aSelectedText.getLength() - 1 );
            }
            else
            {
                aSel.nStartPos = aSel.nEndPos - aSelectedText.getLength();
            }
            aSel.nEndPos = aOldSelection.nEndPos;
            pView->SetSelection(aSel);
        }
 
        OUString aInsStr = rInsert;
        sal_Int32 nInsLen = aInsStr.getLength();
        bool bDoParen = ( nInsLen > 1 && aInsStr[nInsLen-2] == '('
                                      && aInsStr[nInsLen-1] == ')' );
        if ( bDoParen )
        {
            // Do not insert parentheses after function names if there already are some
            // (e.g. if the function name was edited).
            ESelection aWordSel = pView->GetSelection();
            OUString aOld = pView->GetEditEngine()->GetText(0);
 
            // aWordSel.EndPos points one behind string if word at end
            if (aWordSel.nEndPos < aOld.getLength())
            {
                sal_Unicode cNext = aOld[aWordSel.nEndPos];
                if ( cNext == '(' )
                {
                    bDoParen = false;
                    aInsStr = aInsStr.copy( 0, nInsLen - 2 ); // Skip parentheses
                }
            }
        }
 
        pView->InsertText( aInsStr );
 
        if ( bDoParen ) // Put cursor between parentheses
        {
            aSel = pView->GetSelection();
            --aSel.nStartPos;
            --aSel.nEndPos;
            pView->SetSelection(aSel);
 
            rParInserted = true;
        }
    }
}
 
}
 
void ScInputHandler::PasteFunctionData()
{
    if (pFormulaData && miAutoPosFormula != pFormulaData->end())
    {
        const ScTypedStrData& rData = *miAutoPosFormula;
        OUString aInsert = rData.GetString();
        if (aInsert[aInsert.getLength()-1] == cParenthesesReplacement)
            aInsert = aInsert.copy( 0, aInsert.getLength()-1) + "()";
        bool bParInserted = false;
 
        DataChanging(); // Cannot be new
        completeFunction( pTopView, aInsert, bParInserted );
        completeFunction( pTableView, aInsert, bParInserted );
        DataChanged();
        ShowTipCursor();
 
        if (bParInserted)
            AutoParAdded();
    }
 
    HideTip();
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (pActiveView)
        pActiveView->ShowCursor();
}
 
// Calculate selection and display as tip help
static OUString lcl_Calculate( const OUString& rFormula, ScDocument* pDoc, const ScAddress &rPos )
{
    //TODO: Merge with ScFormulaDlg::CalcValue and move into Document!
    // Quotation marks for Strings are only inserted here.
 
    if(rFormula.isEmpty())
        return OUString();
 
    std::unique_ptr<ScSimpleFormulaCalculator> pCalc( new ScSimpleFormulaCalculator( pDoc, rPos, rFormula, false ) );
 
    // FIXME: HACK! In order to not get a #REF! for ColRowNames, if a name is actually inserted as a Range
    // into the whole Formula, but is interpreted as a single cell reference when displaying it on its own
    bool bColRowName = pCalc->HasColRowName();
    if ( bColRowName )
    {
        // ColRowName in RPN code?
        if ( pCalc->GetCode()->GetCodeLen() <= 1 )
        {   // ==1: Single one is as a Parameter always a Range
            // ==0: It might be one, if ...
            OUStringBuffer aBraced;
            aBraced.append('(');
            aBraced.append(rFormula);
            aBraced.append(')');
            pCalc.reset( new ScSimpleFormulaCalculator( pDoc, rPos, aBraced.makeStringAndClear(), false ) );
        }
        else
            bColRowName = false;
    }
 
    FormulaError nErrCode = pCalc->GetErrCode();
    if ( nErrCode != FormulaError::NONE )
        return ScGlobal::GetErrorString(nErrCode);
 
    SvNumberFormatter& aFormatter = *(pDoc->GetFormatTable());
    OUString aValue;
    if ( pCalc->IsValue() )
    {
        double n = pCalc->GetValue();
        sal_uInt32 nFormat = aFormatter.GetStandardFormat( n, 0,
                pCalc->GetFormatType(), ScGlobal::eLnge );
        aFormatter.GetInputLineString( n, nFormat, aValue );
        //! display OutputString but insert InputLineString
    }
    else
    {
        OUString aStr = pCalc->GetString().getString();
        sal_uInt32 nFormat = aFormatter.GetStandardFormat(
                pCalc->GetFormatType(), ScGlobal::eLnge);
        {
            Color* pColor;
            aFormatter.GetOutputString( aStr, nFormat,
                    aValue, &pColor );
        }
 
        aValue = "\"" + aValue + "\"";
        //! Escape quotation marks in String??
    }
 
    ScRange aTestRange;
    if ( bColRowName || (aTestRange.Parse(rFormula) & ScRefFlags::VALID) )
        aValue = aValue + " ...";
 
    return aValue;
}
 
void ScInputHandler::FormulaPreview()
{
    OUString aValue;
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && pActiveViewSh )
    {
        OUString aPart = pActiveView->GetSelected();
        if (aPart.isEmpty())
            aPart = mpEditEngine->GetText(0);
        ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
        aValue = lcl_Calculate( aPart, &rDoc, aCursorPos );
    }
 
    if (!aValue.isEmpty())
    {
        ShowTip( aValue );          //  Display as QuickHelp
        aManualTip = aValue;        //  Set after ShowTip
        if (pFormulaData)
            miAutoPosFormula = pFormulaData->end();
        if (pColumnData)
            miAutoPosColumn = pColumnData->end();
    }
}
 
void ScInputHandler::PasteManualTip()
{
    //  Three dots at the end -> Range reference -> do not insert
    //  FIXME: Once we have matrix constants, we can change this
    sal_Int32 nTipLen = aManualTip.getLength();
    sal_uInt32 const nTipLen2(sal::static_int_cast<sal_uInt32>(nTipLen));
    if ( nTipLen && ( nTipLen < 3 || aManualTip.copy( nTipLen2-3 ) != "..." ) )
    {
        DataChanging(); // Cannot be new
 
        OUString aInsert = aManualTip;
        EditView* pActiveView = pTopView ? pTopView : pTableView;
        if (!pActiveView->HasSelection())
        {
            // Nothing selected -> select everything
            sal_Int32 nOldLen = mpEditEngine->GetTextLen(0);
            ESelection aAllSel( 0, 0, 0, nOldLen );
            if ( pTopView )
                pTopView->SetSelection( aAllSel );
            if ( pTableView )
                pTableView->SetSelection( aAllSel );
        }
 
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();
        OSL_ENSURE( !aSel.nStartPara && !aSel.nEndPara, "Too many paragraphs in Formula" );
        if ( !aSel.nStartPos )  // Selection from the start?
        {
            if ( aSel.nEndPos == mpEditEngine->GetTextLen(0) )
            {
                //  Everything selected -> skip quotation marks
                if ( aInsert[0] == '"' )
                    aInsert = aInsert.copy(1);
                sal_Int32 nInsLen = aInsert.getLength();
                if ( aInsert.endsWith("\"") )
                    aInsert = aInsert.copy( 0, nInsLen-1 );
            }
            else if ( aSel.nEndPos )
            {
                //  Not everything selected -> do not overwrite equality sign
                //FIXME: Even double equality signs??
                aSel.nStartPos = 1;
                if ( pTopView )
                    pTopView->SetSelection( aSel );
                if ( pTableView )
                    pTableView->SetSelection( aSel );
            }
        }
        if ( pTopView )
            pTopView->InsertText( aInsert, true );
        if ( pTableView )
            pTableView->InsertText( aInsert, true );
 
        DataChanged();
    }
 
    HideTip();
}
 
void ScInputHandler::ResetAutoPar()
{
    nAutoPar = 0;
}
 
void ScInputHandler::AutoParAdded()
{
    ++nAutoPar; // Closing parenthesis can be overwritten
}
 
bool ScInputHandler::CursorAtClosingPar()
{
    // Test if the cursor is before a closing parenthesis
    // Selection from SetReference has been removed before
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && !pActiveView->HasSelection() && bFormulaMode )
    {
        ESelection aSel = pActiveView->GetSelection();
        sal_Int32 nPos = aSel.nStartPos;
        OUString aFormula = mpEditEngine->GetText(0);
        if ( nPos < aFormula.getLength() && aFormula[nPos] == ')' )
            return true;
    }
    return false;
}
 
void ScInputHandler::SkipClosingPar()
{
    //  this is called when a ')' is typed and the cursor is before a ')'
    //  that can be overwritten -> just set the cursor behind the ')'
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (pActiveView)
    {
        ESelection aSel = pActiveView->GetSelection();
        ++aSel.nStartPos;
        ++aSel.nEndPos;
 
        //  this is in a formula (only one paragraph), so the selection
        //  can be used directly for the TopView
 
        if ( pTopView )
            pTopView->SetSelection( aSel );
        if ( pTableView )
            pTableView->SetSelection( aSel );
    }
 
    OSL_ENSURE(nAutoPar, "SkipClosingPar: count is wrong");
    --nAutoPar;
}
 
// Auto input
 
void ScInputHandler::GetColData()
{
    if ( pActiveViewSh )
    {
        ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
 
        if ( pColumnData )
            pColumnData->clear();
        else
            pColumnData.reset( new ScTypedCaseStrSet );
 
        std::vector<ScTypedStrData> aEntries;
        rDoc.GetDataEntries(
            aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(), aEntries, true);
        if (!aEntries.empty())
            pColumnData->insert(aEntries.begin(), aEntries.end());
 
        miAutoPosColumn = pColumnData->end();
    }
}
 
void ScInputHandler::UseColData() // When typing
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && pColumnData )
    {
        //  Only change when cursor is at the end
        ESelection aSel = pActiveView->GetSelection();
        aSel.Adjust();
 
        sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
        if ( aSel.nEndPara+1 == nParCnt )
        {
            sal_Int32 nParLen = mpEditEngine->GetTextLen( aSel.nEndPara );
            if ( aSel.nEndPos == nParLen )
            {
                OUString aText = GetEditText(mpEditEngine.get());
                if (!aText.isEmpty())
                {
                    OUString aNew;
                    miAutoPosColumn = pColumnData->end();
                    miAutoPosColumn = findText(*pColumnData, miAutoPosColumn, aText, aNew, false);
                    if (miAutoPosColumn != pColumnData->end())
                    {
                        // Strings can contain line endings (e.g. due to dBase import),
                        // which would result in multiple paragraphs here, which is not desirable.
                        //! Then GetExactMatch doesn't work either
                        lcl_RemoveLineEnd( aNew );
 
                        // Keep paragraph, just append the rest
                        //! Exact replacement in EnterHandler !!!
                        // One Space between paragraphs:
                        sal_Int32 nEdLen = mpEditEngine->GetTextLen() + nParCnt - 1;
                        OUString aIns = aNew.copy(nEdLen);
 
                        // Selection must be "backwards", so the cursor stays behind the last
                        // typed character
                        ESelection aSelection( aSel.nEndPara, aSel.nEndPos + aIns.getLength(),
                                               aSel.nEndPara, aSel.nEndPos );
 
                        // When editing in input line, apply to both edit views
                        if ( pTableView )
                        {
                            pTableView->InsertText( aIns );
                            pTableView->SetSelection( aSelection );
                        }
                        if ( pTopView )
                        {
                            pTopView->InsertText( aIns );
                            pTopView->SetSelection( aSelection );
                        }
 
                        aAutoSearch = aText; // To keep searching - nAutoPos is set
 
                        if (aText.getLength() == aNew.getLength())
                        {
                            // If the inserted text is found, consume TAB only if there's more coming
                            OUString aDummy;
                            ScTypedCaseStrSet::const_iterator itNextPos =
                                findText(*pColumnData, miAutoPosColumn, aText, aDummy, false);
                            bUseTab = itNextPos != pColumnData->end();
                        }
                        else
                            bUseTab = true;
                    }
                }
            }
        }
    }
}
 
void ScInputHandler::NextAutoEntry( bool bBack )
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if ( pActiveView && pColumnData )
    {
        if (miAutoPosColumn != pColumnData->end() && !aAutoSearch.isEmpty())
        {
            // Is the selection still valid (could be changed via the mouse)?
            ESelection aSel = pActiveView->GetSelection();
            aSel.Adjust();
            sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
            if ( aSel.nEndPara+1 == nParCnt && aSel.nStartPara == aSel.nEndPara )
            {
                OUString aText = GetEditText(mpEditEngine.get());
                sal_Int32 nSelLen = aSel.nEndPos - aSel.nStartPos;
                sal_Int32 nParLen = mpEditEngine->GetTextLen( aSel.nEndPara );
                if ( aSel.nEndPos == nParLen && aText.getLength() == aAutoSearch.getLength() + nSelLen )
                {
                    OUString aNew;
                    ScTypedCaseStrSet::const_iterator itNew =
                        findText(*pColumnData, miAutoPosColumn, aAutoSearch, aNew, bBack);
 
                    if (itNew != pColumnData->end())
                    {
                        // match found!
                        miAutoPosColumn = itNew;
                        bInOwnChange = true;        // disable ModifyHdl (reset below)
 
                        lcl_RemoveLineEnd( aNew );
                        OUString aIns = aNew.copy(aAutoSearch.getLength());
 
                        //  when editing in input line, apply to both edit views
                        if ( pTableView )
                        {
                            pTableView->DeleteSelected();
                            pTableView->InsertText( aIns );
                            pTableView->SetSelection( ESelection(
                                                        aSel.nEndPara, aSel.nStartPos + aIns.getLength(),
                                                        aSel.nEndPara, aSel.nStartPos ) );
                        }
                        if ( pTopView )
                        {
                            pTopView->DeleteSelected();
                            pTopView->InsertText( aIns );
                            pTopView->SetSelection( ESelection(
                                                        aSel.nEndPara, aSel.nStartPos + aIns.getLength(),
                                                        aSel.nEndPara, aSel.nStartPos ) );
                        }
 
                        bInOwnChange = false;
                    }
                }
            }
        }
    }
 
    // For Tab, HideCursor was always called first
    if (pActiveView)
        pActiveView->ShowCursor();
}
 
// Highlight parentheses
void ScInputHandler::UpdateParenthesis()
{
    // Find parentheses
    //TODO: Can we disable parentheses highlighting per parentheses?
    bool bFound = false;
    if ( bFormulaMode && eMode != SC_INPUT_TOP )
    {
        if ( pTableView && !pTableView->HasSelection() ) // Selection is always at the bottom
        {
            ESelection aSel = pTableView->GetSelection();
            if (aSel.nStartPos)
            {
                // Examine character left to the cursor
                sal_Int32 nPos = aSel.nStartPos - 1;
                OUString aFormula = mpEditEngine->GetText(0);
                sal_Unicode c = aFormula[nPos];
                if ( c == '(' || c == ')' )
                {
                    sal_Int32 nOther = lcl_MatchParenthesis( aFormula, nPos );
                    if ( nOther != -1 )
                    {
                        SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );
                        aSet.Put( SvxWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT ) );
 
                        //! Distinguish if cell is already highlighted!!!!
                        if (bParenthesisShown)
                        {
                            // Remove old highlighting
                            sal_Int32 nCount = mpEditEngine->GetParagraphCount();
                            for (sal_Int32 i=0; i<nCount; i++)
                                mpEditEngine->RemoveCharAttribs( i, EE_CHAR_WEIGHT );
                        }
 
                        ESelection aSelThis( 0,nPos, 0,nPos+1 );
                        mpEditEngine->QuickSetAttribs( aSet, aSelThis );
                        ESelection aSelOther( 0,nOther, 0,nOther+1 );
                        mpEditEngine->QuickSetAttribs( aSet, aSelOther );
 
                        // Dummy InsertText for Update and Paint (selection is empty)
                        pTableView->InsertText( EMPTY_OUSTRING );
 
                        bFound = true;
                    }
                }
            }
 
            //  mark parenthesis right of cursor if it will be overwritten (nAutoPar)
            //  with different color (COL_LIGHTBLUE) ??
        }
    }
 
    // Remove old highlighting, if no new one is set
    if ( bParenthesisShown && !bFound && pTableView )
    {
        sal_Int32 nCount = mpEditEngine->GetParagraphCount();
        for (sal_Int32 i=0; i<nCount; i++)
            pTableView->RemoveCharAttribs( i, EE_CHAR_WEIGHT );
    }
 
    bParenthesisShown = bFound;
}
 
void ScInputHandler::ViewShellGone(const ScTabViewShell* pViewSh) // Executed synchronously!
{
    if ( pViewSh == pActiveViewSh )
    {
        pLastState.reset();
        pLastPattern = nullptr;
    }
 
    if ( pViewSh == pRefViewSh )
    {
        //! The input from the EnterHandler does not arrive anymore
        // We end the EditMode anyways
        EnterHandler();
        bFormulaMode = false;
        pRefViewSh = nullptr;
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
        SC_MOD()->SetRefInputHdl(nullptr);
        if (pInputWin)
            pInputWin->SetFormulaMode(false);
        UpdateAutoCorrFlag();
    }
 
    pActiveViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current()  );
 
    if ( pActiveViewSh && pActiveViewSh == pViewSh )
    {
        OSL_FAIL("pActiveViewSh is gone");
        pActiveViewSh = nullptr;
    }
 
    if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() )
        UpdateRefDevice(); // Don't keep old document's printer as RefDevice
}
 
void ScInputHandler::UpdateActiveView()
{
    ImplCreateEditEngine();
 
    // #i20588# Don't rely on focus to find the active edit view. Instead, the
    // active pane at the start of editing is now stored (GetEditActivePart).
    // GetActiveWin (the currently active pane) fails for ref input across the
    // panes of a split view.
 
    vcl::Window* pShellWin = pActiveViewSh ?
                pActiveViewSh->GetWindowByPos( pActiveViewSh->GetViewData().GetEditActivePart() ) :
                nullptr;
 
    sal_uInt16 nCount = mpEditEngine->GetViewCount();
    if (nCount > 0)
    {
        pTableView = mpEditEngine->GetView();
        for (sal_uInt16 i=1; i<nCount; i++)
        {
            EditView* pThis = mpEditEngine->GetView(i);
            vcl::Window* pWin = pThis->GetWindow();
            if ( pWin==pShellWin )
                pTableView = pThis;
        }
    }
    else
        pTableView = nullptr;
 
    // setup the pTableView editeng for tiled rendering to get cursor and selections
    if (pTableView && pActiveViewSh)
    {
        if (comphelper::LibreOfficeKit::isActive())
        {
            pTableView->RegisterViewShell(pActiveViewSh);
        }
    }
 
    if (pInputWin && (eMode == SC_INPUT_TOP || eMode == SC_INPUT_TABLE))
    {
        // tdf#71409: Always create the edit engine instance for the input
        // window, in order to properly manage accessibility events.
        pTopView = pInputWin->GetEditView();
        if (eMode != SC_INPUT_TOP)
            pTopView = nullptr;
    }
    else
        pTopView = nullptr;
}
 
void ScInputHandler::SetInputWindow(  ScInputWindow* pNew )
{
    pInputWin = pNew;
}
 
void ScInputHandler::StopInputWinEngine( bool bAll )
{
    if (pInputWin && !pInputWin->IsDisposed())
        pInputWin->StopEditEngine( bAll );
 
    pTopView = nullptr; // invalid now
}
 
EditView* ScInputHandler::GetActiveView()
{
    UpdateActiveView();
    return pTopView ? pTopView : pTableView;
}
 
void ScInputHandler::ForgetLastPattern()
{
    pLastPattern = nullptr;
    if ( !pLastState && pActiveViewSh )
        pActiveViewSh->UpdateInputHandler( true ); // Get status again
    else
        NotifyChange( pLastState.get(), true );
}
 
void ScInputHandler::UpdateAdjust( sal_Unicode cTyped )
{
    SvxAdjust eSvxAdjust;
    switch (eAttrAdjust)
    {
        case SvxCellHorJustify::Standard:
            {
                bool bNumber = false;
                if (cTyped)                                     // Restarted
                    bNumber = (cTyped>='0' && cTyped<='9');     // Only ciphers are numbers
                else if ( pActiveViewSh )
                {
                    ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
                    bNumber = ( rDoc.GetCellType( aCursorPos ) == CELLTYPE_VALUE );
                }
                eSvxAdjust = bNumber ? SvxAdjust::Right : SvxAdjust::Left;
            }
            break;
        case SvxCellHorJustify::Block:
            eSvxAdjust = SvxAdjust::Block;
            break;
        case SvxCellHorJustify::Center:
            eSvxAdjust = SvxAdjust::Center;
            break;
        case SvxCellHorJustify::Right:
            eSvxAdjust = SvxAdjust::Right;
            break;
        default:    // SvxCellHorJustify::Left
            eSvxAdjust = SvxAdjust::Left;
            break;
    }
 
    bool bAsianVertical = pLastPattern &&
        pLastPattern->GetItem( ATTR_STACKED ).GetValue() &&
        pLastPattern->GetItem( ATTR_VERTICAL_ASIAN ).GetValue();
    if ( bAsianVertical )
    {
        // Always edit at top of cell -> LEFT when editing vertically
        eSvxAdjust = SvxAdjust::Left;
    }
 
    pEditDefaults->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
    mpEditEngine->SetDefaults( *pEditDefaults );
 
    if ( pActiveViewSh )
    {
        pActiveViewSh->GetViewData().SetEditAdjust( eSvxAdjust );
    }
    mpEditEngine->SetVertical( bAsianVertical );
}
 
void ScInputHandler::RemoveAdjust()
{
    // Delete hard alignment attributes
    bool bUndo = mpEditEngine->IsUndoEnabled();
    if ( bUndo )
        mpEditEngine->EnableUndo( false );
 
    // Non-default paragraph attributes (e.g. from clipboard)
    // must be turned into character attributes
    mpEditEngine->RemoveParaAttribs();
 
    if ( bUndo )
        mpEditEngine->EnableUndo( true );
 
}
 
void ScInputHandler::RemoveRangeFinder()
{
    // Delete pRangeFindList and colors
    mpEditEngine->SetUpdateMode(false);
    sal_Int32 nCount = mpEditEngine->GetParagraphCount(); // Could just have been inserted
    for (sal_Int32 i=0; i<nCount; i++)
        mpEditEngine->RemoveCharAttribs( i, EE_CHAR_COLOR );
    mpEditEngine->SetUpdateMode(true);
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    pActiveView->ShowCursor( false );
 
    DeleteRangeFinder(); // Deletes the list and the labels on the table
}
 
bool ScInputHandler::StartTable( sal_Unicode cTyped, bool bFromCommand, bool bInputActivated,
        ScEditEngineDefaulter* pTopEngine )
{
    bool bNewTable = false;
 
    if (bModified || !ValidCol(aCursorPos.Col()))
        return false;
 
    if (pActiveViewSh)
    {
        ImplCreateEditEngine();
        UpdateActiveView();
        SyncViews();
 
        ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
 
        const ScMarkData& rMark = pActiveViewSh->GetViewData().GetMarkData();
        ScEditableTester aTester;
        if ( rMark.IsMarked() || rMark.IsMultiMarked() )
            aTester.TestSelection( &rDoc, rMark );
        else
            aTester.TestSelectedBlock(
                &rDoc, aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Col(), aCursorPos.Row(), rMark );
 
        bool bStartInputMode = true;
 
        if (!aTester.IsEditable())
        {
            bProtected = true;
            // We allow read-only input mode activation regardless
            // whether it's part of an array or not or whether explicit cell
            // activation is requested (double-click or F2) or a click in input
            // line.
            bool bShowError = (!bInputActivated || !aTester.GetMessageId() || strcmp(aTester.GetMessageId(), STR_PROTECTIONERR) != 0) &&
                !pActiveViewSh->GetViewData().GetDocShell()->IsReadOnly();
            if (bShowError)
            {
                eMode = SC_INPUT_NONE;
                StopInputWinEngine( true );
                UpdateFormulaMode();
                if ( pActiveViewSh && ( !bFromCommand || !bCommandErrorShown ) )
                {
                    //  Prevent repeated error messages for the same cell from command events
                    //  (for keyboard events, multiple messages are wanted).
                    //  Set the flag before showing the error message because the command handler
                    //  for the next IME command may be called when showing the dialog.
                    if ( bFromCommand )
                        bCommandErrorShown = true;
 
                    pActiveViewSh->GetActiveWin()->GrabFocus();
                    pActiveViewSh->ErrorMessage(aTester.GetMessageId());
                }
                bStartInputMode = false;
            }
        }
 
        if (bStartInputMode)
        {
            // UpdateMode is enabled again in ScViewData::SetEditEngine (and not needed otherwise)
            mpEditEngine->SetUpdateMode( false );
 
            // Take over attributes in EditEngine
            const ScPatternAttr* pPattern = rDoc.GetPattern( aCursorPos.Col(),
                                                              aCursorPos.Row(),
                                                              aCursorPos.Tab() );
            if (pPattern != pLastPattern)
            {
                // Percent format?
                const SfxItemSet& rAttrSet = pPattern->GetItemSet();
                const SfxPoolItem* pItem;
 
                if ( SfxItemState::SET == rAttrSet.GetItemState( ATTR_VALUE_FORMAT, true, &pItem ) )
                {
                    sal_uInt32 nFormat = static_cast<const SfxUInt32Item*>(pItem)->GetValue();
                    bCellHasPercentFormat = ( SvNumFormatType::PERCENT ==
                                              rDoc.GetFormatTable()->GetType( nFormat ) );
                }
                else
                    bCellHasPercentFormat = false; // Default: no percent
 
                // Validity specified?
                if ( SfxItemState::SET == rAttrSet.GetItemState( ATTR_VALIDDATA, true, &pItem ) )
                    nValidation = static_cast<const SfxUInt32Item*>(pItem)->GetValue();
                else
                    nValidation = 0;
 
                //  EditEngine Defaults
                //  In no case SetParaAttribs, because the EditEngine might already
                //  be filled (for Edit cells).
                //  SetParaAttribs would change the content.
 
                //! The SetDefaults is now (since MUST/src602
                //! EditEngine changes) implemented as a SetParaAttribs.
                //! Any problems?
 
                pPattern->FillEditItemSet( pEditDefaults.get() );
                mpEditEngine->SetDefaults( *pEditDefaults );
                pLastPattern = pPattern;
                bLastIsSymbol = pPattern->IsSymbolFont();
 
                //  Background color must be known for automatic font color.
                //  For transparent cell background, the document background color must be used.
 
                Color aBackCol = pPattern->GetItem( ATTR_BACKGROUND ).GetColor();
                ScModule* pScMod = SC_MOD();
                if ( aBackCol.GetTransparency() > 0 ||
                        Application::GetSettings().GetStyleSettings().GetHighContrastMode() )
                    aBackCol = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
                mpEditEngine->SetBackgroundColor( aBackCol );
 
                // Adjustment
                eAttrAdjust = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue();
                if ( eAttrAdjust == SvxCellHorJustify::Repeat &&
                     pPattern->GetItem(ATTR_LINEBREAK).GetValue() )
                {
                    // #i31843# "repeat" with "line breaks" is treated as default alignment
                    eAttrAdjust = SvxCellHorJustify::Standard;
                }
            }
 
            if (pTopEngine)
            {
                // Necessary to sync SvxAutoCorrect behavior. This has to be
                // done before InitRangeFinder() below.
                MergeLanguageAttributes( *pTopEngine);
            }
 
            //  UpdateSpellSettings enables online spelling if needed
            //  -> also call if attributes are unchanged
            UpdateSpellSettings( true ); // uses pLastPattern
 
            // Fill EditEngine
            OUString aStr;
            if (bTextValid)
            {
                mpEditEngine->SetText(aCurrentText);
                aStr = aCurrentText;
                bTextValid = false;
                aCurrentText.clear();
            }
            else
                aStr = GetEditText(mpEditEngine.get());
 
            if (aStr.startsWith("{=") && aStr.endsWith("}") )  // Matrix formula?
            {
                aStr = aStr.copy(1, aStr.getLength() -2);
                mpEditEngine->SetText(aStr);
                if ( pInputWin )
                    pInputWin->SetTextString(aStr);
            }
 
            UpdateAdjust( cTyped );
 
            if ( bAutoComplete )
                GetColData();
 
            if ( !aStr.isEmpty() && ( aStr[0] == '=' || aStr[0] == '+' || aStr[0] == '-' ) &&
                 !cTyped && !bCreatingFuncView )
                InitRangeFinder(aStr); // Formula is being edited -> RangeFinder
 
            bNewTable = true; // -> PostEditView Call
        }
    }
 
    if (!bProtected && pInputWin)
        pInputWin->SetOkCancelMode();
 
    return bNewTable;
}
 
void ScInputHandler::MergeLanguageAttributes( ScEditEngineDefaulter& rDestEngine ) const
{
    const SfxItemSet& rSrcSet = mpEditEngine->GetDefaults();
    rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE ));
    rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE_CJK ));
    rDestEngine.SetDefaultItem( rSrcSet.Get( EE_CHAR_LANGUAGE_CTL ));
}
 
static void lcl_SetTopSelection( EditView* pEditView, ESelection& rSel )
{
    OSL_ENSURE( rSel.nStartPara==0 && rSel.nEndPara==0, "SetTopSelection: Para != 0" );
 
    EditEngine* pEngine = pEditView->GetEditEngine();
    sal_Int32 nCount = pEngine->GetParagraphCount();
    if (nCount > 1)
    {
        sal_Int32 nParLen = pEngine->GetTextLen(rSel.nStartPara);
        while (rSel.nStartPos > nParLen && rSel.nStartPara+1 < nCount)
        {
            rSel.nStartPos -= nParLen + 1; // Including space from line break
            nParLen = pEngine->GetTextLen(++rSel.nStartPara);
        }
 
        nParLen = pEngine->GetTextLen(rSel.nEndPara);
        while (rSel.nEndPos > nParLen && rSel.nEndPara+1 < nCount)
        {
            rSel.nEndPos -= nParLen + 1; // Including space from line break
            nParLen = pEngine->GetTextLen(++rSel.nEndPara);
        }
    }
 
    ESelection aSel = pEditView->GetSelection();
 
    if (   rSel.nStartPara != aSel.nStartPara || rSel.nEndPara != aSel.nEndPara
        || rSel.nStartPos  != aSel.nStartPos  || rSel.nEndPos  != aSel.nEndPos )
        pEditView->SetSelection( rSel );
}
 
void ScInputHandler::SyncViews( const EditView* pSourceView )
{
    if (pSourceView)
    {
        bool bSelectionForTopView = false;
        if (pTopView && pTopView != pSourceView)
            bSelectionForTopView = true;
        bool bSelectionForTableView = false;
        if (pTableView && pTableView != pSourceView)
            bSelectionForTableView = true;
        if (bSelectionForTopView || bSelectionForTableView)
        {
            ESelection aSel(pSourceView->GetSelection());
            if (bSelectionForTopView)
                pTopView->SetSelection(aSel);
            if (bSelectionForTableView)
                lcl_SetTopSelection(pTableView, aSel);
        }
    }
    // Only sync selection from topView if we are actually editing there
    else if (pTopView && pTableView)
    {
        ESelection aSel(pTopView->GetSelection());
        lcl_SetTopSelection( pTableView, aSel );
    }
}
 
IMPL_LINK_NOARG(ScInputHandler, ModifyHdl, LinkParamNone*, void)
{
    if ( !bInOwnChange && ( eMode==SC_INPUT_TYPE || eMode==SC_INPUT_TABLE ) &&
         mpEditEngine && mpEditEngine->GetUpdateMode() && pInputWin )
    {
        // Update input line from ModifyHdl for changes that are not
        // wrapped by DataChanging/DataChanged calls (like Drag&Drop)
        OUString aText(ScEditUtil::GetMultilineString(*mpEditEngine));
        lcl_RemoveTabs(aText);
        pInputWin->SetTextString(aText);
    }
}
 
/**
 * @return true means new view created
 */
bool ScInputHandler::DataChanging( sal_Unicode cTyped, bool bFromCommand )
{
    if (pActiveViewSh)
        pActiveViewSh->GetViewData().SetPasteMode( ScPasteFlags::NONE );
    bInOwnChange = true; // disable ModifyHdl (reset in DataChanged)
 
    if ( eMode == SC_INPUT_NONE )
        return StartTable( cTyped, bFromCommand, false, nullptr );
    else
        return false;
}
 
void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified )
{
    ImplCreateEditEngine();
 
    if (eMode==SC_INPUT_NONE)
        eMode = SC_INPUT_TYPE;
 
    if ( eMode == SC_INPUT_TOP && pTopView && !bFromTopNotify )
    {
        //  table EditEngine is formatted below, input line needs formatting after paste
        //  #i20282# not when called from the input line's modify handler
        pTopView->GetEditEngine()->QuickFormatDoc( true );
 
        //  #i23720# QuickFormatDoc hides the cursor, but can't show it again because it
        //  can't safely access the EditEngine's current view, so the cursor has to be
        //  shown again here.
        pTopView->ShowCursor();
    }
 
    if (bSetModified)
        bModified = true;
    bSelIsRef = false;
 
    if ( pRangeFindList && !bInRangeUpdate )
        RemoveRangeFinder(); // Delete attributes and labels
 
    UpdateParenthesis(); // Highlight parentheses anew
 
    if (eMode==SC_INPUT_TYPE || eMode==SC_INPUT_TABLE)
    {
        OUString aText;
        if (pInputWin)
            aText = ScEditUtil::GetMultilineString(*mpEditEngine);
        else
            aText = GetEditText(mpEditEngine.get());
        lcl_RemoveTabs(aText);
 
        if ( pInputWin )
            pInputWin->SetTextString( aText );
 
        if (comphelper::LibreOfficeKit::isActive())
        {
            if (pActiveViewSh)
                pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aText.toUtf8().getStr());
        }
    }
 
    // If the cursor is before the end of a paragraph, parts are being pushed to
    // the right (independently from the eMode) -> Adapt View!
    // If the cursor is at the end, the StatusHandler of the ViewData is sufficient.
    //
    // First make sure the status handler is called now if the cursor
    // is outside the visible area
    mpEditEngine->QuickFormatDoc();
 
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (pActiveView && pActiveViewSh)
    {
        ScViewData& rViewData = pActiveViewSh->GetViewData();
 
        bool bNeedGrow = ( rViewData.GetEditAdjust() != SvxAdjust::Left ); // Always right-aligned
        if (!bNeedGrow)
        {
            // Cursor before the end?
            ESelection aSel = pActiveView->GetSelection();
            aSel.Adjust();
            bNeedGrow = ( aSel.nEndPos != mpEditEngine->GetTextLen(aSel.nEndPara) );
        }
        if (!bNeedGrow)
        {
            bNeedGrow = rViewData.GetDocument()->IsLayoutRTL( rViewData.GetTabNo() );
        }
        if (bNeedGrow)
        {
            // Adjust inplace view
            rViewData.EditGrowY();
            rViewData.EditGrowX();
        }
    }
 
    UpdateFormulaMode();
    bTextValid = false; // Changes only in the EditEngine
    bInOwnChange = false;
}
 
void ScInputHandler::UpdateFormulaMode()
{
    SfxApplication* pSfxApp = SfxGetpApp();
 
    bool bIsFormula = !bProtected && mpEditEngine->GetParagraphCount() == 1;
    if (bIsFormula)
    {
        const OUString& rText = mpEditEngine->GetText(0);
        bIsFormula = !rText.isEmpty() &&
            (rText[0] == '=' || rText[0] == '+' || rText[0] == '-');
    }
 
    // 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::LibreOfficeKit::isActive()
        && SfxViewShell::GetActiveShells(/*only visible shells*/ false) > 1)
    {
        // we look for not visible shells, too, since this method can be
        // invoked by a TabViewShell ctor and at such a stage the view
        // is not yet visible,
        bIsFormula = false;
    }
 
    if ( bIsFormula )
    {
        if (!bFormulaMode)
        {
            bFormulaMode = true;
            pRefViewSh = pActiveViewSh;
            pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
            SC_MOD()->SetRefInputHdl(this);
            if (pInputWin)
                pInputWin->SetFormulaMode(true);
 
            if ( bAutoComplete )
                GetFormulaData();
 
            UpdateParenthesis();
            UpdateAutoCorrFlag();
        }
    }
    else // Deactivate
    {
        if (bFormulaMode)
        {
            ShowRefFrame();
            bFormulaMode = false;
            pRefViewSh = nullptr;
            pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
            SC_MOD()->SetRefInputHdl(nullptr);
            if (pInputWin)
                pInputWin->SetFormulaMode(false);
            UpdateAutoCorrFlag();
        }
    }
}
 
void ScInputHandler::ShowRefFrame()
{
    // Modifying pActiveViewSh here would interfere with the bInEnterHandler / bRepeat
    // checks in NotifyChange, and lead to keeping the wrong value in pActiveViewSh.
    // A local variable is used instead.
    ScTabViewShell* pVisibleSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current()  );
    if ( pRefViewSh && pRefViewSh != pVisibleSh )
    {
        bool bFound = false;
        SfxViewFrame* pRefFrame = pRefViewSh->GetViewFrame();
        SfxViewFrame* pOneFrame = SfxViewFrame::GetFirst();
        while ( pOneFrame && !bFound )
        {
            if ( pOneFrame == pRefFrame )
                bFound = true;
            pOneFrame = SfxViewFrame::GetNext( *pOneFrame );
        }
 
        if (bFound)
        {
            // We count on Activate working synchronously here
            // (pActiveViewSh is set while doing so)
            pRefViewSh->SetActive(); // Appear and SetViewFrame
 
            //  pLastState is set correctly in the NotifyChange from the Activate
        }
        else
        {
            OSL_FAIL("ViewFrame for reference input is not here anymore");
        }
    }
}
 
void ScInputHandler::RemoveSelection()
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (!pActiveView)
        return;
 
    ESelection aSel = pActiveView->GetSelection();
    aSel.nStartPara = aSel.nEndPara;
    aSel.nStartPos  = aSel.nEndPos;
    if (pTableView)
        pTableView->SetSelection( aSel );
    if (pTopView)
        pTopView->SetSelection( aSel );
}
 
void ScInputHandler::InvalidateAttribs()
{
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    if (pViewFrm)
    {
        SfxBindings& rBindings = pViewFrm->GetBindings();
 
        rBindings.Invalidate( SID_ATTR_CHAR_FONT );
        rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT );
        rBindings.Invalidate( SID_ATTR_CHAR_COLOR );
 
        rBindings.Invalidate( SID_ATTR_CHAR_WEIGHT );
        rBindings.Invalidate( SID_ATTR_CHAR_POSTURE );
        rBindings.Invalidate( SID_ATTR_CHAR_UNDERLINE );
        rBindings.Invalidate( SID_ATTR_CHAR_OVERLINE );
        rBindings.Invalidate( SID_ULINE_VAL_NONE );
        rBindings.Invalidate( SID_ULINE_VAL_SINGLE );
        rBindings.Invalidate( SID_ULINE_VAL_DOUBLE );
        rBindings.Invalidate( SID_ULINE_VAL_DOTTED );
 
        rBindings.Invalidate( SID_HYPERLINK_GETLINK );
 
        rBindings.Invalidate( SID_ATTR_CHAR_KERNING );
        rBindings.Invalidate( SID_SET_SUPER_SCRIPT );
        rBindings.Invalidate( SID_SET_SUB_SCRIPT );
        rBindings.Invalidate( SID_ATTR_CHAR_STRIKEOUT );
        rBindings.Invalidate( SID_ATTR_CHAR_SHADOWED );
    }
}
 
// --------------- public methods --------------------------------------------
 
void ScInputHandler::SetMode( ScInputMode eNewMode, const OUString* pInitText, ScEditEngineDefaulter* pTopEngine )
{
    if ( eMode == eNewMode )
        return;
 
    ImplCreateEditEngine();
 
    if (bProtected)
    {
        eMode = SC_INPUT_NONE;
        StopInputWinEngine( true );
        if (pActiveViewSh)
            pActiveViewSh->GetActiveWin()->GrabFocus();
        return;
    }
 
    if (eNewMode != SC_INPUT_NONE && pActiveViewSh)
        // Disable paste mode when edit mode starts.
        pActiveViewSh->GetViewData().SetPasteMode( ScPasteFlags::NONE );
 
    bInOwnChange = true; // disable ModifyHdl (reset below)
 
    ScInputMode eOldMode = eMode;
    eMode = eNewMode;
    if (eOldMode == SC_INPUT_TOP && eNewMode != eOldMode)
        StopInputWinEngine( false );
 
    if (eMode==SC_INPUT_TOP || eMode==SC_INPUT_TABLE)
    {
        if (eOldMode == SC_INPUT_NONE) // not if switching between modes
        {
            if (StartTable(0, false, eMode == SC_INPUT_TABLE, pTopEngine))
            {
                if (pActiveViewSh)
                    pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
            }
        }
 
        if (pInitText)
        {
            mpEditEngine->SetText(*pInitText);
            bModified = true;
        }
 
        sal_Int32 nPara = mpEditEngine->GetParagraphCount()-1;
        sal_Int32 nLen = mpEditEngine->GetText(nPara).getLength();
        sal_uInt16 nCount = mpEditEngine->GetViewCount();
 
        for (sal_uInt16 i=0; i<nCount; i++)
        {
            if ( eMode == SC_INPUT_TABLE && eOldMode == SC_INPUT_TOP )
            {
                // Keep Selection
            }
            else
            {
                mpEditEngine->GetView(i)->
                    SetSelection( ESelection( nPara, nLen, nPara, nLen ) );
            }
            mpEditEngine->GetView(i)->ShowCursor(false);
        }
    }
 
    UpdateActiveView();
    if (eMode==SC_INPUT_TABLE || eMode==SC_INPUT_TYPE)
    {
        if (pTableView)
            pTableView->SetEditEngineUpdateMode(true);
    }
    else
    {
        if (pTopView)
            pTopView->SetEditEngineUpdateMode(true);
    }
 
    if (eNewMode != eOldMode)
        UpdateFormulaMode();
 
    bInOwnChange = false;
}
 
/**
 * @return true if rString only contains digits (no autocorrect then)
 */
static bool lcl_IsNumber(const OUString& rString)
{
    sal_Int32 nLen = rString.getLength();
    for (sal_Int32 i=0; i<nLen; i++)
    {
        sal_Unicode c = rString[i];
        if ( c < '0' || c > '9' )
            return false;
    }
    return true;
}
 
static void lcl_SelectionToEnd( EditView* pView )
{
    if ( pView )
    {
        EditEngine* pEngine = pView->GetEditEngine();
        sal_Int32 nParCnt = pEngine->GetParagraphCount();
        if ( nParCnt == 0 )
            nParCnt = 1;
        ESelection aSel( nParCnt-1, pEngine->GetTextLen(nParCnt-1) ); // empty selection, cursor at the end
        pView->SetSelection( aSel );
    }
}
 
void ScInputHandler::EnterHandler( ScEnterMode nBlockMode )
{
    if (!mbDocumentDisposing && comphelper::LibreOfficeKit::isActive()
        && pActiveViewSh != SfxViewShell::Current())
        return;
 
    // Macro calls for validity can cause a lot of problems, so inhibit
    // nested calls of EnterHandler().
    if (bInEnterHandler) return;
    bInEnterHandler = true;
    bInOwnChange = true; // disable ModifyHdl (reset below)
 
    ImplCreateEditEngine();
 
    bool bMatrix = ( nBlockMode == ScEnterMode::MATRIX );
 
    SfxApplication* pSfxApp     = SfxGetpApp();
    std::unique_ptr<EditTextObject> pObject;
    std::unique_ptr<ScPatternAttr> pCellAttrs;
    bool            bForget     = false; // Remove due to validity?
 
    OUString aString = GetEditText(mpEditEngine.get());
    OUString aPreAutoCorrectString(aString);
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    if (bModified && pActiveView && !aString.isEmpty() && !lcl_IsNumber(aString))
    {
        if (pColumnData && miAutoPosColumn != pColumnData->end())
        {
            // #i47125# If AutoInput appended something, do the final AutoCorrect
            // with the cursor at the end of the input.
            lcl_SelectionToEnd(pTopView);
            lcl_SelectionToEnd(pTableView);
        }
 
        vcl::Window* pFrameWin = pActiveViewSh ? pActiveViewSh->GetFrameWin() : nullptr;
 
        if (pTopView)
            pTopView->CompleteAutoCorrect(); // CompleteAutoCorrect for both Views
        if (pTableView)
            pTableView->CompleteAutoCorrect(pFrameWin);
        aString = GetEditText(mpEditEngine.get());
    }
    lcl_RemoveTabs(aString);
    lcl_RemoveTabs(aPreAutoCorrectString);
 
    // Test if valid (always with simple string)
    if ( bModified && nValidation && pActiveViewSh )
    {
        ScDocument* pDoc = pActiveViewSh->GetViewData().GetDocument();
        const ScValidationData* pData = pDoc->GetValidationEntry( nValidation );
        if (pData && pData->HasErrMsg())
        {
            // #i67990# don't use pLastPattern in EnterHandler
            const ScPatternAttr* pPattern = pDoc->GetPattern( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
 
            bool bOk;
 
            if (pData->GetDataMode() == SC_VALID_CUSTOM)
            {
                bOk = pData->IsDataValidCustom( aString, *pPattern, aCursorPos,  ScValidationData::CustomValidationPrivateAccess() );
            }
            else
            {
                bOk = pData->IsDataValid( aString, *pPattern, aCursorPos );
            }
 
            if (!bOk)
            {
                if ( pActiveViewSh )                // If it came from MouseButtonDown
                    pActiveViewSh->StopMarking();   // (the InfoBox consumes the MouseButtonUp)
 
                vcl::Window* pParent = nullptr;
                if (pActiveViewSh)
                    pParent = &pActiveViewSh->GetViewFrame()->GetWindow();
                else
                    pParent = Application::GetDefDialogParent();
 
                if (pData->DoError(pParent ? pParent->GetFrameWeld() : nullptr, aString, aCursorPos))
                    bForget = true;                 // Do not take over input
            }
        }
    }
 
    // Check for input into DataPilot table
    if ( bModified && pActiveViewSh && !bForget )
    {
        ScDocument* pDoc = pActiveViewSh->GetViewData().GetDocument();
        ScDPObject* pDPObj = pDoc->GetDPAtCursor( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
        if ( pDPObj )
        {
            // Any input within the DataPilot table is either a valid renaming
            // or an invalid action - normal cell input is always aborted
            pActiveViewSh->DataPilotInput( aCursorPos, aString );
            bForget = true;
        }
    }
 
    std::vector<editeng::MisspellRanges> aMisspellRanges;
    mpEditEngine->CompleteOnlineSpelling();
    bool bSpellErrors = !bFormulaMode && mpEditEngine->HasOnlineSpellErrors();
    if ( bSpellErrors )
    {
        //  #i3820# If the spell checker flags numerical input as error,
        //  it still has to be treated as number, not EditEngine object.
        if ( pActiveViewSh )
        {
            ScDocument* pDoc = pActiveViewSh->GetViewData().GetDocument();
            // #i67990# don't use pLastPattern in EnterHandler
            const ScPatternAttr* pPattern = pDoc->GetPattern( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
            if (pPattern)
            {
                SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
                // without conditional format, as in ScColumn::SetString
                sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
                double nVal;
                if ( pFormatter->IsNumberFormat( aString, nFormat, nVal ) )
                {
                    bSpellErrors = false;       // ignore the spelling errors
                }
            }
        }
    }
 
    //  After RemoveAdjust, the EditView must not be repainted (has wrong font size etc).
    //  SetUpdateMode must come after CompleteOnlineSpelling.
    //  The view is hidden in any case below (Broadcast).
    mpEditEngine->SetUpdateMode( false );
 
    if ( bModified && !bForget ) // What is being entered (text/object)?
    {
        sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
        if ( nParCnt == 0 )
            nParCnt = 1;
 
        bool bUniformAttribs = true;
        SfxItemSet aPara1Attribs = mpEditEngine->GetAttribs(0, 0, mpEditEngine->GetTextLen(0));
        for (sal_Int32 nPara = 1; nPara < nParCnt; ++nPara)
        {
            SfxItemSet aPara2Attribs = mpEditEngine->GetAttribs(nPara, 0, mpEditEngine->GetTextLen(nPara));
            if (!(aPara1Attribs == aPara2Attribs))
            {
                // Paragraph format different from that of the 1st paragraph.
                bUniformAttribs = false;
                break;
            }
        }
 
        ESelection aSel( 0, 0, nParCnt-1, mpEditEngine->GetTextLen(nParCnt-1) );
        SfxItemSet aOldAttribs = mpEditEngine->GetAttribs( aSel );
        const SfxPoolItem* pItem = nullptr;
 
        // Find common (cell) attributes before RemoveAdjust
        if ( pActiveViewSh && bUniformAttribs )
        {
            SfxItemSet* pCommonAttrs = nullptr;
            for (sal_uInt16 nId = EE_CHAR_START; nId <= EE_CHAR_END; nId++)
            {
                SfxItemState eState = aOldAttribs.GetItemState( nId, false, &pItem );
                if ( eState == SfxItemState::SET &&
                        nId != EE_CHAR_ESCAPEMENT && nId != EE_CHAR_PAIRKERNING &&
                        nId != EE_CHAR_KERNING && nId != EE_CHAR_XMLATTRIBS &&
                            *pItem != pEditDefaults->Get(nId) )
                {
                    if ( !pCommonAttrs )
                        pCommonAttrs = new SfxItemSet( mpEditEngine->GetEmptyItemSet() );
                    pCommonAttrs->Put( *pItem );
                }
            }
 
            if ( pCommonAttrs )
            {
                ScDocument* pDoc = pActiveViewSh->GetViewData().GetDocument();
                pCellAttrs = o3tl::make_unique<ScPatternAttr>(pDoc->GetPool());
                pCellAttrs->GetFromEditItemSet( pCommonAttrs );
                delete pCommonAttrs;
            }
        }
 
        // Clear ParaAttribs (including adjustment)
        RemoveAdjust();
 
        bool bAttrib = false; // Formatting present?
 
        //  check if EditObject is needed
        if (nParCnt > 1)
            bAttrib = true;
        else
        {
            for (sal_uInt16 nId = EE_CHAR_START; nId <= EE_CHAR_END && !bAttrib; nId++)
            {
                SfxItemState eState = aOldAttribs.GetItemState( nId, false, &pItem );
                if (eState == SfxItemState::DONTCARE)
                    bAttrib = true;
                else if (eState == SfxItemState::SET)
                {
                    // Keep same items in EditEngine as in ScEditAttrTester
                    if ( nId == EE_CHAR_ESCAPEMENT || nId == EE_CHAR_PAIRKERNING ||
                         nId == EE_CHAR_KERNING || nId == EE_CHAR_XMLATTRIBS )
                    {
                        if ( *pItem != pEditDefaults->Get(nId) )
                            bAttrib = true;
                    }
                }
            }
 
            // Contains fields?
            SfxItemState eFieldState = aOldAttribs.GetItemState( EE_FEATURE_FIELD, false );
            if ( eFieldState == SfxItemState::DONTCARE || eFieldState == SfxItemState::SET )
                bAttrib = true;
 
            // Not converted characters?
            SfxItemState eConvState = aOldAttribs.GetItemState( EE_FEATURE_NOTCONV, false );
            if ( eConvState == SfxItemState::DONTCARE || eConvState == SfxItemState::SET )
                bAttrib = true;
 
            // Always recognize formulas as formulas
            // We still need the preceding test due to cell attributes
        }
 
        if (bSpellErrors)
            mpEditEngine->GetAllMisspellRanges(aMisspellRanges);
 
        if (bMatrix)
            bAttrib = false;
 
        if (bAttrib)
        {
            mpEditEngine->ClearSpellErrors();
            pObject = mpEditEngine->CreateTextObject();
        }
        else if (bAutoComplete) // Adjust Upper/Lower case
        {
            // Perform case-matching only when the typed text is partial.
            if (pColumnData && aAutoSearch.getLength() < aString.getLength())
                aString = getExactMatch(*pColumnData, aString);
        }
    }
 
    // Don't rely on ShowRefFrame switching the active view synchronously
    // execute the function directly on the correct view's bindings instead
    // pRefViewSh is reset in ShowRefFrame - get pointer before ShowRefFrame call
    ScTabViewShell* pExecuteSh = pRefViewSh ? pRefViewSh : pActiveViewSh;
 
    if (bFormulaMode)
    {
        ShowRefFrame();
 
        if (pExecuteSh)
        {
            pExecuteSh->SetTabNo(aCursorPos.Tab());
            pExecuteSh->ActiveGrabFocus();
        }
 
        bFormulaMode = false;
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
        SC_MOD()->SetRefInputHdl(nullptr);
        if (pInputWin)
            pInputWin->SetFormulaMode(false);
        UpdateAutoCorrFlag();
    }
    pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
    DeleteRangeFinder();
    ResetAutoPar();
 
    bool bOldMod = bModified;
 
    bModified = false;
    bSelIsRef = false;
    eMode     = SC_INPUT_NONE;
    StopInputWinEngine(true);
 
    // Text input (through number formats) or ApplySelectionPattern modify
    // the cell's attributes, so pLastPattern is no longer valid
    pLastPattern = nullptr;
 
    if (bOldMod && !bProtected && !bForget)
    {
        bool bInsertPreCorrectedString = true;
        // No typographic quotes in formulas
        if (aString.startsWith("="))
        {
            SvxAutoCorrect* pAuto = SvxAutoCorrCfg::Get().GetAutoCorrect();
            if ( pAuto )
            {
                bInsertPreCorrectedString = false;
                OUString aReplace(pAuto->GetStartDoubleQuote());
                if( aReplace.isEmpty() )
                    aReplace = ScGlobal::pLocaleData->getDoubleQuotationMarkStart();
                if( aReplace != "\"" )
                    aString = aString.replaceAll( aReplace, "\"" );
 
                aReplace = OUString(pAuto->GetEndDoubleQuote());
                if( aReplace.isEmpty() )
                    aReplace = ScGlobal::pLocaleData->getDoubleQuotationMarkEnd();
                if( aReplace != "\"" )
                    aString = aString.replaceAll( aReplace, "\"" );
 
                aReplace = OUString(pAuto->GetStartSingleQuote());
                if( aReplace.isEmpty() )
                    aReplace = ScGlobal::pLocaleData->getQuotationMarkStart();
                if( aReplace != "'" )
                    aString = aString.replaceAll( aReplace, "'" );
 
                aReplace = OUString(pAuto->GetEndSingleQuote());
                if( aReplace.isEmpty() )
                    aReplace = ScGlobal::pLocaleData->getQuotationMarkEnd();
                if( aReplace != "'" )
                    aString = aString.replaceAll( aReplace, "'");
            }
        }
 
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScKillEditViewNoPaint ) );
 
        if ( pExecuteSh )
        {
            SfxBindings& rBindings = pExecuteSh->GetViewFrame()->GetBindings();
 
            sal_uInt16 nId = FID_INPUTLINE_ENTER;
            if ( nBlockMode == ScEnterMode::BLOCK )
                nId = FID_INPUTLINE_BLOCK;
            else if ( nBlockMode == ScEnterMode::MATRIX )
                nId = FID_INPUTLINE_MATRIX;
 
            const SfxPoolItem* aArgs[2];
            aArgs[1] = nullptr;
 
            if ( bInsertPreCorrectedString && aString != aPreAutoCorrectString )
            {
               ScInputStatusItem aItem(FID_INPUTLINE_STATUS,
                                       aCursorPos, aCursorPos, aCursorPos,
                                       aPreAutoCorrectString, pObject.get());
                aArgs[0] = &aItem;
                rBindings.Execute(nId, aArgs);
            }
 
            ScInputStatusItem aItemCorrected(FID_INPUTLINE_STATUS,
                                             aCursorPos, aCursorPos, aCursorPos,
                                             aString, pObject.get());
            if ( !aMisspellRanges.empty() )
                aItemCorrected.SetMisspellRanges(&aMisspellRanges);
 
            aArgs[0] = &aItemCorrected;
            rBindings.Execute(nId, aArgs);
        }
 
        pLastState.reset(); // pLastState still contains the old text
    }
    else
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScKillEditView ) );
 
    if ( bOldMod && pExecuteSh && pCellAttrs && !bForget )
    {
        // Combine with input?
        pExecuteSh->ApplySelectionPattern( *pCellAttrs, true );
        pExecuteSh->AdjustBlockHeight();
    }
 
    HideTip();
    HideTipBelow();
 
    nFormSelStart = nFormSelEnd = 0;
    aFormText.clear();
 
    bInOwnChange = false;
    bInEnterHandler = false;
}
 
void ScInputHandler::CancelHandler()
{
    bInOwnChange = true; // Also without FormulaMode due to FunctionsAutoPilot
 
    ImplCreateEditEngine();
 
    bModified = false;
 
    // Don't rely on ShowRefFrame switching the active view synchronously
    // execute the function directly on the correct view's bindings instead
    // pRefViewSh is reset in ShowRefFrame - get pointer before ShowRefFrame call
    ScTabViewShell* pExecuteSh = pRefViewSh ? pRefViewSh : pActiveViewSh;
 
    if (bFormulaMode)
    {
        ShowRefFrame();
        if (pExecuteSh)
        {
            pExecuteSh->SetTabNo(aCursorPos.Tab());
            pExecuteSh->ActiveGrabFocus();
        }
        bFormulaMode = false;
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
        SC_MOD()->SetRefInputHdl(nullptr);
        if (pInputWin)
            pInputWin->SetFormulaMode(false);
        UpdateAutoCorrFlag();
    }
    pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
    DeleteRangeFinder();
    ResetAutoPar();
 
    eMode = SC_INPUT_NONE;
    StopInputWinEngine( true );
    if (pExecuteSh)
        pExecuteSh->StopEditShell();
 
    aCursorPos.Set(MAXCOL+1,0,0); // Invalid flag
    mpEditEngine->SetText(OUString());
 
    if ( !pLastState && pExecuteSh )
        pExecuteSh->UpdateInputHandler( true );  // Update status again
    else
        NotifyChange( pLastState.get(), true );
 
    nFormSelStart = nFormSelEnd = 0;
    aFormText.clear();
 
    bInOwnChange = false;
}
 
bool ScInputHandler::IsModalMode( const SfxObjectShell* pDocSh )
{
    // References to unnamed document; that doesn't work
    return bFormulaMode && pRefViewSh
            && pRefViewSh->GetViewData().GetDocument()->GetDocumentShell() != pDocSh
            && !pDocSh->HasName();
}
 
void ScInputHandler::AddRefEntry()
{
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    UpdateActiveView();
    if (!pTableView && !pTopView)
        return;                             // E.g. FillMode
 
    DataChanging();                         // Cannot be new
 
    RemoveSelection();
    OUString aText = GetEditText(mpEditEngine.get());
    sal_Unicode cLastChar = 0;
    sal_Int32 nPos = aText.getLength() - 1;
    while (nPos >= 0 && ((cLastChar = aText[nPos]) == ' ')) //checking space
        --nPos;
 
    bool bAppendSeparator = (cLastChar != '(' && cLastChar != cSep && cLastChar != '=');
    if (bAppendSeparator)
    {
        if (pTableView)
            pTableView->InsertText( OUString(cSep) );
        if (pTopView)
            pTopView->InsertText( OUString(cSep) );
    }
 
    DataChanged();
}
 
void ScInputHandler::SetReference( const ScRange& rRef, const ScDocument* pDoc )
{
    HideTip();
 
    bool bOtherDoc = ( pRefViewSh &&
                        pRefViewSh->GetViewData().GetDocument() != pDoc );
    if (bOtherDoc)
        if (!pDoc->GetDocumentShell()->HasName())
        {
            // References to unnamed document; that doesn't work
            // SetReference should not be called, then
            return;
        }
 
    UpdateActiveView();
    if (!pTableView && !pTopView)
        return;                             // E.g. FillMode
 
    // Never overwrite the "="!
    EditView* pActiveView = pTopView ? pTopView : pTableView;
    ESelection aSel = pActiveView->GetSelection();
    aSel.Adjust();
    if ( aSel.nStartPara == 0 && aSel.nStartPos == 0 )
        return;
 
    DataChanging();                         // Cannot be new
 
    // Turn around selection if backwards (TODO: Do we really need to do that?)
    if (pTableView)
    {
        ESelection aTabSel = pTableView->GetSelection();
        if (aTabSel.nStartPos > aTabSel.nEndPos && aTabSel.nStartPara == aTabSel.nEndPara)
        {
            aTabSel.Adjust();
            pTableView->SetSelection(aTabSel);
        }
    }
    if (pTopView)
    {
        ESelection aTopSel = pTopView->GetSelection();
        if (aTopSel.nStartPos > aTopSel.nEndPos && aTopSel.nStartPara == aTopSel.nEndPara)
        {
            aTopSel.Adjust();
            pTopView->SetSelection(aTopSel);
        }
    }
 
    // Create string from reference
    OUString aRefStr;
    const ScAddress::Details aAddrDetails( pDoc, aCursorPos );
    if (bOtherDoc)
    {
        // Reference to other document
        OSL_ENSURE(rRef.aStart.Tab()==rRef.aEnd.Tab(), "nStartTab!=nEndTab");
 
        // Always 3D and absolute.
        OUString aTmp(rRef.Format( ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, pDoc, aAddrDetails));
 
        SfxObjectShell* pObjSh = pDoc->GetDocumentShell();
        // #i75893# convert escaped URL of the document to something user friendly
        OUString aFileName = pObjSh->GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::Unambiguous );
 
        switch(aAddrDetails.eConv)
        {
             case formula::FormulaGrammar::CONV_XL_A1 :
             case formula::FormulaGrammar::CONV_XL_OOX :
             case formula::FormulaGrammar::CONV_XL_R1C1 :
                         aRefStr = "[\'" + aFileName + "']";
                         break;
             case formula::FormulaGrammar::CONV_OOO :
             default:
                         aRefStr = "\'" + aFileName + "'#";
                         break;
        }
        aRefStr += aTmp;
    }
    else
    {
        if ( rRef.aStart.Tab() != aCursorPos.Tab() ||
             rRef.aStart.Tab() != rRef.aEnd.Tab() )
            // pointer-selected => absolute sheet reference
            aRefStr = rRef.Format(ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, pDoc, aAddrDetails);
        else
            aRefStr = rRef.Format(ScRefFlags::VALID, pDoc, aAddrDetails);
    }
 
    if (pTableView || pTopView)
    {
        if (pTableView)
            pTableView->InsertText( aRefStr, true );
        if (pTopView)
            pTopView->InsertText( aRefStr, true );
 
        DataChanged();
    }
 
    bSelIsRef = true;
}
 
void ScInputHandler::InsertFunction( const OUString& rFuncName, bool bAddPar )
{
    if ( eMode == SC_INPUT_NONE )
    {
        OSL_FAIL("InsertFunction, not during input mode");
        return;
    }
 
    UpdateActiveView();
    if (!pTableView && !pTopView)
        return;                             // E.g. FillMode
 
    DataChanging();                         // Cannot be new
 
    OUString aText = rFuncName;
    if (bAddPar)
        aText += "()";
 
    if (pTableView)
    {
        pTableView->InsertText( aText );
        if (bAddPar)
        {
            ESelection aSel = pTableView->GetSelection();
            --aSel.nStartPos;
            --aSel.nEndPos;
            pTableView->SetSelection(aSel);
        }
    }
    if (pTopView)
    {
        pTopView->InsertText( aText );
        if (bAddPar)
        {
            ESelection aSel = pTopView->GetSelection();
            --aSel.nStartPos;
            --aSel.nEndPos;
            pTopView->SetSelection(aSel);
        }
    }
 
    DataChanged();
 
    if (bAddPar)
        AutoParAdded();
}
 
void ScInputHandler::ClearText()
{
    if ( eMode == SC_INPUT_NONE )
    {
        OSL_FAIL("ClearText, not during input mode");
        return;
    }
 
    UpdateActiveView();
    if (!pTableView && !pTopView)
        return;                             // E.g. FillMode
 
    DataChanging();                         // Cannot be new
 
    if (pTableView)
    {
        pTableView->GetEditEngine()->SetText( "" );
        pTableView->SetSelection( ESelection(0,0, 0,0) );
    }
    if (pTopView)
    {
        pTopView->GetEditEngine()->SetText( "" );
        pTopView->SetSelection( ESelection(0,0, 0,0) );
    }
 
    DataChanged();
}
 
bool ScInputHandler::KeyInput( const KeyEvent& rKEvt, bool bStartEdit /* = false */ )
{
    if (!bOptLoaded)
    {
        bAutoComplete = SC_MOD()->GetAppOptions().GetAutoComplete();
        bOptLoaded = true;
    }
 
    vcl::KeyCode aCode = rKEvt.GetKeyCode();
    sal_uInt16 nModi  = aCode.GetModifier();
    bool bShift   = aCode.IsShift();
    bool bControl = aCode.IsMod1();
    bool bAlt     = aCode.IsMod2();
    sal_uInt16 nCode  = aCode.GetCode();
    sal_Unicode nChar = rKEvt.GetCharCode();
 
    if (bAlt && !bControl && nCode != KEY_RETURN)
        // Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT are not.
        return false;
 
    if (!bControl && nCode == KEY_TAB)
    {
        // Normal TAB moves the cursor right.
        EnterHandler();
 
        if (pActiveViewSh)
            pActiveViewSh->FindNextUnprot( bShift, true );
        return true;
    }
 
    bool bInputLine = ( eMode==SC_INPUT_TOP );
 
    bool bUsed = false;
    bool bSkip = false;
    bool bDoEnter = false;
 
    switch ( nCode )
    {
        case KEY_RETURN:
            // New line when in the input line and Shift/Ctrl-Enter is pressed,
            // or when in a cell and Ctrl-Enter is pressed.
            if ((pInputWin && bInputLine && bControl != bShift) || (!bInputLine && bControl && !bShift))
            {
                bDoEnter = true;
            }
            else if (nModi == 0 && nTipVisible && pFormulaData && miAutoPosFormula != pFormulaData->end())
            {
                PasteFunctionData();
                bUsed = true;
            }
            else if ( nModi == 0 && nTipVisible && !aManualTip.isEmpty() )
            {
                PasteManualTip();
                bUsed = true;
            }
            else
            {
                ScEnterMode nMode = ScEnterMode::NORMAL;
                if ( bShift && bControl )
                    nMode = ScEnterMode::MATRIX;
                else if ( bAlt )
                    nMode = ScEnterMode::BLOCK;
                EnterHandler( nMode );
 
                if (pActiveViewSh)
                    pActiveViewSh->MoveCursorEnter( bShift && !bControl );
 
                bUsed = true;
            }
            break;
        case KEY_TAB:
            if (bControl && !bAlt)
            {
                if (pFormulaData && nTipVisible && miAutoPosFormula != pFormulaData->end())
                {
                    // Iterate
                    NextFormulaEntry( bShift );
                    bUsed = true;
                }
                else if (pColumnData && bUseTab && miAutoPosColumn != pColumnData->end())
                {
                    // Iterate through AutoInput entries
                    NextAutoEntry( bShift );
                    bUsed = true;
                }
            }
            break;
        case KEY_ESCAPE:
            if ( nTipVisible )
            {
                HideTip();
                bUsed = true;
            }
            else if( nTipVisibleSec )
            {
                HideTipBelow();
                bUsed = true;
            }
            else if (eMode != SC_INPUT_NONE)
            {
                CancelHandler();
                bUsed = true;
            }
            else
                bSkip = true;
            break;
        case KEY_F2:
            if ( !bShift && !bControl && !bAlt && eMode == SC_INPUT_TABLE )
            {
                eMode = SC_INPUT_TYPE;
                bUsed = true;
            }
            break;
    }
 
    // Only execute cursor keys if already in EditMode
    // E.g. due to Shift-Ctrl-PageDn (not defined as an accelerator)
    bool bCursorKey = EditEngine::DoesKeyMoveCursor(rKEvt);
    bool bInsKey = ( nCode == KEY_INSERT && !nModi ); // Treat Insert like Cursorkeys
    if ( !bUsed && !bSkip && ( bDoEnter || EditEngine::DoesKeyChangeText(rKEvt) ||
                    ( eMode != SC_INPUT_NONE && ( bCursorKey || bInsKey ) ) ) )
    {
        HideTip();
        HideTipBelow();
 
        if (bSelIsRef)
        {
            RemoveSelection();
            bSelIsRef = false;
        }
 
        UpdateActiveView();
        bool bNewView = DataChanging( nChar );
 
        if (bProtected)                             // Protected cell?
            bUsed = true;                           // Don't forward KeyEvent
        else                                        // Changes allowed
        {
            if (bNewView )                          // Create anew
            {
                if (pActiveViewSh)
                    pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
                UpdateActiveView();
                if (eMode==SC_INPUT_NONE)
                    if (pTableView || pTopView)
                    {
                        OUString aStrLoP;
 
                        if ( bStartEdit && bCellHasPercentFormat && ((nChar >= '0' && nChar <= '9') || nChar == '-') )
                            aStrLoP = "%";
 
                        if (pTableView)
                        {
                            pTableView->GetEditEngine()->SetText( aStrLoP );
                            if ( !aStrLoP.isEmpty() )
                                pTableView->SetSelection( ESelection(0,0, 0,0) );   // before the '%'
 
                            // Don't call SetSelection if the string is empty anyway,
                            // to avoid breaking the bInitial handling in ScViewData::EditGrowY
                        }
                        if (pTopView)
                        {
                            pTopView->GetEditEngine()->SetText( aStrLoP );
                            if ( !aStrLoP.isEmpty() )
                                pTopView->SetSelection( ESelection(0,0, 0,0) );     // before the '%'
                        }
                    }
                SyncViews();
            }
 
            if (pTableView || pTopView)
            {
                if (bDoEnter)
                {
                    if (pTableView)
                        if( pTableView->PostKeyEvent( KeyEvent( '\r', vcl::KeyCode(KEY_RETURN) ) ) )
                            bUsed = true;
                    if (pTopView)
                        if( pTopView->PostKeyEvent( KeyEvent( '\r', vcl::KeyCode(KEY_RETURN) ) ) )
                            bUsed = true;
                }
                else if ( nAutoPar && nChar == ')' && CursorAtClosingPar() )
                {
                    SkipClosingPar();
                    bUsed = true;
                }
                else
                {
                    if (pTableView)
                    {
                        if (pTopView)
                            pTableView->SetControlWord(pTableView->GetControlWord() | EVControlBits::SINGLELINEPASTE);
 
                        vcl::Window* pFrameWin = pActiveViewSh ? pActiveViewSh->GetFrameWin() : nullptr;
                        if ( pTableView->PostKeyEvent( rKEvt, pFrameWin ) )
                            bUsed = true;
 
                        pTableView->SetControlWord(pTableView->GetControlWord() & ~EVControlBits::SINGLELINEPASTE);
                    }
                    if (pTopView)
                    {
                        if ( bUsed && rKEvt.GetKeyCode().GetFunction() == KeyFuncType::CUT )
                            pTopView->DeleteSelected();
                        else if ( pTopView->PostKeyEvent( rKEvt ) )
                            bUsed = true;
                    }
                }
 
                // AutoInput:
                if ( bUsed && bAutoComplete )
                {
                    bUseTab = false;
                    if (pFormulaData)
                        miAutoPosFormula = pFormulaData->end();     // do not search further
                    if (pColumnData)
                        miAutoPosColumn = pColumnData->end();
 
                    KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
                    if ( nChar && nChar != 8 && nChar != 127 &&     // no 'backspace', no 'delete'
                         KeyFuncType::CUT != eFunc)                      // and no 'CTRL-X'
                    {
                        if (bFormulaMode)
                            UseFormulaData();
                        else
                            UseColData();
                    }
                }
 
                // When the selection is changed manually or an opening parenthesis
                // is typed, stop overwriting parentheses
                if ( bUsed && nChar == '(' )
                    ResetAutoPar();
 
                if ( KEY_INSERT == nCode )
                {
                    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                    if (pViewFrm)
                        pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT );
                }
                if( bUsed && bFormulaMode && ( bCursorKey || bInsKey || nCode == KEY_DELETE || nCode == KEY_BACKSPACE ) )
                {
                    ShowTipCursor();
                }
                if( bUsed && bFormulaMode && nCode == KEY_BACKSPACE )
                {
                    UseFormulaData();
                }
 
            }
 
            // #i114511# don't count cursor keys as modification
            bool bSetModified = !bCursorKey;
            DataChanged(false, bSetModified); // also calls UpdateParenthesis()
            InvalidateAttribs();        //! in DataChanged?
        }
    }
 
    if (pTopView && eMode != SC_INPUT_NONE)
        SyncViews();
 
    return bUsed;
}
 
void ScInputHandler::InputCommand( const CommandEvent& rCEvt )
{
    if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
    {
        // For CommandEventId::CursorPos, do as little as possible, because
        // with remote VCL, even a ShowCursor will generate another event.
        if ( eMode != SC_INPUT_NONE )
        {
            UpdateActiveView();
            if (pTableView || pTopView)
            {
                if (pTableView)
                    pTableView->Command( rCEvt );
                else if (pTopView)                      // call only once
                    pTopView->Command( rCEvt );
            }
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
    {
        if ( eMode != SC_INPUT_NONE )
        {
            UpdateActiveView();
            if (pTableView || pTopView)
            {
                if (pTableView)
                    pTableView->Command( rCEvt );
                else if (pTopView)                      // call only once
                    pTopView->Command( rCEvt );
            }
        }
    }
    else
    {
        if (!bOptLoaded)
        {
            bAutoComplete = SC_MOD()->GetAppOptions().GetAutoComplete();
            bOptLoaded = true;
        }
 
        HideTip();
        HideTipBelow();
 
        if ( bSelIsRef )
        {
            RemoveSelection();
            bSelIsRef = false;
        }
 
        UpdateActiveView();
        bool bNewView = DataChanging( 0, true );
 
        if (!bProtected)                            // changes allowed
        {
            if (bNewView)                           // create new edit view
            {
                if (pActiveViewSh)
                    pActiveViewSh->GetViewData().GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
                UpdateActiveView();
                if (eMode==SC_INPUT_NONE)
                    if (pTableView || pTopView)
                    {
                        OUString aStrLoP;
                        if (pTableView)
                        {
                            pTableView->GetEditEngine()->SetText( aStrLoP );
                            pTableView->SetSelection( ESelection(0,0, 0,0) );
                        }
                        if (pTopView)
                        {
                            pTopView->GetEditEngine()->SetText( aStrLoP );
                            pTopView->SetSelection( ESelection(0,0, 0,0) );
                        }
                    }
                SyncViews();
            }
 
            if (pTableView || pTopView)
            {
                if (pTableView)
                    pTableView->Command( rCEvt );
                if (pTopView)
                    pTopView->Command( rCEvt );
 
                if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
                {
                    //  AutoInput after ext text input
 
                    if (pFormulaData)
                        miAutoPosFormula = pFormulaData->end();
                    if (pColumnData)
                        miAutoPosColumn = pColumnData->end();
 
                    if (bFormulaMode)
                        UseFormulaData();
                    else
                        UseColData();
                }
            }
 
            DataChanged();              //  calls UpdateParenthesis()
            InvalidateAttribs();        //! in DataChanged ?
        }
 
        if (pTopView && eMode != SC_INPUT_NONE)
            SyncViews();
    }
}
 
void ScInputHandler::NotifyChange( const ScInputHdlState* pState,
                                   bool bForce, ScTabViewShell* pSourceSh,
                                   bool bStopEditing)
{
    // If the call originates from a macro call in the EnterHandler,
    // return immediately and don't mess up the status
    if (bInEnterHandler)
        return;
 
    bool bRepeat = (pState == pLastState.get());
    if (!bRepeat && pState && pLastState)
        bRepeat = (*pState == *pLastState);
    if (bRepeat && !bForce)
        return;
 
    bInOwnChange = true;                // disable ModifyHdl (reset below)
 
    if ( pState && !pLastState )        // Enable again
        bForce = true;
 
    bool bHadObject = pLastState && pLastState->GetEditData();
 
    //! Before EditEngine gets eventually created (so it gets the right pools)
    if ( pSourceSh )
        pActiveViewSh = pSourceSh;
    else
        pActiveViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() );
 
    ImplCreateEditEngine();
 
    if ( pState != pLastState.get() )
    {
        pLastState.reset( pState ? new ScInputHdlState( *pState ) : nullptr);
    }
 
    if ( pState && pActiveViewSh )
    {
        ScModule* pScMod = SC_MOD();
 
        if ( pState )
        {
 
            // Also take foreign reference input into account here (e.g. FunctionsAutoPilot),
            // FormEditData, if we're switching from Help to Calc:
            if ( !bFormulaMode && !pScMod->IsFormulaMode() && !pScMod->GetFormEditData() )
            {
                bool bIgnore = false;
                if ( bModified )
                {
                    if (pState->GetPos() != aCursorPos)
                    {
                        if (!bProtected)
                            EnterHandler();
                    }
                    else
                        bIgnore = true;
                }
 
                if ( !bIgnore )
                {
                    const ScAddress&        rSPos   = pState->GetStartPos();
                    const ScAddress&        rEPos   = pState->GetEndPos();
                    const EditTextObject*   pData   = pState->GetEditData();
                    OUString aString = pState->GetString();
                    bool bTxtMod = false;
                    ScDocShell* pDocSh = pActiveViewSh->GetViewData().GetDocShell();
                    ScDocument& rDoc = pDocSh->GetDocument();
 
                    aCursorPos  = pState->GetPos();
 
                    if ( pData )
                        bTxtMod = true;
                    else if ( bHadObject )
                        bTxtMod = true;
                    else if ( bTextValid )
                        bTxtMod = ( aString != aCurrentText );
                    else
                        bTxtMod = ( aString != GetEditText(mpEditEngine.get()) );
 
                    if ( bTxtMod || bForce )
                    {
                        if (pData)
                        {
                            mpEditEngine->SetText( *pData );
                            if (pInputWin)
                                aString = ScEditUtil::GetMultilineString(*mpEditEngine);
                            else
                                aString = GetEditText(mpEditEngine.get());
                            lcl_RemoveTabs(aString);
                            bTextValid = false;
                            aCurrentText.clear();
                        }
                        else
                        {
                            aCurrentText = aString;
                            bTextValid = true;              //! To begin with remember as a string
                        }
 
                        if ( pInputWin )
                            pInputWin->SetTextString(aString);
                        else if (comphelper::LibreOfficeKit::isActive())
                        {
                            if (pActiveViewSh)
                                pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aString.toUtf8().getStr());
                        }
                    }
 
                    if ( pInputWin || comphelper::LibreOfficeKit::isActive())                        // Named range input
                    {
                        OUString aPosStr;
                        const ScAddress::Details aAddrDetails( &rDoc, aCursorPos );
 
                        // Is the range a name?
                        //! Find by Timer?
                        if ( pActiveViewSh )
                            pActiveViewSh->GetViewData().GetDocument()->
                                GetRangeAtBlock( ScRange( rSPos, rEPos ), &aPosStr );
 
                        if ( aPosStr.isEmpty() )           // Not a name -> format
                        {
                            ScRefFlags nFlags = ScRefFlags::ZERO;
                            if( aAddrDetails.eConv == formula::FormulaGrammar::CONV_XL_R1C1 )
                                nFlags |= ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS;
                            if ( rSPos != rEPos )
                            {
                                ScRange r(rSPos, rEPos);
                                applyStartToEndFlags(nFlags);
                                aPosStr = r.Format(ScRefFlags::VALID | nFlags, &rDoc, aAddrDetails);
                            }
                            else
                                aPosStr = aCursorPos.Format(ScRefFlags::VALID | nFlags, &rDoc, aAddrDetails);
                        }
 
                        if (pInputWin)
                        {
                            // Disable the accessible VALUE_CHANGE event
                            bool bIsSuppressed = pInputWin->IsAccessibilityEventsSuppressed(false);
                            pInputWin->SetAccessibilityEventsSuppressed(true);
                            pInputWin->SetPosString(aPosStr);
                            pInputWin->SetAccessibilityEventsSuppressed(bIsSuppressed);
                            pInputWin->SetSumAssignMode();
                        }
                        else if (pActiveViewSh)
                        {
                            pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_ADDRESS, aPosStr.toUtf8().getStr());
                        }
                    }
 
                    if (bStopEditing)
                        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScKillEditView ) );
 
                    //  As long as the content is not edited, turn off online spelling.
                    //  Online spelling is turned back on in StartTable, after setting
                    //  the right language from cell attributes.
 
                    EEControlBits nCntrl = mpEditEngine->GetControlWord();
                    if ( nCntrl & EEControlBits::ONLINESPELLING )
                        mpEditEngine->SetControlWord( nCntrl & ~EEControlBits::ONLINESPELLING );
 
                    bModified = false;
                    bSelIsRef = false;
                    bProtected = false;
                    bCommandErrorShown = false;
                }
            }
        }
 
        if ( pInputWin)
        {
            // Do not enable if RefDialog is open
            if(!pScMod->IsFormulaMode()&& !pScMod->IsRefDialogOpen())
            {
                if ( !pInputWin->IsEnabled())
                {
                    pDelayTimer->Stop();
                    pInputWin->Enable();
                }
            }
            else if(pScMod->IsRefDialogOpen())
            {   // Because every document has its own InputWin,
                // we should start Timer again, because the input line may
                // still be active
                if ( !pDelayTimer->IsActive() )
                    pDelayTimer->Start();
            }
        }
    }
    else // !pState || !pActiveViewSh
    {
        if ( !pDelayTimer->IsActive() )
            pDelayTimer->Start();
    }
 
    HideTip();
    HideTipBelow();
    bInOwnChange = false;
}
 
void ScInputHandler::UpdateCellAdjust( SvxCellHorJustify eJust )
{
    eAttrAdjust = eJust;
    UpdateAdjust( 0 );
}
 
void ScInputHandler::ResetDelayTimer()
{
    if( pDelayTimer->IsActive() )
    {
        pDelayTimer->Stop();
        if ( pInputWin )
            pInputWin->Enable();
    }
}
 
IMPL_LINK_NOARG( ScInputHandler, DelayTimer, Timer*, void )
{
    if ( nullptr == pLastState || SC_MOD()->IsFormulaMode() || SC_MOD()->IsRefDialogOpen())
    {
        //! New method at ScModule to query if function autopilot is open
        SfxViewFrame* pViewFrm = SfxViewFrame::Current();
        if ( pViewFrm && pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) )
        {
            if ( pInputWin)
            {
                pInputWin->EnableButtons( false );
                pInputWin->Disable();
            }
        }
        else if ( !bFormulaMode ) // Keep formula e.g. for help
        {
            bInOwnChange = true; // disable ModifyHdl (reset below)
 
            pActiveViewSh = nullptr;
            mpEditEngine->SetText( EMPTY_OUSTRING );
            if ( pInputWin )
            {
                pInputWin->SetPosString( EMPTY_OUSTRING );
                pInputWin->SetTextString( EMPTY_OUSTRING );
                pInputWin->Disable();
            }
 
            bInOwnChange = false;
        }
    }
}
 
void ScInputHandler::InputSelection( const EditView* pView )
{
    SyncViews( pView );
    ShowTipCursor();
    UpdateParenthesis(); // Selection changed -> update parentheses highlighting
 
    // When the selection is changed manually, stop overwriting parentheses
    ResetAutoPar();
}
 
void ScInputHandler::InputChanged( const EditView* pView, bool bFromNotify )
{
    UpdateActiveView();
 
    // #i20282# DataChanged needs to know if this is from the input line's modify handler
    bool bFromTopNotify = ( bFromNotify && pView == pTopView );
 
    bool bNewView = DataChanging();                     //FIXME: Is this at all possible?
    aCurrentText = pView->GetEditEngine()->GetText();   // Also remember the string
    mpEditEngine->SetText( aCurrentText );
    DataChanged( bFromTopNotify );
    bTextValid = true; // Is set to false in DataChanged
 
    if ( pActiveViewSh )
    {
        ScViewData& rViewData = pActiveViewSh->GetViewData();
        if ( bNewView )
            rViewData.GetDocShell()->PostEditView( mpEditEngine.get(), aCursorPos );
 
        rViewData.EditGrowY();
        rViewData.EditGrowX();
    }
 
    SyncViews( pView );
}
 
const OUString& ScInputHandler::GetEditString()
{
    if (mpEditEngine)
    {
        aCurrentText = mpEditEngine->GetText(); // Always new from Engine
        bTextValid = true;
    }
 
    return aCurrentText;
}
 
Size ScInputHandler::GetTextSize()
{
    Size aSize;
    if ( mpEditEngine )
        aSize = Size( mpEditEngine->CalcTextWidth(), mpEditEngine->GetTextHeight() );
 
    return aSize;
}
 
bool ScInputHandler::GetTextAndFields( ScEditEngineDefaulter& rDestEngine )
{
    bool bRet = false;
    if (mpEditEngine)
    {
        // Contains field?
        sal_Int32 nParCnt = mpEditEngine->GetParagraphCount();
        SfxItemSet aSet = mpEditEngine->GetAttribs( ESelection(0,0,nParCnt,0) );
        SfxItemState eFieldState = aSet.GetItemState( EE_FEATURE_FIELD, false );
        if ( eFieldState == SfxItemState::DONTCARE || eFieldState == SfxItemState::SET )
        {
            // Copy content
            std::unique_ptr<EditTextObject> pObj = mpEditEngine->CreateTextObject();
            rDestEngine.SetText(*pObj);
            pObj.reset();
 
            // Delete attributes
            for (sal_Int32 i=0; i<nParCnt; i++)
                rDestEngine.RemoveCharAttribs( i );
 
            // Combine paragraphs
            while ( nParCnt > 1 )
            {
                sal_Int32 nLen = rDestEngine.GetTextLen( 0 );
                ESelection aSel( 0,nLen, 1,0 );
                rDestEngine.QuickInsertText( OUString(' '), aSel ); // Replace line break with space
                --nParCnt;
            }
 
            bRet = true;
        }
    }
    return bRet;
}
 
/**
 * Methods for FunctionAutoPilot:
 * InputGetSelection, InputSetSelection, InputReplaceSelection, InputGetFormulaStr
 */
void ScInputHandler::InputGetSelection( sal_Int32& rStart, sal_Int32& rEnd )
{
    rStart = nFormSelStart;
    rEnd = nFormSelEnd;
}
 
EditView* ScInputHandler::GetFuncEditView()
{
    UpdateActiveView(); // Due to pTableView
 
    EditView* pView = nullptr;
    if ( pInputWin )
    {
        pInputWin->MakeDialogEditView();
        pView = pInputWin->GetEditView();
    }
    else
    {
        if ( eMode != SC_INPUT_TABLE )
        {
            bCreatingFuncView = true; // Don't display RangeFinder
            SetMode( SC_INPUT_TABLE );
            bCreatingFuncView = false;
            if ( pTableView )
                pTableView->GetEditEngine()->SetText( EMPTY_OUSTRING );
        }
        pView = pTableView;
    }
 
    return pView;
}
 
void ScInputHandler::InputSetSelection( sal_Int32 nStart, sal_Int32 nEnd )
{
    if ( nStart <= nEnd )
    {
        nFormSelStart = nStart;
        nFormSelEnd = nEnd;
    }
    else
    {
        nFormSelEnd = nStart;
        nFormSelStart = nEnd;
    }
 
    EditView* pView = GetFuncEditView();
    if (pView)
        pView->SetSelection( ESelection(0,nStart, 0,nEnd) );
 
    bModified = true;
}
 
void ScInputHandler::InputReplaceSelection( const OUString& rStr )
{
    if (!pRefViewSh)
        pRefViewSh = pActiveViewSh;
 
    OSL_ENSURE(nFormSelEnd>=nFormSelStart,"Selection broken...");
 
    sal_Int32 nOldLen = nFormSelEnd - nFormSelStart;
    sal_Int32 nNewLen = rStr.getLength();
 
    OUStringBuffer aBuf(aFormText);
    if (nOldLen)
        aBuf.remove(nFormSelStart, nOldLen);
    if (nNewLen)
        aBuf.insert(nFormSelStart, rStr);
 
    aFormText = aBuf.makeStringAndClear();
 
    nFormSelEnd = nFormSelStart + nNewLen;
 
    EditView* pView = GetFuncEditView();
    if (pView)
    {
        pView->SetEditEngineUpdateMode( false );
        pView->GetEditEngine()->SetText( aFormText );
        pView->SetSelection( ESelection(0,nFormSelStart, 0,nFormSelEnd) );
        pView->SetEditEngineUpdateMode( true );
    }
    bModified = true;
}
 
void ScInputHandler::InputTurnOffWinEngine()
{
    bInOwnChange = true;                // disable ModifyHdl (reset below)
 
    eMode = SC_INPUT_NONE;
    /* TODO: it would be better if there was some way to reset the input bar
     * engine instead of deleting and having it recreate through
     * GetFuncEditView(), but first least invasively let this fix fdo#71667 and
     * fdo#72278 without reintroducing fdo#69971. */
    StopInputWinEngine(true);
 
    bInOwnChange = false;
}
 
/**
 * ScInputHdlState
 */
ScInputHdlState::ScInputHdlState( const ScAddress& rCurPos,
                                  const ScAddress& rStartPos,
                                  const ScAddress& rEndPos,
                                  const OUString& rString,
                                  const EditTextObject* pData )
    :   aCursorPos  ( rCurPos ),
        aStartPos   ( rStartPos ),
        aEndPos     ( rEndPos ),
        aString     ( rString ),
        pEditData   ( pData ? pData->Clone() : nullptr )
{
}
 
ScInputHdlState::ScInputHdlState( const ScInputHdlState& rCpy )
{
    *this = rCpy;
}
 
ScInputHdlState::~ScInputHdlState()
{
}
 
bool ScInputHdlState::operator==( const ScInputHdlState& r ) const
{
    return (    (aStartPos  == r.aStartPos)
             && (aEndPos    == r.aEndPos)
             && (aCursorPos == r.aCursorPos)
             && (aString    == r.aString)
             && ScGlobal::EETextObjEqual( pEditData.get(), r.pEditData.get() ) );
}
 
ScInputHdlState& ScInputHdlState::operator=( const ScInputHdlState& r )
{
    aCursorPos  = r.aCursorPos;
    aStartPos   = r.aStartPos;
    aEndPos     = r.aEndPos;
    aString     = r.aString;
    pEditData.reset();
    if (r.pEditData)
        pEditData = r.pEditData->Clone();
 
    return *this;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V547 Expression 'pState' is always true.

V794 The assignment operator should be protected from the case of 'this == &r'.

V506 Pointer to local variable 'aItem' is stored outside the scope of this variable. Such a pointer will become invalid.