Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sc/source/ui/app/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 161 kB image not shown  

Quelle  inputhdl.cxx   Sprache: C

 
/* -*- 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 <iterator>
#include <memory>
#include <string_view>

#include <inputhdl.hxx>
#include <scitems.hxx>
#include <editeng/eeitem.hxx>

#include <sfx2/app.hxx>
#include <editeng/acorrcfg.hxx>
#include <formula/errorcodes.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/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/docfile.hxx>
#include <sfx2/printer.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svtools/langtab.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/charclass.hxx>
#include <utility>
#include <vcl/help.hxx>
#include <vcl/jsdialog/executor.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/cursor.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <tools/urlobj.hxx>
#include <tools/json_writer.hxx>
#include <formula/formulahelper.hxx>
#include <formula/funcvarargs.h>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <osl/diagnose.h>

#include <attrib.hxx>
#include <inputwin.hxx>
#include <tabvwsh.hxx>
#include <docsh.hxx>
#include <scmod.hxx>
#include <formulaopt.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 <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>
#include <output.hxx>
#include <fillinfo.hxx>

// Maximum Ranges in RangeFinder
#define RANGEFIND_MAX   128

using namespace formula;

namespace {

ScTypedCaseStrSet::const_iterator findText(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
    const OUString& rStart, OUString& rResult, bool bBack)
{
    auto lIsMatch = [&rStart](const ScTypedStrData& rData) {
        return (rData.GetStringType() != ScTypedStrData::Value) && ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()); };

    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;
        }

        it = std::find_if(it, itEnd, lIsMatch);
        if (it != itEnd)
        {
            rResult = it->GetString();
            return (++it).base(); // convert the reverse iterator back to iterator.
        }
    }
    else // Forwards
    {
        ScTypedCaseStrSet::const_iterator it = rDataSet.begin(), itEnd = rDataSet.end();
        if (itPos != itEnd)
        {
            it = std::next(itPos);
        }

        it = std::find_if(it, itEnd, lIsMatch);
        if (it != itEnd)
        {
            rResult = it->GetString();
            return it;
        }
    }

    return rDataSet.end(); // no matching text found
}

OUString getExactMatch(const ScTypedCaseStrSet& rDataSet, const OUString& rString)
{
    auto it = std::find_if(rDataSet.begin(), rDataSet.end(),
        [&rString](const ScTypedStrData& rData) {
            return (rData.GetStringType() != ScTypedStrData::Value)
                && ScGlobal::GetTransliteration().isEqual(rData.GetString(), rString);
        });
    if (it != rDataSet.end())
        return it->GetString();
    return rString;
}

// This assumes that rResults is a sorted ring w.r.t ScTypedStrData::LessCaseInsensitive() or
// in the reverse direction, whose origin is specified by nRingOrigin.
sal_Int32 getLongestCommonPrefixLength(const std::vector<OUString>& rResults, std::u16string_view aUserEntry, sal_Int32 nRingOrigin)
{
    sal_Int32 nResults = rResults.size();
    if (!nResults)
        return 0;

    if (nResults == 1)
        return rResults[0].getLength();

    sal_Int32 nMinLen = aUserEntry.size();
    sal_Int32 nLastIdx = nRingOrigin ? nRingOrigin - 1 : nResults - 1;
    const OUString& rFirst = rResults[nRingOrigin];
    const OUString& rLast = rResults[nLastIdx];
    const sal_Int32 nMaxLen = std::min(rFirst.getLength(), rLast.getLength());

    for (sal_Int32 nLen = nMaxLen; nLen > nMinLen; --nLen)
    {
        if (ScGlobal::GetTransliteration().isMatch(rFirst.copy(0, nLen), rLast))
            return nLen;
    }

    return nMinLen;
}

ScTypedCaseStrSet::const_iterator findTextAll(
    const ScTypedCaseStrSet& rDataSet, ScTypedCaseStrSet::const_iterator const & itPos,
    const OUString& rStart, ::std::vector< OUString > &rResultVec, bool bBack, sal_Int32* pLongestPrefixLen = nullptr)
{
    rResultVec.clear(); // clear contents

    if (!rDataSet.size())
        return rDataSet.end();

    sal_Int32 nRingOrigin = 0;
    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();
                nRingOrigin = nCount;
            }

            if ( bFirstTime )
                bFirstTime = false;
            const ScTypedStrData& rData = *it;
            if ( rData.GetStringType() == ScTypedStrData::Value )
                // skip values
                continue;

            if ( !ScGlobal::GetTransliteration().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.end();
        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();
                nRingOrigin = nCount;
            }

            if ( bFirstTime )
                bFirstTime = false;
            const ScTypedStrData& rData = *it;
            if ( rData.GetStringType() == ScTypedStrData::Value )
                // skip values
                continue;

            if ( !ScGlobal::GetTransliteration().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 (pLongestPrefixLen)
    {
        if (nRingOrigin >= static_cast<sal_Int32>(nCount))
        {
            // All matches were picked when rDataSet was read in one direction.
            nRingOrigin = 0;
        }
        // rResultsVec is a sorted ring with nRingOrigin "origin".
        // The direction of sorting is not important for getLongestCommonPrefixLength.
        *pLongestPrefixLen = getLongestCommonPrefixLength(rResultVec, rStart, nRingOrigin);
    }

    if ( nCount > 0 ) // at least one function has matched
        return retit;
    return rDataSet.end(); // no matching text found
}

}

void ScInputHandler::SendReferenceMarks( const SfxViewShell* pViewShell,
                            const std::vector<ReferenceMark>& rReferenceMarks )
{
    if ( !pViewShell )
        return;

    bool bSend = false;

    std::stringstream ss;

    ss << "{ \"marks\": [ ";

    for ( size_t i = 0; i < rReferenceMarks.size(); i++ )
    {
        if ( rReferenceMarks[i].Is() )
        {
            if ( bSend )
                ss << ", ";

            ss << "{ \"rectangle\": \""
               << rReferenceMarks[i].nX << ", "
               << rReferenceMarks[i].nY << ", "
               << rReferenceMarks[i].nWidth << ", "
               << rReferenceMarks[i].nHeight << "\""
                  "\"color\": \"" << rReferenceMarks[i].aColor.AsRGBHexString() << "\", "
                  "\"part\": \"" << rReferenceMarks[i].nTab << "\" } ";

            bSend = true;
        }
    }

    ss <<  " ] }";

    OString aPayload( ss.str() );
    pViewShell->libreOfficeKitViewCallback(
                LOK_CALLBACK_REFERENCE_MARKS, aPayload );
}

static inline void incPos( const sal_Unicode c, sal_Int32& rPos, ESelection& rSel )
{
    ++rPos;
    if (c == '\n')
    {
        ++rSel.end.nPara;
        rSel.end.nIndex = 0;
    }
    else
    {
        ++rSel.end.nIndex;
    }
}

void ScInputHandler::InitRangeFinder( const OUString& rFormula )
{
    DeleteRangeFinder();
    if (!pActiveViewSh || !ScModule::get()->GetInputOptions().GetRangeFinder())
        return;
    ScDocShell& rDocSh = pActiveViewSh->GetViewData().GetDocShell();
    ScDocument& rDoc = rDocSh.GetDocument();
    const sal_Unicode cSheetSep = rDoc.GetSheetSeparator();

    OUString aDelimiters = ScEditUtil::ModifyDelimiters(u" !~%\"\t\n"_ustr);
        // 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, u""); // Delimiter without colon
    sal_Int32 nDot = aDelimiters.indexOf(cSheetSep);
    if ( nDot != -1 )
        aDelimiters = aDelimiters.replaceAt( nDot, 1 , u""); // Delimiter without dot

    const sal_Unicode* pChar = rFormula.getStr();
    sal_Int32 nLen = rFormula.getLength();
    sal_Int32 nPos = 0;
    sal_Int32 nStart = 0;
    ESelection aSel;
    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
            {
                incPos( pChar[nPos], nPos, aSel);
                while (nPos<nLen && pChar[nPos] != '"')     // Skip until end
                    incPos( pChar[nPos], nPos, aSel);
            }
            incPos( pChar[nPos], nPos, aSel);  // Separator or closing quote
        }

        // Text between separators. We only consider within one line/paragraph.
        aSel.CollapseToEnd();
        nStart = nPos;
handle_r1c1:
        {
            bool bSingleQuoted = false;
            while (nPos < nLen)
            {
                // tdf#114113: handle addresses with quoted sheet names like "'Sheet 1'.A1"
                // Literal single quotes in sheet names are masked by another single quote
                if (pChar[nPos] == '\'')
                {
                    bSingleQuoted = !bSingleQuoted;
                }
                else if (!bSingleQuoted) // Get everything in single quotes, including separators
                {
                    if (ScGlobal::UnicodeStrChr(aDelimiters.getStr(), pChar[nPos]))
                        break;
                }
                incPos( pChar[nPos], nPos, aSel);
            }
        }

        // 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() )
        {
            incPos( pChar[nPos], nPos, aSel);
            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->SetUpdateLayout( false );
                    pRangeFindList.reset(new ScRangeFindList( rDocSh.GetTitle() ));
                }

                Color nColor = pRangeFindList->Insert( ScRangeFindData( aRange, nFlags, aSel));

                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 (?)
    }

    UpdateLokReferenceMarks();

    if (nCount)
    {
        mpEditEngine->SetUpdateLayout( true );

        rDocSh.Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );
    }
}

ReferenceMark ScInputHandler::GetReferenceMark( const ScViewData& rViewData, ScDocShell& rDocSh,
                                    tools::Long nX1, tools::Long nX2, tools::Long nY1, tools::Long nY2,
                                    tools::Long nTab, const Color& rColor )
{
    ScSplitPos eWhich = rViewData.GetActivePart();

    // This method is LOK specific.
    if (comphelper::LibreOfficeKit::isCompatFlagSet(
            comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs))
    {
        SCCOL nCol1 = nX1, nCol2 = nX2;
        SCROW nRow1 = nY1, nRow2 = nY2;
        ScDocument& rDoc = rDocSh.GetDocument();

        PutInOrder(nCol1, nCol2);
        PutInOrder(nRow1, nRow2);

        if (nCol1 == nCol2 && nRow1 == nRow2)
            rDoc.ExtendMerge(nCol1, nRow1, nCol2, nRow2, nTab);
        else if (rDoc.HasAttrib(nCol2, nRow2, nTab, HasAttrFlags::Merged))
            rDoc.ExtendMerge(nCol2, nRow2, nCol2, nRow2, nTab);

        Point aTopLeft = rViewData.GetPrintTwipsPos(nCol1, nRow1);
        Point aBottomRight = rViewData.GetPrintTwipsPos(nCol2 + 1, nRow2 + 1);
        tools::Long nSizeX = aBottomRight.X() - aTopLeft.X() - 1;
        tools::Long nSizeY = aBottomRight.Y() - aTopLeft.Y() - 1;

        return ReferenceMark(aTopLeft.X(), aTopLeft.Y(), nSizeX, nSizeY, nTab, rColor);
    }

    Point aScrPos = rViewData.GetScrPos( nX1, nY1, eWhich );
    tools::Long nScrX = aScrPos.X();
    tools::Long nScrY = aScrPos.Y();

    double nPPTX = rViewData.GetPPTX();
    double nPPTY = rViewData.GetPPTY();

    Fraction aZoomX = rViewData.GetZoomX();
    Fraction aZoomY = rViewData.GetZoomY();

    ScTableInfo aTabInfo(nY1, nY2, true);
    rDocSh.GetDocument().FillInfo( aTabInfo, nX1, nY1, nX2, nY2,
                                    nTab, nPPTX, nPPTY, falsefalse );

    ScOutputData aOutputData( nullptr, OUTTYPE_WINDOW, aTabInfo,
                              &( rDocSh.GetDocument() ), nTab,
                              nScrX, nScrY,
                              nX1, nY1, nX2, nY2,
                              nPPTX, nPPTY,
                              &aZoomX, &aZoomY );

    return aOutputData.FillReferenceMark( nX1, nY1, nX2, nY2,
                                          rColor );
}

void ScInputHandler::UpdateLokReferenceMarks()
{
    if ( !comphelper::LibreOfficeKit::isActive())
        return;

    ScTabViewShell* pShell = pActiveViewSh ? pActiveViewSh
                                : dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());

    if (!pShell)
        return;

    ScViewData& rViewData = pShell->GetViewData();
    ScDocShell& rDocSh = rViewData.GetDocShell();
    ScRangeFindList* pRangeFinder = GetRangeFindList();

    if ( !pRangeFinder && !rViewData.IsRefMode() )
        return;

    sal_uInt16 nAdditionalMarks = 0;
    std::vector<ReferenceMark> aReferenceMarks( 1 );

    if ( rViewData.IsRefMode() )
    {
        nAdditionalMarks = 1;

        const svtools::ColorConfig& rColorCfg = ScModule::get()->GetColorConfig();
        Color aRefColor( rColorCfg.GetColorValue( svtools::CALCREFERENCE ).nColor );
        tools::Long nX1 = rViewData.GetRefStartX();
        tools::Long nX2 = rViewData.GetRefEndX();
        tools::Long nY1 = rViewData.GetRefStartY();
        tools::Long nY2 = rViewData.GetRefEndY();
        tools::Long nTab = rViewData.GetRefStartZ();

        if (rViewData.GetRefEndZ() == rViewData.GetTabNo())
            nTab = rViewData.GetRefEndZ();

        PutInOrder(nX1, nX2);
        PutInOrder(nY1, nY2);

        aReferenceMarks[0] = ScInputHandler::GetReferenceMark( rViewData, rDocSh,
                                                   nX1, nX2, nY1, nY2,
                                                   nTab, aRefColor );
    }

    sal_uInt16 nCount = pRangeFinder ?
        ( static_cast<sal_uInt16>( pRangeFinder->Count() ) + nAdditionalMarks ) : nAdditionalMarks;
    aReferenceMarks.resize( nCount );

    if ( nCount && pRangeFinder && !pRangeFinder->IsHidden() &&
         pRangeFinder->GetDocName() == rDocSh.GetTitle() )
    {
        for (sal_uInt16 i = 0; i < nCount - nAdditionalMarks; i++)
        {
            ScRangeFindData& rData = pRangeFinder->GetObject( i );
            ScRange aRef = rData.aRef;
            aRef.PutInOrder();

            tools::Long nX1 = aRef.aStart.Col();
            tools::Long nX2 = aRef.aEnd.Col();
            tools::Long nY1 = aRef.aStart.Row();
            tools::Long nY2 = aRef.aEnd.Row();
            tools::Long nTab = aRef.aStart.Tab();

            aReferenceMarks[i + nAdditionalMarks] = ScInputHandler::GetReferenceMark( rViewData, rDocSh,
                                                                          nX1, nX2, nY1, nY2,
                                                                          nTab, rData.nColor );

            ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
        }
    }
    else if ( nCount )
    {
        ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
    }
    else
    {
        // Clear
        aReferenceMarks.clear();
        ScInputHandler::SendReferenceMarks( pShell, aReferenceMarks );
    }
}

void ScInputHandler::SetDocumentDisposing( bool b )
{
    mbDocumentDisposing = b;
}

static void lcl_Replace( EditView* pView, const OUString& rNewStr, const ESelection&&nbsp;rOldSel )
{
    if ( !pView )
        return;

    ESelection aOldSel = pView->GetSelection();
    if (aOldSel.HasRange())
        pView->SetSelection(ESelection(aOldSel.end));

    EditEngine& rEngine = pView->getEditEngine();
    rEngine.QuickInsertText( rNewStr, rOldSel );

    // Dummy InsertText for Update and Paint
    // To do that we need to cancel the selection from above (before QuickInsertText)
    pView->InsertText( OUString() );

    pView->SetSelection(ESelection::AtEnd()); // 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 );
        Color nNewColor = pRangeFindList->FindColor( rNew, nIndex );

        ScRange aJustified = rNew;
        aJustified.PutInOrder(); // Always display Ref in the Formula the right way
        ScDocument& rDoc = pDocView->GetViewData().GetDocument();
        const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
        OUString aNewStr(aJustified.Format(rDoc, rData.nFlags, aAddrDetails));
        SfxItemSet aSet( mpEditEngine->GetEmptyItemSet() );

        DataChanging();

        lcl_Replace( pTopView, aNewStr, rData.maSel );
        lcl_Replace( pTableView, aNewStr, rData.maSel );

        // We are within one paragraph.
        const sal_Int32 nDiff = aNewStr.getLength() - (rData.maSel.end.nIndex - rData.maSel.start.nIndex);
        rData.maSel.end.nIndex += nDiff;

        aSet.Put( SvxColorItem( nNewColor, EE_CHAR_COLOR ) );
        mpEditEngine->QuickSetAttribs( aSet, rData.maSel );

        bInRangeUpdate = true;
        DataChanged();
        bInRangeUpdate = false;

        rData.aRef = rNew;
        rData.nColor = nNewColor;

        if (nDiff)
        {
            const size_t nCount = pRangeFindList->Count();
            for (size_t i = nIndex + 1; i < nCount; ++i)
            {
                ScRangeFindData& rNext = pRangeFindList->GetObject( i );
                if (rNext.maSel.start.nPara != rData.maSel.start.nPara)
                    break;

                rNext.maSel.start.nIndex += nDiff;
                rNext.maSel.end.nIndex   += 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& rDocSh = pActiveViewSh->GetViewData().GetDocShell();
        pRangeFindList->SetHidden(true);
        rDocSh.Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );  // Steal
        pRangeFindList.reset();
    }
}

static OUString GetEditText(const EditEngine* pEng)
{
    return ScEditUtil::GetMultilineString(*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 ),
        nCellPercentFormatDecSep( 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 ),
        bLastIsSymbol( false ),
        mbDocumentDisposing(false),
        mbPartialPrefix(false),
        mbEditingExistingContent(false),
        nValidation( 0 ),
        eAttrAdjust( SvxCellHorJustify::Standard ),
        aScaleX( 1,1 ),
        aScaleY( 1,1 ),
        pRefViewSh( nullptr ),
        pLastPattern( nullptr )
{
    //  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 (ScModule* mod = ScModule::get(); mod->GetRefInputHdl() == this)
        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 = ScModule::get()->GetInputOptions().GetTextWysiwyg();
    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( ScModule::GetOptDigitLanguage() );
    }
}

void ScInputHandler::ImplCreateEditEngine()
{
    if ( mpEditEngine )
        return;

    // we cannot create a properly initialised EditEngine until we have a document
    assert( pActiveViewSh );
    ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell().GetDocument();
    mpEditEngine = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
    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 )
        return;

    ScViewData& rViewData = pActiveViewSh->GetViewData();
    bool bOnlineSpell = pActiveViewSh->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& rDoc = rViewData.GetDocument();
        rDoc.ApplyAsianEditSettings( *mpEditEngine );
        mpEditEngine->SetDefaultHorizontalTextDirection(
            rDoc.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 )
        return;

    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();
    const sal_uInt32 nListCount = pFuncList->GetCount();
    const InputHandlerFunctionNames& rFunctionNames = ScGlobal::GetInputHandlerFunctionNames();
    *pFormulaData     = rFunctionNames.maFunctionData;
    *pFormulaDataPara = rFunctionNames.maFunctionDataPara;
    maFormulaChar     = rFunctionNames.maFunctionChar;

    // Increase suggestion priority of MRU formulas
    const ScAppOptions& rOpt = ScModule::get()->GetAppOptions();
    const sal_uInt16 nMRUCount = rOpt.GetLRUFuncListCount();
    const sal_uInt16* pMRUList = rOpt.GetLRUFuncList();
    for (sal_uInt16 i = 0; i < nMRUCount; i++)
    {
        const sal_uInt16 nId = pMRUList[i];
        for (sal_uInt32 j = 0; j < nListCount; j++)
        {
            const ScFuncDesc* pDesc = pFuncList->GetFunction(j);
            if (pDesc->nFIndex == nId && pDesc->mxFuncName)
            {
                const OUString aEntry = *pDesc->mxFuncName + aParenthesesReplacement;;
                const ScTypedStrData aData(aEntry, 0.0, 0.0, ScTypedStrData::Standard);
                auto it = pFormulaData->find(aData);
                if (it != pFormulaData->end())
                    pFormulaData->erase(it);
                pFormulaData->insert(ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::MRU));
                break// Stop searching
            }
        }
    }
    miAutoPosFormula = pFormulaData->end();

    // tdf#142031 - collect all the characters for the formula suggestion auto input
    ScTypedCaseStrSet aStrSet;
    rDoc.GetFormulaEntries( aStrSet );
    for (auto iter = aStrSet.begin(); iter != aStrSet.end(); ++iter)
    {
        const OUString aFuncName = ScGlobal::getCharClass().uppercase((*iter).GetString());
        // fdo#75264 fill maFormulaChar with all characters used in formula names
        for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
            maFormulaChar.insert(aFuncName[j]);
    }
    pFormulaData->insert(aStrSet.begin(), aStrSet.end());
    pFormulaDataPara->insert(aStrSet.begin(), aStrSet.end());
}

IMPL_LINK( ScInputHandler, ShowHideTipVisibleParentListener, VclWindowEvent&, rEvent, void )
{
    if (rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide
        || rEvent.GetId() == VclEventId::WindowLoseFocus || rEvent.GetId() == VclEventId::ControlLoseFocus)
        HideTip();
}

IMPL_LINK( ScInputHandler, ShowHideTipVisibleSecParentListener, VclWindowEvent&, rEvent, void )
{
    if (rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide
        || rEvent.GetId() == VclEventId::WindowLoseFocus || rEvent.GetId() == VclEventId::ControlLoseFocus)
        HideTipBelow();
}

void ScInputHandler::HideTip()
{
    if ( nTipVisible )
    {
        pTipVisibleParent->RemoveEventListener( LINK( this, ScInputHandler, ShowHideTipVisibleParentListener ) );
        Help::HidePopover(pTipVisibleParent, nTipVisible );
        nTipVisible = nullptr;
        pTipVisibleParent = nullptr;
    }
    aManualTip.clear();

    const SfxViewShell* pViewShell = SfxViewShell::Current();
    if (comphelper::LibreOfficeKit::isActive() && pViewShell)
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, "hidetip"_ostr);
}
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(std::u16string_view s, sal_Unicode c)
{
    return !s.empty() && s.find(c) == std::u16string_view::npos;
}

}

void ScInputHandler::ShowArgumentsTip( OUString& rSelText )
{
    if ( !pActiveViewSh )
        return;

    ScDocShell& rDocSh = pActiveViewSh->GetViewData().GetDocShell();
    const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
    const sal_Unicode cSheetSep = rDocSh.GetDocument().GetSheetSeparator();
    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)
                            {
                                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;
                                aNew = OUString::Concat(aNew.subView(0, nStartPosition)) +
                                        u"\x25BA" +
                                        aNew.subView(nStartPosition) +
                                        " : " +
                                        ppFDesc->getParameterDescription(nActive-1);
                                if (eMode != SC_INPUT_TOP)
                                {
                                    ShowTipBelow( aNew );
                                }
                                else
                                {
                                    ShowTip(aNew);
                                }
                                bFound = true;
                            }
                        }
                        else
                        {
                            ShowTipBelow( aNew );
                            bFound = true;
                        }

                        const SfxViewShell* pViewShell = SfxViewShell::Current();
                        if (comphelper::LibreOfficeKit::isActive() && pViewShell && pViewShell->isLOKDesktop())
                        {
                            tools::JsonWriter writer;
                            writer.put("type""formulausage");
                            writer.put("text", aNew);
                            OString sFunctionUsageTip = writer.finishAndGetAsOString();
                            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TOOLTIP, sFunctionUsageTip);
                        }
                    }
                }
            }
        }
        else
        {
            break;
        }
    }
}

void ScInputHandler::ShowTipCursor()
{
    HideTip();
    HideTipBelow();
    EditView* pActiveView = pTopView ? pTopView : pTableView;

    /* TODO: MLFORMULA: this should work also with multi-line formulas. */
    if ( !(bFormulaMode && pActiveView && pFormulaDataPara && mpEditEngine->GetParagraphCount() == 1) )
        return;

    OUString aParagraph = mpEditEngine->GetText( 0 );
    ESelection aSel = pActiveView->GetSelection();
    aSel.Adjust();

    if (aParagraph.getLength() < aSel.end.nIndex)
        return;

    if (aSel.end.nIndex > 0)
    {
        OUString aSelText(aParagraph.copy(0, aSel.end.nIndex));

        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)
        return;

    Point aPos;
    if (pInputWin && pInputWin->GetEditView() == pActiveView)
    {
        pTipVisibleParent = pInputWin->GetEditWindow();
        aPos = pInputWin->GetCursorScreenPixelPos();
    }
    else
    {
        pTipVisibleParent = pActiveView->GetWindow();
        if (vcl::Cursor* pCur = pActiveView->GetCursor())
            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 )
        return;

    Point aPos;
    if (pInputWin && pInputWin->GetEditView() == pActiveView)
    {
        pTipVisibleSecParent = pInputWin->GetEditWindow();
        aPos = pInputWin->GetCursorScreenPixelPos(true);
    }
    else
    {
        pTipVisibleSecParent = pActiveView->GetWindow();
        if (vcl::Cursor* pCur = pActiveView->GetCursor())
        {
            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::getCharClass().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 { 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 += OUStringChar( *rIt++ );

    return true;
}

namespace {
    /// Rid ourselves of unwanted " quoted json characters.
    OString escapeJSON(const OUString &aStr)
    {
        OUString aEscaped = aStr;
        aEscaped = aEscaped.replaceAll("\n"" ");
        aEscaped = aEscaped.replaceAll("\"", "'");
        return OUStringToOString(aEscaped, RTL_TEXTENCODING_UTF8);
    }
}

void ScInputHandler::ShowFuncList( const ::std::vector< OUString > & rFuncStrVec )
{
    const SfxViewShell* pViewShell = SfxViewShell::Current();
    if (comphelper::LibreOfficeKit::isActive())
    {
        if (rFuncStrVec.size() && pViewShell)
        {
            auto aPos = pFormulaData->begin();
            sal_uInt32 nCurIndex = std::distance(aPos, miAutoPosFormula);
            const sal_uInt32 nSize = pFormulaData->size();

            OUString aFuncNameStr;
            OUString aDescFuncNameStr;
            OStringBuffer aPayload("[ ");
            for (const OUString& rFunc : rFuncStrVec)
            {
                if ( rFunc[rFunc.getLength()-1] == cParenthesesReplacement )
                {
                    aFuncNameStr = rFunc.copy(0, rFunc.getLength()-1);
                }
                else
                {
                    aFuncNameStr = rFunc;
                }

                FormulaHelper aHelper(ScGlobal::GetStarCalcFunctionMgr());
                aDescFuncNameStr = aFuncNameStr + "()";
                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() )
                    {
                        aPayload.append("{"
                            "\"index\": "
                            + OString::number(static_cast<sal_Int64>(nCurIndex))
                            + ", "
                            "\"signature\": \""
                            + escapeJSON(ppFDesc->getSignature())
                            + "\""
                            "\"description\": \""
                            + escapeJSON(ppFDesc->getDescription())
                            + "\", \"namedRange\"false }, ");
                    }
                    else
                    {
                        aPayload.append("{"
                            "\"index\": "
                            + OString::number(static_cast<sal_Int64>(nCurIndex))
                            + ", "
                                "\"signature\": \""
                            + escapeJSON(aFuncNameStr)
                            + "\""
                                "\"description\": \""
                            + escapeJSON(OUString())
                            + "\", \"namedRange\"true }, ");
                    }
                }
                ++nCurIndex;
                if (nCurIndex == nSize)
                    nCurIndex = 0;
            }
            sal_Int32 nLen = aPayload.getLength();
            if (nLen <= 2)
            {
                aPayload[nLen - 1] = ']';
            }
            else
            {
                aPayload[nLen - 2] = ' ';
                aPayload[nLen - 1] = ']';
            }

            OString s = aPayload.makeStringAndClear();
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, s);
        }
        return;
    }

    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);
        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(" : " + ppFDesc->getDescription());
        }
    }
    ShowTip( aTipStr.makeStringAndClear() );
}

