summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/app/inputhdl.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sc/source/ui/app/inputhdl.cxx4384
1 files changed, 4384 insertions, 0 deletions
diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx
new file mode 100644
index 000000000..9fa0b7da2
--- /dev/null
+++ b/sc/source/ui/app/inputhdl.cxx
@@ -0,0 +1,4384 @@
+/* -*- 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 <sfx2/lokhelper.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/zforlist.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/charclass.hxx>
+#include <vcl/help.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+#include <formula/formulahelper.hxx>
+#include <formula/funcvarargs.h>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+
+#include <attrib.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 <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;
+
+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)
+{
+ auto lIsMatch = [&rStart](const ScTypedStrData& rData) {
+ return (rData.GetStringType() != ScTypedStrData::Value) && ScGlobal::GetpTransliteration()->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::GetpTransliteration()->isEqual(rData.GetString(), rString);
+ });
+ if (it != rDataSet.end())
+ return it->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::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().c_str();
+ pViewShell->libreOfficeKitViewCallback(
+ LOK_CALLBACK_REFERENCE_MARKS, aPayload.getStr() );
+}
+
+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 between separators
+ 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;
+ }
+ ++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 (?)
+ }
+
+ UpdateLokReferenceMarks();
+
+ if (nCount)
+ {
+ mpEditEngine->SetUpdateMode( true );
+
+ pDocSh->Broadcast( SfxHint( SfxHintId::ScShowRangeFinder ) );
+ }
+}
+
+ReferenceMark ScInputHandler::GetReferenceMark( ScViewData& rViewData, ScDocShell* pDocSh,
+ long nX1, long nX2, long nY1, long nY2,
+ long nTab, const Color& rColor )
+{
+ ScSplitPos eWhich = rViewData.GetActivePart();
+
+ Point aScrPos = rViewData.GetScrPos( nX1, nY1, eWhich );
+ long nScrX = aScrPos.X();
+ long nScrY = aScrPos.Y();
+
+ double nPPTX = rViewData.GetPPTX();
+ double nPPTY = rViewData.GetPPTY();
+
+ Fraction aZoomX = rViewData.GetZoomX();
+ Fraction aZoomY = rViewData.GetZoomY();
+
+ ScTableInfo aTabInfo;
+ pDocSh->GetDocument().FillInfo( aTabInfo, nX1, nY1, nX2, nY2,
+ nTab, nPPTX, nPPTY, false, false );
+
+ ScOutputData aOutputData( nullptr, OUTTYPE_WINDOW, aTabInfo,
+ &( pDocSh->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() || !pActiveViewSh )
+ return;
+
+ ScViewData& rViewData = pActiveViewSh->GetViewData();
+ ScDocShell* pDocSh = 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 = SC_MOD()->GetColorConfig();
+ Color aRefColor( rColorCfg.GetColorValue( svtools::CALCREFERENCE ).nColor );
+ long nX1 = rViewData.GetRefStartX();
+ long nX2 = rViewData.GetRefEndX();
+ long nY1 = rViewData.GetRefStartY();
+ long nY2 = rViewData.GetRefEndY();
+ long nTab = rViewData.GetRefTabNo();
+
+ PutInOrder(nX1, nX2);
+ PutInOrder(nY1, nY2);
+
+ aReferenceMarks[0] = ScInputHandler::GetReferenceMark( rViewData, pDocSh,
+ 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() == pDocSh->GetTitle() )
+ {
+ for (sal_uInt16 i = 0; i < nCount - nAdditionalMarks; i++)
+ {
+ ScRangeFindData& rData = pRangeFinder->GetObject( i );
+ ScRange aRef = rData.aRef;
+ aRef.PutInOrder();
+
+ long nX1 = aRef.aStart.Col();
+ long nX2 = aRef.aEnd.Col();
+ long nY1 = aRef.aStart.Row();
+ long nY2 = aRef.aEnd.Row();
+ long nTab = aRef.aStart.Tab();
+
+ aReferenceMarks[i + nAdditionalMarks] = ScInputHandler::GetReferenceMark( rViewData, pDocSh,
+ nX1, nX2, nY1, nY2,
+ nTab, rData.nColor );
+
+ ScInputHandler::SendReferenceMarks( pActiveViewSh, aReferenceMarks );
+ }
+ }
+ else if ( nCount )
+ {
+ ScInputHandler::SendReferenceMarks( pActiveViewSh, aReferenceMarks );
+ }
+ else
+ {
+ // Clear
+ aReferenceMarks.clear();
+ ScInputHandler::SendReferenceMarks( pActiveViewSh, aReferenceMarks );
+ }
+}
+
+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(*pDoc, rData.nFlags, 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();
+ }
+}
+
+static 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 ),
+ 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),
+ 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 ) );
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ ScInputHandler::bOptLoaded = true; // Evaluate App options
+ ScInputHandler::bAutoComplete = true; // Is set in KeyInput
+ }
+}
+
+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 = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
+ }
+ else
+ mpEditEngine = std::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
+ || rEvent.GetId() == VclEventId::WindowLoseFocus)
+ HideTip();
+}
+
+IMPL_LINK( ScInputHandler, ShowHideTipVisibleSecParentListener, VclWindowEvent&, rEvent, void )
+{
+ if (rEvent.GetId() == VclEventId::ObjectDying || rEvent.GetId() == VclEventId::WindowHide
+ || rEvent.GetId() == VclEventId::WindowLoseFocus)
+ 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 )
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ return;
+ }
+
+ 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.append(std::u16string_view(aNew).substr(0, nStartPosition));
+ aBuf.append(u'\x25BA');
+ aBuf.append(std::u16string_view(aNew).substr(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();
+ if (eMode != SC_INPUT_TOP)
+ {
+ ShowTipBelow( aNew );
+ }
+ else
+ {
+ ShowTip(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::getCharClassPtr()->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 += 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() &&
+ pViewShell && pViewShell->isLOKMobilePhone())
+ {
+ if (rFuncStrVec.size())
+ {
+ auto aPos = pFormulaData->begin();
+ sal_uInt32 nCurIndex = std::distance(aPos, miAutoPosFormula);
+ const sal_uInt32 nSize = pFormulaData->size();
+
+ OUString aFuncNameStr;
+ OUString aDescFuncNameStr;
+ OStringBuffer aPayload;
+ aPayload.append("[ ");
+ 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("{");
+ aPayload.append("\"index\": ");
+ aPayload.append(OString::number(nCurIndex));
+ aPayload.append(", ");
+ aPayload.append("\"signature\": \"");
+ aPayload.append(escapeJSON(ppFDesc->getSignature()));
+ aPayload.append("\", ");
+ aPayload.append("\"description\": \"");
+ aPayload.append(escapeJSON(ppFDesc->getDescription()));
+ aPayload.append("\"}, ");
+ }
+ }
+ ++nCurIndex;
+ if (nCurIndex == nSize)
+ nCurIndex = 0;
+ }
+ sal_Int32 nLen = aPayload.getLength();
+ aPayload[nLen - 2] = ' ';
+ aPayload[nLen - 1] = ']';
+
+ OString s = aPayload.makeStringAndClear();
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, s.getStr());
+ }
+ 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.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::getCharClassPtr()->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 {
+
+void completeFunction( EditView* pView, const OUString& rInsert, bool& rParInserted )
+{
+ if (pView)
+ {
+ 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.nStartPos;
+ --aSelRange.nEndPos;
+ pView->SetSelection(aSelRange);
+ pView->SelectCurrentWord();
+
+ if ( aOld == "=" )
+ {
+ bNoInitialLetter = true;
+ aSelRange.nStartPos = 1;
+ aSelRange.nEndPos = 1;
+ pView->SetSelection(aSelRange);
+ }
+ else if ( pView->GetSelected().startsWith("()") )
+ {
+ bNoInitialLetter = true;
+ ++aSelRange.nStartPos;
+ ++aSelRange.nEndPos;
+ pView->SetSelection(aSelRange);
+ }
+ }
+
+ if(!bNoInitialLetter)
+ {
+ const sal_Int32 nMinLen = std::max(aSel.nEndPos - aSel.nStartPos, 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.nStartPos to
+ // std::max(sal_Int32(0), aSel.nEndPos - rInsert.getLength()).
+ aSel.nStartPos = 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::GetpTransliteration()->isMatch(aTest, rInsert))
+ aMatch = aTest; // Found => break the loop
+ }
+
+ aSel.nStartPos = aSel.nEndPos - 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.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();
+}
+
+void ScInputHandler::LOKPasteFunctionData(const OUString& rFunctionName)
+{
+ if (pActiveViewSh && (pTopView || pTableView))
+ {
+ bool bEdit = false;
+ OUString aFormula;
+ EditView* pEditView = pTopView ? pTopView : pTableView;
+ const EditEngine* pEditEngine = pEditView->GetEditEngine();
+ if (pEditEngine)
+ {
+ aFormula = pEditEngine->GetText(0);
+ 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();
+ }
+ }
+ }
+}
+
+// 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 ...
+ OUString aBraced = "(" + rFormula + ")";
+ pCalc.reset( new ScSimpleFormulaCalculator( pDoc, rPos, aBraced, 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 += " ...";
+
+ 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)
+ return false;
+
+ if (pActiveViewSh)
+ {
+ ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell()->GetDocument();
+
+ if (!rDoc.ValidCol(aCursorPos.Col()))
+ return false;
+
+ ImplCreateEditEngine();
+ UpdateActiveView();
+ SyncViews();
+
+
+ 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();
+ if (SvNumFormatType::PERCENT == rDoc.GetFormatTable()->GetType( nFormat ))
+ nCellPercentFormatDecSep = rDoc.GetFormatTable()->GetFormatDecimalSep( nFormat).toChar();
+ else
+ nCellPercentFormatDecSep = 0;
+ }
+ else
+ nCellPercentFormatDecSep = 0; // 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->SetTextCurrentDefaults(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->SetTextCurrentDefaults(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] == '-');
+ }
+
+ 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->SetTextCurrentDefaults(*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)
+
+ // tdf#125917 Release the grab that a current mouse-down event being handled
+ // by ScTabView has put on the mouse via its SelectionEngine.
+ // Otherwise the warning box cannot interact with the mouse
+ if (ScTabView* pView = pActiveViewSh->GetViewData().GetView())
+ {
+ if (ScViewSelectionEngine* pSelEngine = pView->GetSelEngine())
+ pSelEngine->ReleaseMouse();
+ }
+ }
+
+ 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 )
+ {
+ std::unique_ptr<SfxItemSet> pCommonAttrs;
+ 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.reset(new SfxItemSet( mpEditEngine->GetEmptyItemSet() ));
+ pCommonAttrs->Put( *pItem );
+ }
+ }
+
+ if ( pCommonAttrs )
+ {
+ ScDocument* pDoc = pActiveViewSh->GetViewData().GetDocument();
+ pCellAttrs = std::make_unique<ScPatternAttr>(pDoc->GetPool());
+ pCellAttrs->GetFromEditItemSet( pCommonAttrs.get() );
+ }
+ }
+
+ // 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::getLocaleDataPtr()->getDoubleQuotationMarkStart();
+ if( aReplace != "\"" )
+ aString = aString.replaceAll( aReplace, "\"" );
+
+ aReplace = OUString(pAuto->GetEndDoubleQuote());
+ if( aReplace.isEmpty() )
+ aReplace = ScGlobal::getLocaleDataPtr()->getDoubleQuotationMarkEnd();
+ if( aReplace != "\"" )
+ aString = aString.replaceAll( aReplace, "\"" );
+
+ aReplace = OUString(pAuto->GetStartSingleQuote());
+ if( aReplace.isEmpty() )
+ aReplace = ScGlobal::getLocaleDataPtr()->getQuotationMarkStart();
+ if( aReplace != "'" )
+ aString = aString.replaceAll( aReplace, "'" );
+
+ aReplace = OUString(pAuto->GetEndSingleQuote());
+ if( aReplace.isEmpty() )
+ aReplace = ScGlobal::getLocaleDataPtr()->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(pExecuteSh->GetViewData().GetDocument()->MaxCol()+1,0,0); // Invalid flag
+ mpEditEngine->SetTextCurrentDefaults(OUString());
+
+ if ( !pLastState && pExecuteSh )
+ pExecuteSh->UpdateInputHandler( true ); // Update status again
+ else
+ NotifyChange( pLastState.get(), true );
+
+ nFormSelStart = nFormSelEnd = 0;
+ aFormText.clear();
+
+ bInOwnChange = false;
+
+ if ( comphelper::LibreOfficeKit::isActive() && pExecuteSh )
+ {
+ // Clear
+ std::vector<ReferenceMark> aReferenceMarks;
+ ScInputHandler::SendReferenceMarks( pActiveViewSh, aReferenceMarks );
+ }
+}
+
+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) //checking space
+ {
+ cLastChar = aText[nPos];
+ if (cLastChar != ' ')
+ break;
+ --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& rDoc )
+{
+ HideTip();
+
+ const ScDocument* pThisDoc = nullptr;
+ if (pRefViewSh)
+ pThisDoc = pRefViewSh->GetViewData().GetDocument();
+ bool bOtherDoc = (pThisDoc != &rDoc);
+ if (bOtherDoc && !rDoc.GetDocumentShell()->HasName())
+ {
+ // References to unnamed document; that doesn't work
+ // SetReference should not be called, then
+ return;
+ }
+ if (!pThisDoc)
+ pThisDoc = &rDoc;
+
+ 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, in the syntax of the document being edited.
+ OUString aRefStr;
+ const ScAddress::Details aAddrDetails( *pThisDoc, 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(rDoc, ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, aAddrDetails));
+
+ SfxObjectShell* pObjSh = rDoc.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(rDoc, ScRefFlags::VALID | ScRefFlags::TAB_ABS_3D, aAddrDetails);
+ else
+ aRefStr = rRef.Format(rDoc, ScRefFlags::VALID, 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 && nCellPercentFormatDecSep != 0 &&
+ ((nChar >= '0' && nChar <= '9') || nChar == '-' || nChar == nCellPercentFormatDecSep))
+ {
+ 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 && !comphelper::LibreOfficeKit::isActive())
+ 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();
+
+ ScTabViewShell* pScTabViewShell = dynamic_cast<ScTabViewShell*>(pScMod->GetViewShell());
+
+ // Also take foreign reference input into account here (e.g. FunctionsAutoPilot),
+ // FormEditData, if we're switching from Help to Calc:
+ if ( !bFormulaMode && !pScMod->IsFormulaMode() &&
+ ( !pScTabViewShell || !pScTabViewShell->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->SetTextCurrentDefaults( *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);
+
+ if (comphelper::LibreOfficeKit::isActive() && 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(rDoc, ScRefFlags::VALID | nFlags, 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();
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() && 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->SetTextCurrentDefaults( 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->SetTextCurrentDefaults( 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.SetTextCurrentDefaults(*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 )
+{
+ if (this != &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: */