// This assumes that rResults is a sorted ring w.r.t ScTypedStrData::LessCaseInsensitive() or // in the reverse direction, whose origin is specified by nRingOrigin.
sal_Int32 getLongestCommonPrefixLength(const std::vector<OUString>& rResults, std::u16string_view aUserEntry, sal_Int32 nRingOrigin)
{
sal_Int32 nResults = rResults.size(); if (!nResults) return0;
if (nResults == 1) return rResults[0].getLength();
sal_Int32 nRingOrigin = 0;
size_t nCount = 0;
ScTypedCaseStrSet::const_iterator retit; if ( bBack ) // Backwards
{
ScTypedCaseStrSet::const_reverse_iterator it, itEnd; if ( itPos == rDataSet.end() )
{
it = rDataSet.rend();
--it;
itEnd = it;
} else
{
it = rDataSet.rbegin();
size_t nPos = std::distance(rDataSet.begin(), itPos);
size_t nRPos = rDataSet.size() - 1 - nPos; // if itPos == rDataSet.end(), then nRPos = -1
std::advance(it, nRPos); if ( it == rDataSet.rend() )
it = rDataSet.rbegin();
itEnd = it;
} bool bFirstTime = true;
while ( it != itEnd || bFirstTime )
{
++it; if ( it == rDataSet.rend() ) // go to the first if reach the end
{
it = rDataSet.rbegin();
nRingOrigin = nCount;
}
if ( !ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()) ) // not a match continue;
rResultVec.push_back(rData.GetString()); // set the match data if ( nCount == 0 ) // convert the reverse iterator back to iterator.
{ // actually we want to do "retit = it;".
retit = rDataSet.begin();
size_t nRPos = std::distance(rDataSet.rbegin(), it);
size_t nPos = rDataSet.size() - 1 - nRPos;
std::advance(retit, nPos);
}
++nCount;
}
} else// Forwards
{
ScTypedCaseStrSet::const_iterator it, itEnd;
it = itPos; if ( it == rDataSet.end() )
it = --rDataSet.end();
itEnd = it; bool bFirstTime = true;
while ( it != itEnd || bFirstTime )
{
++it; if ( it == rDataSet.end() ) // go to the first if reach the end
{
it = rDataSet.begin();
nRingOrigin = nCount;
}
if ( !ScGlobal::GetTransliteration().isMatch(rStart, rData.GetString()) ) // not a match continue;
rResultVec.push_back(rData.GetString()); // set the match data if ( nCount == 0 )
retit = it; // remember first match iterator
++nCount;
}
}
if (pLongestPrefixLen)
{ if (nRingOrigin >= static_cast<sal_Int32>(nCount))
{ // All matches were picked when rDataSet was read in one direction.
nRingOrigin = 0;
} // rResultsVec is a sorted ring with nRingOrigin "origin". // The direction of sorting is not important for getLongestCommonPrefixLength.
*pLongestPrefixLen = getLongestCommonPrefixLength(rResultVec, rStart, nRingOrigin);
}
if ( nCount > 0 ) // at least one function has matched return retit; return rDataSet.end(); // no matching text found
}
OUString aDelimiters = ScEditUtil::ModifyDelimiters(u" !~%\"\t\n"_ustr); // delimiters (in addition to ScEditUtil): only characters that are // allowed in formulas next to references and the quotation mark (so // string constants can be skipped)
// Text between separators. We only consider within one line/paragraph.
aSel.CollapseToEnd();
nStart = nPos;
handle_r1c1:
{ bool bSingleQuoted = false; while (nPos < nLen)
{ // tdf#114113: handle addresses with quoted sheet names like "'Sheet 1'.A1" // Literal single quotes in sheet names are masked by another single quote if (pChar[nPos] == '\'')
{
bSingleQuoted = !bSingleQuoted;
} elseif (!bSingleQuoted) // Get everything in single quotes, including separators
{ if (ScGlobal::UnicodeStrChr(aDelimiters.getStr(), pChar[nPos])) break;
}
incPos( pChar[nPos], nPos, aSel);
}
}
// for R1C1 '-' in R[-]... or C[-]... are not delimiters // Nothing heroic here to ensure that there are '[]' around a negative // integer. we need to clean up this code. if( nPos < nLen && nPos > 0 && '-' == pChar[nPos] && '[' == pChar[nPos-1] &&
formula::FormulaGrammar::CONV_XL_R1C1 == rDoc.GetAddressConvention() )
{
incPos( pChar[nPos], nPos, aSel); goto handle_r1c1;
}
if ( nPos > nStart )
{
OUString aTest = rFormula.copy( nStart, nPos-nStart ); const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
ScRefFlags nFlags = aRange.ParseAny( aTest, rDoc, aAddrDetails ); if ( nFlags & ScRefFlags::VALID )
{ // Set tables if not specified if ( (nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
aRange.aStart.SetTab( pActiveViewSh->GetViewData().GetTabNo() ); if ( (nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
aRange.aEnd.SetTab( aRange.aStart.Tab() );
if ( ( nFlags & (ScRefFlags::COL2_VALID|ScRefFlags::ROW2_VALID|ScRefFlags::TAB2_VALID) ) ==
ScRefFlags::ZERO )
{ // #i73766# if a single ref was parsed, set the same "abs" flags for ref2, // so Format doesn't output a double ref because of different flags.
ScRefFlags nAbsFlags = nFlags & (ScRefFlags::COL_ABS|ScRefFlags::ROW_ABS|ScRefFlags::TAB_ABS);
applyStartToEndFlags(nFlags, nAbsFlags);
}
// Dummy InsertText for Update and Paint // To do that we need to cancel the selection from above (before QuickInsertText)
pView->InsertText( OUString() );
pView->SetSelection(ESelection::AtEnd()); // Set cursor to the end
}
// We are within one paragraph. const sal_Int32 nDiff = aNewStr.getLength() - (rData.maSel.end.nIndex - rData.maSel.start.nIndex);
rData.maSel.end.nIndex += nDiff;
ScInputHandler::~ScInputHandler()
{ // If this is the application InputHandler, the dtor is called after SfxApplication::Main, // thus we can't rely on any Sfx functions if (!mbDocumentDisposing) // inplace
EnterHandler(); // Finish input
if (ScModule* mod = ScModule::get(); mod->GetRefInputHdl() == this)
mod->SetRefInputHdl(nullptr);
if ( pInputWin && pInputWin->GetInputHandler() == this )
pInputWin->SetInputHandler( nullptr );
}
// SetRefDevice(NULL) uses VirtualDevice, SetRefMapMode forces creation of a local VDev, // so the DigitLanguage can be safely modified (might use an own VDev instead of NULL). if ( !( bTextWysiwyg && pActiveViewSh ) )
{
mpEditEngine->GetRefDevice()->SetDigitLanguage( ScModule::GetOptDigitLanguage() );
}
}
void ScInputHandler::ImplCreateEditEngine()
{ if ( mpEditEngine ) return;
// we cannot create a properly initialised EditEngine until we have a document
assert( pActiveViewSh );
ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocShell().GetDocument();
mpEditEngine = std::make_unique<ScFieldEditEngine>(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool());
mpEditEngine->SetWordDelimiters( ScEditUtil::ModifyDelimiters( mpEditEngine->GetWordDelimiters() ) );
UpdateRefDevice(); // also sets MapMode
mpEditEngine->SetPaperSize( Size( 1000000, 1000000 ) );
pEditDefaults.reset( new SfxItemSet( mpEditEngine->GetEmptyItemSet() ) );
// 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);
}
// 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.
// 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 );
}
// Increase suggestion priority of MRU formulas const ScAppOptions& rOpt = ScModule::get()->GetAppOptions(); const sal_uInt16 nMRUCount = rOpt.GetLRUFuncListCount(); const sal_uInt16* pMRUList = rOpt.GetLRUFuncList(); for (sal_uInt16 i = 0; i < nMRUCount; i++)
{ const sal_uInt16 nId = pMRUList[i]; for (sal_uInt32 j = 0; j < nListCount; j++)
{ const ScFuncDesc* pDesc = pFuncList->GetFunction(j); if (pDesc->nFIndex == nId && pDesc->mxFuncName)
{ const OUString aEntry = *pDesc->mxFuncName + aParenthesesReplacement;; const ScTypedStrData aData(aEntry, 0.0, 0.0, ScTypedStrData::Standard); auto it = pFormulaData->find(aData); if (it != pFormulaData->end())
pFormulaData->erase(it);
pFormulaData->insert(ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::MRU)); break; // Stop searching
}
}
}
miAutoPosFormula = pFormulaData->end();
// tdf#142031 - collect all the characters for the formula suggestion auto input
ScTypedCaseStrSet aStrSet;
rDoc.GetFormulaEntries( aStrSet ); for (auto iter = aStrSet.begin(); iter != aStrSet.end(); ++iter)
{ const OUString aFuncName = ScGlobal::getCharClass().uppercase((*iter).GetString()); // fdo#75264 fill maFormulaChar with all characters used in formula names for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
maFormulaChar.insert(aFuncName[j]);
}
pFormulaData->insert(aStrSet.begin(), aStrSet.end());
pFormulaDataPara->insert(aStrSet.begin(), aStrSet.end());
}
/* TODO: MLFORMULA: this should work also with multi-line formulas. */ if ( !(bFormulaMode && pActiveView && pFormulaDataPara && mpEditEngine->GetParagraphCount() == 1) ) return;
aStart = ScGlobal::getCharClass().uppercase( aStart );
sal_Int32 nPos = aStart.getLength() - 1;
sal_Unicode c = aStart[ nPos ]; // fdo#75264 use maFormulaChar to check if characters are used in function names
::std::set< sal_Unicode >::const_iterator p = maFormulaChar.find( c ); if ( p == maFormulaChar.end() ) returnfalse; // last character is not part of any function name, quit
::std::vector<sal_Unicode> aTemp { c }; for(sal_Int32 i = nPos - 1; i >= 0; --i)
{
c = aStart[ i ];
p = maFormulaChar.find( c );
if (p == maFormulaChar.end()) break;
aTemp.push_back( c );
}
::std::vector<sal_Unicode>::reverse_iterator rIt = aTemp.rbegin();
aResult = OUString( *rIt++ ); while ( rIt != aTemp.rend() )
aResult += OUStringChar( *rIt++ );
/* TODO: MLFORMULA: this should work also with multi-line formulas. */ if ( !(pActiveView && pFormulaData && mpEditEngine->GetParagraphCount() == 1) ) return;
// Due to differences between table and input cell (e.g clipboard with line breaks), // the selection may not be in line with the EditEngine anymore. // Just return without any indication as to why. if (aSel.end.nIndex > aParagraph.getLength()) return;
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;
} elseif ( aSelText[ n ] == '"' )
cBetweenQuotes = '"'; elseif ( aSelText[ n ] == '\'' )
cBetweenQuotes = '\'';
} if ( cBetweenQuotes ) return; // we're between quotes
bool bNoInitialLetter = false;
OUString aOld = pView->getEditEngine().GetText(0); // in case we want just insert a function and not completing if ( comphelper::LibreOfficeKit::isActive() )
{
ESelection aSelRange = aSel;
--aSelRange.start.nIndex;
--aSelRange.end.nIndex;
pView->SetSelection(aSelRange);
pView->SelectCurrentWord();
if(!bNoInitialLetter)
{ const sal_Int32 nMinLen = std::max(aSel.end.nIndex - aSel.start.nIndex, sal_Int32(1)); // Since transliteration service is used to test for match, the replaced string could be // longer than rInsert, so in order to find longest match before the cursor, test whole // string from start to current cursor position (don't limit to length of rInsert) // Disclaimer: I really don't know if a match longer than rInsert is actually possible, // so the above is based on assumptions how "transliteration" might possibly work. If // it's in fact impossible, an optimization would be useful to limit aSel.start.nPos to // std::max(sal_Int32(0), aSel.end.nIndex - rInsert.getLength()).
aSel.start.nIndex = 0;
pView->SetSelection(aSel); const OUString aAll = pView->GetSelected();
OUString aMatch; for (sal_Int32 n = aAll.getLength(); n >= nMinLen && aMatch.isEmpty(); --n)
{ const OUString aTest = aAll.copy(aAll.getLength() - n); // n trailing chars if (ScGlobal::GetTransliteration().isMatch(aTest, rInsert))
aMatch = aTest; // Found => break the loop
}
OUString aInsStr = rInsert;
sal_Int32 nInsLen = aInsStr.getLength(); bool bDoParen = ( nInsLen > 1 && aInsStr[nInsLen-2] == '('
&& aInsStr[nInsLen-1] == ')' ); if ( bDoParen )
{ // Do not insert parentheses after function names if there already are some // (e.g. if the function name was edited).
ESelection aWordSel = pView->GetSelection();
// aWordSel.EndPos points one behind string if word at end if (aWordSel.end.nIndex < aOld.getLength())
{
sal_Unicode cNext = aOld[aWordSel.end.nIndex]; if ( cNext == '(' )
{
bDoParen = false;
aInsStr = aInsStr.copy( 0, nInsLen - 2 ); // Skip parentheses
}
}
}
pView->InsertText( aInsStr );
if ( bDoParen ) // Put cursor between parentheses
{
aSel = pView->GetSelection();
--aSel.start.nIndex;
--aSel.end.nIndex;
pView->SetSelection(aSel);
DataChanging(); // Cannot be new
completeFunction( pTopView, aInsert, bParInserted );
completeFunction( pTableView, aInsert, bParInserted );
DataChanged();
ShowTipCursor();
if (bParInserted)
AutoParAdded();
}
HideTip();
EditView* pActiveView = pTopView ? pTopView : pTableView; if (comphelper::LibreOfficeKit::isActive() && pTopView && pInputWin)
pInputWin->TextGrabFocus(); if (pActiveView)
pActiveView->ShowCursor();
}
void ScInputHandler::LOKPasteFunctionData(const OUString& rFunctionName)
{ // in case we have no top view try to create it if (!pTopView && pInputWin)
{
ScInputMode eCurMode = eMode;
SetMode(SC_INPUT_TOP); if (!pTopView)
SetMode(eCurMode);
}
// We can get three updates per keystroke, StartExtTextInput, ExtTextInput and PostExtTextInput // Skip duplicate updates. Be conservative and don't skip duplicates that are 5+ seconds // apart.
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); if (maSendFormulabarUpdate.m_nShellId == nCurrentShellId &&
maSendFormulabarUpdate.m_aText == rText &&
maSendFormulabarUpdate.m_aSelection == aSelection &&
std::chrono::duration_cast<std::chrono::seconds>(
now - maSendFormulabarUpdate.m_nTimeStamp) < std::chrono::seconds(5))
{ return;
}
// Calculate selection and display as tip help static OUString lcl_Calculate( const OUString& rFormula, ScDocument& rDoc, constScAddress &rPos )
{ //TODO: Merge with ScFormulaDlg::CalcValue and move into Document! // Quotation marks for Strings are only inserted here.
// 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.emplace( rDoc, rPos, aBraced, false );
} else
bColRowName = false;
}
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.subView( nTipLen2-3 ) != u"..." ) )
{
DataChanging(); // Cannot be new
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.start.nIndex;
OUString aFormula = mpEditEngine->GetText(0); if ( nPos < aFormula.getLength() && aFormula[nPos] == ')' ) returntrue;
} returnfalse;
}
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 ')'
// Display the rest of longest common prefix as suggestion.
aNew = aResultVec[0].copy(0, nLongestPrefixLen);
} else
{
aNew = aResultVec[0];
}
// 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.end.nPara, aSel.end.nIndex + aIns.getLength(),
aSel.end.nPara, aSel.end.nIndex );
// 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
}
void ScInputHandler::NextAutoEntry( bool bBack )
{
EditView* pActiveView = pTopView ? pTopView : pTableView; if ( pActiveView && pColumnData )
{ if (!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.end.nPara+1 == nParCnt && aSel.start.nPara == aSel.end.nPara )
{
OUString aText = GetEditText(mpEditEngine.get());
sal_Int32 nSelLen = aSel.end.nIndex - aSel.start.nIndex;
sal_Int32 nParLen = mpEditEngine->GetTextLen( aSel.end.nPara ); if ( aSel.end.nIndex == nParLen && aText.getLength() == aAutoSearch.getLength() + nSelLen )
{
OUString aNew;
ScTypedCaseStrSet::const_iterator itNew =
findText(*pColumnData, miAutoPosColumn, aAutoSearch, aNew, bBack);
// when editing in input line, apply to both edit views if ( pTableView )
{
pTableView->DeleteSelected();
pTableView->InsertText( aIns );
pTableView->SetSelection( ESelection(
aSel.end.nPara, aSel.start.nIndex + aIns.getLength(),
aSel.end.nPara, aSel.start.nIndex ) );
} if ( pTopView )
{
pTopView->DeleteSelected();
pTopView->InsertText( aIns );
pTopView->SetSelection( ESelection(
aSel.end.nPara, aSel.start.nIndex + aIns.getLength(),
aSel.end.nPara, aSel.start.nIndex ) );
}
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.start.nIndex)
{ // Examine character left to the cursor
sal_Int32 nPos = aSel.start.nIndex - 1;
OUString aFormula = mpEditEngine->GetText(aSel.start.nPara);
sal_Unicode c = aFormula[nPos]; if ( c == '(' || c == ')' )
{ // Note this matches only within one paragraph.
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 );
}
// Dummy InsertText for Update and Paint (selection is empty)
pTableView->InsertText( 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 );
}
ScModule* mod = ScModule::get(); 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 ) );
mod->SetRefInputHdl(nullptr); if (pInputWin)
pInputWin->SetFormulaMode(false);
UpdateAutoCorrFlag();
}
// #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.
// 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::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->SetUpdateLayout(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->SetUpdateLayout(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() || aTester.GetMessageId() != STR_PROTECTIONERR) &&
!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;
// 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?
// 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
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
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)
{
// If we will end up updating LoKit at the end, we can skip it here
pInputWin->SetTextString(aText, !bUpdateKit);
}
}
// 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();
UpdateFormulaMode();
bTextValid = false; // Changes only in the EditEngine
bInOwnChange = false;
}
bool ScInputHandler::StartsLikeFormula( std::u16string_view rStr ) const
{
// For new input '+' and '-' may start the dreaded "lazy data typist"
// formula input, editing existing formula content can only start with '='.
return !rStr.empty() && (rStr[0] == '=' || (!mbEditingExistingContent && (rStr[0] == '+' || rStr[0] == '-')));
}
if ( bIsFormula )
{
if (!bFormulaMode)
{
pActiveViewSh->GetViewData().SetEditHighlight(true);
bFormulaMode = true;
pRefViewSh = pActiveViewSh;
pSfxApp->Broadcast( SfxHint( SfxHintId::ScRefModeChanged ) );
ScModule* pMod = ScModule::get();
pMod->SetRefInputHdl(this);
if (pInputWin)
pInputWin->SetFormulaMode(true);
// in LOK, we always need to perform the GetFormulaData() call so
// that the formula insertion works
if (comphelper::LibreOfficeKit::isActive() || pMod->GetAppOptions().GetAutoComplete())
GetFormulaData();
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) )
return;
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");
}
}
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))
{
pActiveViewSh->GetViewData().GetDocShell().PostEditView(*mpEditEngine, aCursorPos);
}
}
if (pInitText)
{
mpEditEngine->SetTextCurrentDefaults(*pInitText);
bModified = true;
}
// 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)
mbPartialPrefix = false;
ImplCreateEditEngine();
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);
}
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)
{
ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
const ScValidationData* pData = rDoc.GetValidationEntry( nValidation );
if (pData)
{
// #i67990# don't use pLastPattern in EnterHandler
const ScPatternAttr* pPattern = rDoc.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)
{
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();
}
if (bBeforeSavingInLOK)
{
// Invalid entry but not applied to the document model.
// Exit to complete the "save", leaving the edit view as it is
// for the user to continue after save.
bInOwnChange = false;
bInEnterHandler = false;
return;
}
// Check for input into DataPilot table
if ( bModified && !bForget )
{
ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
ScDPObject* pDPObj = rDoc.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;
// UpdateLayout must be true during CompleteOnlineSpelling
const bool bUpdateLayout = mpEditEngine->SetUpdateLayout( true );
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.
ScDocument& rDoc = pActiveViewSh->GetViewData().GetDocument();
// #i67990# don't use pLastPattern in EnterHandler
const ScPatternAttr* pPattern = rDoc.GetPattern( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab() );
if (pPattern)
{
SvNumberFormatter* pFormatter = rDoc.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).
// SetUpdateLayout must come after CompleteOnlineSpelling.
// The view is hidden in any case below (Broadcast).
mpEditEngine->SetUpdateLayout( 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;
}
}
// 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::INVALID)
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;
}
}
}
// 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 (ScModule::get()->GetAppOptions().GetAutoComplete()) // 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 ) );
ScModule::get()->SetRefInputHdl(nullptr);
if (pInputWin)
pInputWin->SetFormulaMode(false);
UpdateAutoCorrFlag();
}
pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
DeleteRangeFinder();
ResetAutoPar();
// Text input (through number formats) or ApplySelectionPattern modify
// the cell's attributes, so pLastPattern is no longer valid
pLastPattern = nullptr;
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;
// 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 ) );
ScModule::get()->SetRefInputHdl(nullptr);
if (pInputWin)
pInputWin->SetFormulaMode(false);
UpdateAutoCorrFlag();
}
pRefViewSh = nullptr; // Also without FormulaMode due to FunctionsAutoPilot
DeleteRangeFinder();
ResetAutoPar();
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.start.nPara == 0 && aSel.start.nIndex == 0)
return;
DataChanging(); // Cannot be new
// Turn around selection if backwards.
if (pTableView)
{
ESelection aTabSel = pTableView->GetSelection();
if (aTabSel.start.nIndex > aTabSel.end.nIndex && aTabSel.start.nPara == aTabSel.end.nPara)
{
aTabSel.Adjust();
pTableView->SetSelection(aTabSel);
}
}
if (pTopView)
{
ESelection aTopSel = pTopView->GetSelection();
if (aTopSel.start.nIndex > aTopSel.end.nIndex && aTopSel.start.nPara == aTopSel.end.nPara)
{
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));
ScDocShell* pObjSh = rDoc.GetDocumentShell();
// #i75893# convert escaped URL of the document to something user friendly
OUString aFileName = pObjSh->GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::Unambiguous );
if (bAlt && !bControl && nCode != KEY_RETURN)
// Alt-Return and Alt-Ctrl-* are accepted. Everything else with ALT are not.
return false;
// There is a partial autocomplete suggestion.
// Allow its completion with right arrow key (without modifiers).
if (mbPartialPrefix && nCode == KEY_RIGHT && !bControl && !bShift && !bAlt &&
(pTopView || pTableView))
{
if (pTopView)
pTopView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
if (pTableView)
pTableView->PostKeyEvent(KeyEvent(0, css::awt::Key::MOVE_TO_END_OF_PARAGRAPH));
mbPartialPrefix = false;
// Indicate that this event has been consumed and ScTabViewShell should not act on this.
return true;
}
if (!bControl && nCode == KEY_TAB)
{
// Normal TAB moves the cursor right.
EnterHandler();
if (pActiveViewSh)
pActiveViewSh->FindNextUnprot( bShift, true );
if (pTableView)
{
pTableView->getEditEngine().SetText( aStrLoP );
if ( !aStrLoP.isEmpty() )
pTableView->SetSelection(ESelection()); // 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()); // 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);
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 && ScModule::get()->GetAppOptions().GetAutoComplete())
{
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();
// #i114511# don't count cursor keys as modification
bool bSetModified = !bCursorKey;
// tdf#81913 - don't delete range finder since cursor keys don't count as modifications
bInRangeUpdate = bCursorKey;
DataChanged(false, bSetModified); // also calls UpdateParenthesis()
bInRangeUpdate = false;
// In the LOK case, we want to set the document modified state
// right away at the start of the edit, so that the content is
// saved even when the user leaves the document before hitting
// Enter
if (comphelper::LibreOfficeKit::isActive() && bSetModified && pActiveViewSh && !pActiveViewSh->GetViewData().GetDocShell().IsModified())
pActiveViewSh->GetViewData().GetDocShell().SetModified();
InvalidateAttribs(); //! in DataChanged?
}
}
if (pTopView && eMode != SC_INPUT_NONE)
SyncViews();
return bUsed;
}
OUString ScInputHandler::GetSurroundingText()
{
if (eMode != SC_INPUT_NONE)
{
UpdateActiveView();
if (pTableView || pTopView)
{
if (pTableView)
return pTableView->GetSurroundingText();
else if (pTopView) // call only once
return pTopView->GetSurroundingText();
}
}
return OUString();
}
Selection ScInputHandler::GetSurroundingTextSelection()
{
if (eMode != SC_INPUT_NONE)
{
UpdateActiveView();
if (pTableView || pTopView)
{
if (pTableView)
return pTableView->GetSurroundingTextSelection();
else if (pTopView) // call only once
return pTopView->GetSurroundingTextSelection();
}
}
return Selection(0, 0);
}
bool ScInputHandler::DeleteSurroundingText(const Selection& rSelection)
{
if (eMode != SC_INPUT_NONE)
{
UpdateActiveView();
if (pTableView || pTopView)
{
if (pTableView)
return pTableView->DeleteSurroundingText(rSelection);
else if (pTopView) // call only once
return pTopView->DeleteSurroundingText(rSelection);
}
}
return false;
}
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
{
HideTip();
HideTipBelow();
if ( bSelIsRef )
{
RemoveSelection();
bSelIsRef = false;
}
if (!bProtected && pActiveViewSh && !(pActiveViewSh->GetViewShell() && pActiveViewSh->GetViewShell()->IsLokReadOnlyView())) // changes allowed
{
if (bNewView) // create new edit view
{
pActiveViewSh->GetViewData().GetDocShell().PostEditView(*mpEditEngine, aCursorPos);
UpdateActiveView();
if (eMode==SC_INPUT_NONE)
if (pTableView || pTopView)
{
if (pTableView)
{
pTableView->getEditEngine().SetText( u""_ustr );
pTableView->SetSelection(ESelection());
}
if (pTopView)
{
pTopView->getEditEngine().SetText( u""_ustr );
pTopView->SetSelection(ESelection());
}
}
SyncViews();
}
if (pTableView || pTopView)
{
if (pTableView)
pTableView->Command( rCEvt );
if (pTopView)
pTopView->Command( rCEvt );
if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
{
// AutoInput after ext text input
if (pFormulaData)
miAutoPosFormula = pFormulaData->end();
if (pColumnData)
miAutoPosColumn = pColumnData->end();
if (bFormulaMode)
UseFormulaData();
else
UseColData();
}
}
DataChanged(); // calls UpdateParenthesis()
InvalidateAttribs(); //! in DataChanged ?
}
if (pTopView && eMode != SC_INPUT_NONE)
SyncViews();
}
}
static ScInputHdlState* getLastState(const ScInputHdlState* pState)
{
if (!pState)
return nullptr;
return new ScInputHdlState(*pState);
}
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;
//! Before EditEngine gets eventually created (so it gets the right pools)
if ( pSourceSh )
pActiveViewSh = pSourceSh;
else
pActiveViewSh = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() );
if (pActiveViewSh)
ImplCreateEditEngine();
if ( pState != pLastState.get() )
{
pLastState.reset(getLastState(pState));
}
// Also take foreign reference input into account here (e.g. FunctionsAutoPilot),
// FormEditData, if we're switching from Help to Calc:
if ( !bFormulaMode && !pScMod->IsFormulaMode() &&
( !pScTabViewShell || !pScTabViewShell->GetFormEditData() ) )
{
bool bIgnore = false;
if ( bModified )
{
if (pState->GetPos() != aCursorPos)
{
if (!bProtected)
EnterHandler();
}
else
bIgnore = true;
}
if ( pInputWin || comphelper::LibreOfficeKit::isActive()) // Named range input
{
OUString aPosStr;
bool bSheetLocal = false;
const ScAddress::Details aAddrDetails( rDoc, aCursorPos );
// Is the range a name?
//! Find by Timer?
if ( pActiveViewSh )
pActiveViewSh->GetViewData().GetDocument().
GetRangeAtBlock( ScRange( rSPos, rEPos ), aPosStr, &bSheetLocal);
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);
}
else if (bSheetLocal)
{
OUString aName;
if (rDoc.GetName( rSPos.Tab(), aName))
aPosStr = ScPosWnd::createLocalRangeName( aPosStr, aName);
}
if (pInputWin)
{
pInputWin->SetPosString(aPosStr);
pInputWin->SetSumAssignMode();
}
if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh)
pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_ADDRESS, aPosStr.toUtf8());
}
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.
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();
}
// Don't hide function tooltip in LOK, a remote user might be using tip.
if (bStopEditing)
HideTip();
HideTipBelow();
bInOwnChange = false;
}
//! 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)
// #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, aCursorPos);
rViewData.EditGrowY();
rViewData.EditGrowX();
}
SyncViews( pView );
}
const OUString& ScInputHandler::GetEditString()
{ if (mpEditEngine)
{
aCurrentText = mpEditEngine->GetText(); // Always new from Engine
bTextValid = true;
}
eMode = SC_INPUT_NONE; /* TODO: it would be better if there was some way to reset the input bar *engineinsteadofdeletingandhavingitrecreatethrough *GetFuncEditView(),butfirstleastinvasivelyletthisfixfdo#71667and
* fdo#72278 without reintroducing fdo#69971. */
StopInputWinEngine(true);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.