void ScInputHandler::UseFormulaData()
{
    EditView* pActiveView = pTopView ? pTopView : pTableView;

    /* TODO: MLFORMULA: this should work also with multi-line formulas. */
    if ( !(pActiveView && pFormulaData && mpEditEngine->GetParagraphCount() == 1) )
        return;

    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.end.nIndex > aParagraph.getLength())
        return;

    if ( aParagraph.getLength() > aSel.end.nIndex &&
         ( ScGlobal::getCharClass().isLetterNumeric( aParagraph, aSel.end.nIndex ) ||
           aParagraph[ aSel.end.nIndex ] == '_' ||
           aParagraph[ aSel.end.nIndex ] == '.' ||
           aParagraph[ aSel.end.nIndex ] == '$'   ) )
        return;

    //  Is the cursor at the end of a word?
    if (aSel.end.nIndex <= 0)
        return;

    OUString aSelText(aParagraph.copy(0, aSel.end.nIndex));

    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 {

void completeFunction( EditView* pView, const OUString& rInsert, bool& rParInserted )
{
    if (!pView)
        return;

    ESelection aSel = pView->GetSelection();

    bool bNoInitialLetter = false;
    OUString aOld = pView->getEditEngine().GetText(0);
    // in case we want just insert a function and not completing
    if ( comphelper::LibreOfficeKit::isActive() )
    {
        ESelection aSelRange = aSel;
        --aSelRange.start.nIndex;
        --aSelRange.end.nIndex;
        pView->SetSelection(aSelRange);
        pView->SelectCurrentWord();

        if ( aOld == "=" )
        {
            bNoInitialLetter = true;
            aSelRange.start.nIndex = 1;
            aSelRange.end.nIndex = 1;
            pView->SetSelection(aSelRange);
        }
        else if ( pView->GetSelected().startsWith("()") )
        {
            bNoInitialLetter = true;
            ++aSelRange.start.nIndex;
            ++aSelRange.end.nIndex;
            pView->SetSelection(aSelRange);
        }
    }

    if(!bNoInitialLetter)
    {
        const sal_Int32 nMinLen = std::max(aSel.end.nIndex - aSel.start.nIndex, sal_Int32(1));
        // Since transliteration service is used to test for match, the replaced string could be
        // longer than rInsert, so in order to find longest match before the cursor, test whole
        // string from start to current cursor position (don't limit to length of rInsert)
        // Disclaimer: I really don't know if a match longer than rInsert is actually possible,
        // so the above is based on assumptions how "transliteration" might possibly work. If
        // it's in fact impossible, an optimization would be useful to limit aSel.start.nPos to
        // std::max(sal_Int32(0), aSel.end.nIndex - rInsert.getLength()).
        aSel.start.nIndex = 0;
        pView->SetSelection(aSel);
        const OUString aAll = pView->GetSelected();
        OUString aMatch;
        for (sal_Int32 n = aAll.getLength(); n >= nMinLen && aMatch.isEmpty(); --n)
        {
            const OUString aTest = aAll.copy(aAll.getLength() - n); // n trailing chars
            if (ScGlobal::GetTransliteration().isMatch(aTest, rInsert))
                aMatch = aTest; // Found => break the loop
        }

        aSel.start.nIndex = aSel.end.nIndex - aMatch.getLength();
        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();

        // aWordSel.EndPos points one behind string if word at end
        if (aWordSel.end.nIndex < aOld.getLength())
        {
            sal_Unicode cNext = aOld[aWordSel.end.nIndex];
            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.start.nIndex;
        --aSel.end.nIndex;
        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 = OUString::Concat(aInsert.subView( 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 (comphelper::LibreOfficeKit::isActive() && pTopView && pInputWin)
        pInputWin->TextGrabFocus();
    if (pActiveView)
        pActiveView->ShowCursor();
}

void ScInputHandler::LOKPasteFunctionData(const OUString& rFunctionName)
{
    // in case we have no top view try to create it
    if (!pTopView && pInputWin)
    {
        ScInputMode eCurMode = eMode;
        SetMode(SC_INPUT_TOP);
        if (!pTopView)
            SetMode(eCurMode);
    }

    EditView* pEditView = pTopView ? pTopView : pTableView;

    if (!pActiveViewSh || !pEditView)
        return;

    bool bEdit = false;
    OUString aFormula;
    EditEngine const& rEditEngine = pEditView->getEditEngine();
    {
        aFormula = rEditEngine.GetText(0);
        /* TODO: LOK: are you sure you want '+' and '-' let start formulas with
         * function names? That was meant for "data typist" numeric keyboard
         * input. */

        bEdit = aFormula.getLength() > 1 && (aFormula[0] == '=' || aFormula[0] == '+' || aFormula[0] == '-');
    }

    if ( !bEdit )
    {
        OUString aNewFormula('=');
        if ( aFormula.startsWith("=") )
            aNewFormula = aFormula;

        InputReplaceSelection( aNewFormula );
    }

    if (pFormulaData)
    {
        OUString aNew;
        ScTypedCaseStrSet::const_iterator aPos = findText(*pFormulaData, pFormulaData->begin(), rFunctionName, aNew, /* backward = */false);

        if (aPos != pFormulaData->end())
        {
            miAutoPosFormula = aPos;
            PasteFunctionData();
        }
    }
}

void ScTabViewShell::LOKSendFormulabarUpdate(const EditView* pActiveView,
                                             const OUString& rText,
                                             const ESelection& rSelection)
{
    OUString aSelection;
    if (pActiveView)
    {
        aSelection = OUString::number(pActiveView->GetPosWithField(0, rSelection.start.nIndex)) + ";" +
            OUString::number(pActiveView->GetPosWithField(0, rSelection.end.nIndex)) + ";" +
            OUString::number(rSelection.start.nPara) + ";" + OUString::number(rSelection.end.nPara);
    }
    else
    {
        aSelection = OUString::number(rSelection.start.nIndex) + ";" + OUString::number(rSelection.end.nIndex) + ";" +
            OUString::number(rSelection.start.nPara) + ";" + OUString::number(rSelection.end.nPara);
    }

    sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(this);

    // We can get three updates per keystroke, StartExtTextInput, ExtTextInput and PostExtTextInput
    // Skip duplicate updates. Be conservative and don't skip duplicates that are 5+ seconds
    // apart.
    std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
    if (maSendFormulabarUpdate.m_nShellId == nCurrentShellId &&
        maSendFormulabarUpdate.m_aText == rText &&
        maSendFormulabarUpdate.m_aSelection == aSelection &&
        std::chrono::duration_cast<std::chrono::seconds>(
            now - maSendFormulabarUpdate.m_nTimeStamp) < std::chrono::seconds(5))
    {
        return;
    }

    maSendFormulabarUpdate.m_nShellId = nCurrentShellId;
    maSendFormulabarUpdate.m_aText = rText;
    maSendFormulabarUpdate.m_aSelection = aSelection;
    maSendFormulabarUpdate.m_nTimeStamp = now;

    ScViewData& rViewData = this->GetViewData();
    const ScDocument& rDoc = rViewData.GetDocShell().GetDocument();
    const ScPatternAttr* pPattern = rDoc.GetPattern(rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetRefTabNo());

    if (pPattern)
    {
        SvNumberFormatter* pFormatter = rDoc.GetFormatTable();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=87 G=90

¤ Dauer der Verarbeitung: 0.23 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.