sc/source/ui/inc/viewfunc.hxx | 7 sc/source/ui/view/viewfunc.cxx | 402 ++++++++++++++++++++++++----------------- 2 files changed, 246 insertions(+), 163 deletions(-)
New commits: commit 79ff3aa89dbe85d4a648e5fa5953c172b9cd35b5 Author: codewithvk <vivek.jav...@collabora.com> AuthorDate: Thu Jan 11 10:27:27 2024 +0530 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Tue Jan 30 12:50:43 2024 +0100 Implement Async AutoCorrectQuery Dialogs for Formula Check in calc Key changes include: 1. Decomposition into Four Methods: The core logic of formula processing `ScViewFunc::EnterData` has been divided into four distinct methods: `parseAndCorrectFormula`, `runAutoCorrectQueryAsync`, `finalizeFormulaProcessing`, and `performAutoFormatAndUpdate`. This modular approach improves code readability and maintainability. 2. Introduction of `FormulaProcessingContext` Struct: To manage the complexities of asynchronous behavior and parameter passing, a new struct `FormulaProcessingContext` has been introduced. It acts as a central state holder, ensuring parameter persistence throughout the asynchronous operations and avoiding scope-related errors. 3. Recursive Dependency and Loop Replacement: The methods `parseAndCorrectFormula` and `runAutoCorrectQueryAsync` are interdependent and recursively call each other. This recursive strategy effectively replaces the previous `do while` loop in the code, streamlining the process of formula correction. 4. Asynchronous Dialog Invocation: The `runAutoCorrectQueryAsync` method now handles the asynchronous triggering of AutoCorrectQuery dialogs. This change significantly improves the user experience by preventing UI freezes during formula processing. Overall, this commit achieves the goal of making AutoCorrectQuery dialogs asynchronous while refactoring `ScViewFunc::EnterData` for better code structure and performance. Signed-off-by: codewithvk <vivek.jav...@collabora.com> Change-Id: If159b98d54c0eaed41789eca7913a929b1e19c1c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161906 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162741 diff --git a/sc/source/ui/inc/viewfunc.hxx b/sc/source/ui/inc/viewfunc.hxx index bf6a9a77269a..a49941e7052e 100644 --- a/sc/source/ui/inc/viewfunc.hxx +++ b/sc/source/ui/inc/viewfunc.hxx @@ -345,6 +345,10 @@ public: void OnLOKInsertDeleteRow(SCROW nStartRow, tools::Long nOffset); void OnLOKSetWidthOrHeight(SCCOLROW nStart, bool bWidth); + bool TestFormatArea( SCCOL nCol, SCROW nRow, SCTAB nTab, bool bAttrChanged ); + void DoAutoAttributes( SCCOL nCol, SCROW nRow, SCTAB nTab, + bool bAttrChanged ); + // Internal helper functions protected: static void UpdateLineAttrs( ::editeng::SvxBorderLine& rLine, @@ -372,9 +376,6 @@ private: sal_uInt16 GetOptimalColWidth( SCCOL nCol, SCTAB nTab, bool bFormula ); void StartFormatArea(); - bool TestFormatArea( SCCOL nCol, SCROW nRow, SCTAB nTab, bool bAttrChanged ); - void DoAutoAttributes( SCCOL nCol, SCROW nRow, SCTAB nTab, - bool bAttrChanged ); void MarkAndJumpToRanges(const ScRangeList& rRanges); void CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartRow, diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx index 25633cae38f7..5d5d07b215c6 100644 --- a/sc/source/ui/view/viewfunc.cxx +++ b/sc/source/ui/view/viewfunc.cxx @@ -134,6 +134,46 @@ ScViewFunc::~ScViewFunc() { } +struct FormulaProcessingContext +{ + std::shared_ptr<ScAddress> aPos; + std::shared_ptr<ScCompiler> aComp; + std::shared_ptr<ScDocShellModificator> aModificator; + std::shared_ptr<ScTokenArray> pArr; + std::shared_ptr<ScTokenArray> pArrFirst; + + const EditTextObject* pData; + ScMarkData aMark; + ScViewFunc& rViewFunc; + + OUString aCorrectedFormula; + OUString aFormula; + OUString aString; + + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + + bool bMatrixExpand; + bool bNumFmtChanged; + bool bRecord; + + ScViewData& GetViewData() const + { + return rViewFunc.GetViewData(); + } + + ScDocFunc& GetDocFunc() const + { + return GetViewData().GetDocFunc(); + } + + ScDocument& GetDoc() const + { + return GetViewData().GetDocument(); + } +}; + namespace { void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction) @@ -358,7 +398,7 @@ static bool lcl_AddFunction( ScAppOptions& rAppOpt, sal_uInt16 nOpCode ) namespace HelperNotifyChanges { - static void NotifyIfChangesListeners(const ScDocShell &rDocShell, ScMarkData& rMark, + static void NotifyIfChangesListeners(const ScDocShell &rDocShell, const ScMarkData& rMark, SCCOL nCol, SCROW nRow, const OUString& rType = "cell-change") { ScModelObj* pModelObj = rDocShell.GetModel(); @@ -398,134 +438,41 @@ namespace } }; } - -// actual functions - -// input - undo OK -void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, - const OUString& rString, - const EditTextObject* pData, - bool bMatrixExpand ) +namespace { - ScDocument& rDoc = GetViewData().GetDocument(); - ScMarkData rMark(GetViewData().GetMarkData()); - bool bRecord = rDoc.IsUndoEnabled(); - SCTAB i; + void runAutoCorrectQueryAsync(std::shared_ptr<FormulaProcessingContext> context); - ScDocShell* pDocSh = GetViewData().GetDocShell(); - ScDocFunc &rFunc = GetViewData().GetDocFunc(); - ScDocShellModificator aModificator( *pDocSh ); - - ScEditableTester aTester( rDoc, nCol,nRow, nCol,nRow, rMark ); - if (!aTester.IsEditable()) + void performAutoFormatAndUpdate(const OUString& rString, const ScMarkData& rMark, SCCOL nCol, + SCROW nRow, SCTAB nTab, bool bNumFmtChanged, bool bRecord, + const std::shared_ptr<ScDocShellModificator>& pModificator, + ScViewFunc& rViewFunc) { - ErrorMessage(aTester.GetMessageId()); - PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there - return; - } + bool bAutoFormat = rViewFunc.TestFormatArea(nCol, nRow, nTab, bNumFmtChanged); - if ( bRecord ) - rFunc.EnterListAction( STR_UNDO_ENTERDATA ); + if (bAutoFormat) + rViewFunc.DoAutoAttributes(nCol, nRow, nTab, bNumFmtChanged); - bool bFormula = false; + ScViewData& rViewData = rViewFunc.GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + pDocSh->UpdateOle(rViewData); - // do not check formula if it is a text cell - sal_uInt32 format = rDoc.GetNumberFormat( nCol, nRow, nTab ); - SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); - // a single '=' character is handled as string (needed for special filters) - if ( pFormatter->GetType(format) != SvNumFormatType::TEXT && rString.getLength() > 1 ) - { - if ( rString[0] == '=' ) + const OUString aType(rString.isEmpty() ? u"delete-content" : u"cell-change"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow, aType); + + if (bRecord) { - // handle as formula - bFormula = true; + ScDocFunc &rFunc = rViewData.GetDocFunc(); + rFunc.EndListAction(); } - else if ( rString[0] == '+' || rString[0] == '-' ) - { - // if there is more than one leading '+' or '-' character, remove the additional ones - sal_Int32 nIndex = 1; - sal_Int32 nLen = rString.getLength(); - while ( nIndex < nLen && ( rString[ nIndex ] == '+' || rString[ nIndex ] == '-' ) ) - { - ++nIndex; - } - OUString aString = rString.replaceAt( 1, nIndex - 1, u"" ); - // if the remaining part without the leading '+' or '-' character - // is non-empty and not a number, handle as formula - if ( aString.getLength() > 1 ) - { - double fNumber = 0; - if ( !pFormatter->IsNumberFormat( aString, format, fNumber ) ) - { - bFormula = true; - } - } - } + pModificator->SetDocumentModified(); + ScDocument& rDoc = rViewData.GetDocument(); + lcl_PostRepaintCondFormat(rDoc.GetCondFormat(nCol, nRow, nTab), pDocSh); + lcl_PostRepaintSparkLine(rDoc.GetSparklineList(nTab), ScRange(nCol, nRow, nTab), pDocSh); } - bool bNumFmtChanged = false; - if ( bFormula ) - { // formula, compile with autoCorrection - i = rMark.GetFirstSelected(); - ScAddress aPos( nCol, nRow, i ); - ScCompiler aComp( rDoc, aPos, rDoc.GetGrammar(), true, false ); -//2do: enable/disable autoCorrection via calcoptions - aComp.SetAutoCorrection( true ); - if ( rString[0] == '+' || rString[0] == '-' ) - { - aComp.SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_BREAK ); - } - OUString aFormula( rString ); - std::unique_ptr< ScTokenArray > pArr; - bool bAgain; - do - { - bAgain = false; - bool bAddEqual = false; - pArr = aComp.CompileString( aFormula ); - bool bCorrected = aComp.IsCorrected(); - std::unique_ptr< ScTokenArray > pArrFirst; - if ( bCorrected ) - { // try to parse with first parser-correction - pArrFirst = std::move( pArr ); - pArr = aComp.CompileString( aComp.GetCorrectedFormula() ); - } - if ( pArr->GetCodeError() == FormulaError::NONE ) - { - bAddEqual = true; - aComp.CompileTokenArray(); - bCorrected |= aComp.IsCorrected(); - } - if ( bCorrected ) - { - OUString aCorrectedFormula; - if ( bAddEqual ) - { - aCorrectedFormula = "=" + aComp.GetCorrectedFormula(); - } - else - aCorrectedFormula = aComp.GetCorrectedFormula(); - short nResult; - if ( aCorrectedFormula.getLength() == 1 ) - nResult = RET_NO; // empty formula, just '=' - else - { - AutoCorrectQuery aQueryBox(GetViewData().GetDialogParent(), aCorrectedFormula); - nResult = aQueryBox.run(); - } - if ( nResult == RET_YES ) - { - aFormula = aCorrectedFormula; - bAgain = true; - } - else - { - if ( pArrFirst ) - pArr = std::move( pArrFirst ); - } - } - } while ( bAgain ); + void finalizeFormulaProcessing(std::shared_ptr<FormulaProcessingContext> context) + { // to be used in multiple tabs, the formula must be compiled anew // via ScFormulaCell copy-ctor because of RangeNames, // the same code-array for all cells is not possible. @@ -533,7 +480,7 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, // cells and the error be set explicitly, so that // via FormulaCell copy-ctor and Interpreter it will be, when possible, // ironed out again, too intelligent... e.g.: =1)) - FormulaError nError = pArr->GetCodeError(); + FormulaError nError = context->pArr->GetCodeError(); if ( nError == FormulaError::NONE ) { // update list of recent functions with all functions that @@ -543,8 +490,8 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, ScAppOptions aAppOpt = pScMod->GetAppOptions(); bool bOptChanged = false; - formula::FormulaToken** ppToken = pArr->GetArray(); - sal_uInt16 nTokens = pArr->GetLen(); + formula::FormulaToken** ppToken = context->pArr->GetArray(); + sal_uInt16 nTokens = context->pArr->GetLen(); sal_uInt16 nLevel = 0; for (sal_uInt16 nTP=0; nTP<nTokens; nTP++) { @@ -564,53 +511,55 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, pScMod->SetAppOptions(aAppOpt); } - if (bMatrixExpand) + if (context->bMatrixExpand) { // If the outer function/operator returns an array/matrix then // enter a matrix formula. ScViewFunc::EnterMatrix() takes care // of selection/mark of the result dimensions or preselected // mark. If the user wanted less or a single cell then should // mark such prior to entering the formula. - const formula::FormulaToken* pToken = pArr->LastRPNToken(); + const formula::FormulaToken* pToken = context->pArr->LastRPNToken(); if (pToken && (formula::FormulaCompiler::IsMatrixFunction( pToken->GetOpCode()) || pToken->IsInForceArray())) { // Discard this (still empty here) Undo action, // EnterMatrix() will create its own. - if (bRecord) - rFunc.EndListAction(); + if (context->bRecord) + context->GetDocFunc().EndListAction(); // Use corrected formula string. - EnterMatrix( aFormula, rDoc.GetGrammar()); + context->rViewFunc.EnterMatrix( context->aFormula, context->GetDoc().GetGrammar()); return; } } } - ScFormulaCell aCell(rDoc, aPos, std::move( pArr ), formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::NONE); + ScFormulaCell aCell(context->GetDoc(), *context->aPos, std::move(*context->pArr), formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::NONE); - for (const auto& rTab : rMark) + SCTAB i; + SvNumberFormatter* pFormatter = context->GetDoc().GetFormatTable(); + for (const auto& rTab : context->aMark) { i = rTab; - aPos.SetTab( i ); - const sal_uInt32 nIndex = rDoc.GetAttr( - nCol, nRow, i, ATTR_VALUE_FORMAT )->GetValue(); + context->aPos->SetTab( i ); + const sal_uInt32 nIndex = context->GetDoc().GetAttr( + context->nCol, context->nRow, i, ATTR_VALUE_FORMAT )->GetValue(); const SvNumFormatType nType = pFormatter->GetType( nIndex); if (nType == SvNumFormatType::TEXT || - ((rString[0] == '+' || rString[0] == '-') && nError != FormulaError::NONE && rString == aFormula)) + ((context->aString[0] == '+' || context->aString[0] == '-') && nError != FormulaError::NONE && context->aString == context->aFormula)) { - if ( pData ) + if ( context->pData ) { - // A clone of pData will be stored in the cell. - rFunc.SetEditCell(aPos, *pData, true); + // A clone of context->pData will be stored in the cell. + context->GetDocFunc().SetEditCell(*(context->aPos), *context->pData, true); } else - rFunc.SetStringCell(aPos, aFormula, true); + context->GetDocFunc().SetStringCell(*(context->aPos), context->aFormula, true); } else { - ScFormulaCell* pCell = new ScFormulaCell( aCell, rDoc, aPos ); + ScFormulaCell* pCell = new ScFormulaCell( aCell, context->GetDoc(), *(context->aPos) ); if ( nError != FormulaError::NONE ) { pCell->GetCode()->DelRPN(); @@ -627,17 +576,166 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, const SvNumberformat* pEntry = pFormatter->GetEntry( nIndex); const LanguageType nLang = (pEntry ? pEntry->GetLanguage() : ScGlobal::eLnge); const sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, nLang); - ScPatternAttr aPattern( rDoc.GetPool()); + ScPatternAttr aPattern( context->GetDoc().GetPool()); aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nFormat)); - ScMarkData aMark(rDoc.GetSheetLimits()); + ScMarkData aMark(context->GetDoc().GetSheetLimits()); aMark.SelectTable( i, true); - aMark.SetMarkArea( ScRange( aPos)); - rFunc.ApplyAttributes( aMark, aPattern, false); - bNumFmtChanged = true; + aMark.SetMarkArea( ScRange( *(context->aPos))); + context->GetDocFunc().ApplyAttributes( aMark, aPattern, false); + context->bNumFmtChanged = true; } - rFunc.SetFormulaCell(aPos, pCell, true); + context->GetDocFunc().SetFormulaCell(*(context->aPos), pCell, true); } } + + performAutoFormatAndUpdate(context->aString, context->aMark, context->nCol, + context->nRow, context->nTab, context->bNumFmtChanged, + context->bRecord, context->aModificator, context->rViewFunc); + } + + void parseAndCorrectFormula(std::shared_ptr<FormulaProcessingContext> context) + { + bool bAddEqual = false; + context->pArr = context->aComp->CompileString(context->aFormula); + bool bCorrected = context->aComp->IsCorrected(); + + if (bCorrected) { + context->pArrFirst = context->pArr; + context->pArr = context->aComp->CompileString(context->aComp->GetCorrectedFormula()); + } + + if (context->pArr->GetCodeError() == FormulaError::NONE) { + bAddEqual = true; + context->aComp->CompileTokenArray(); + bCorrected |= context->aComp->IsCorrected(); + } + + if (bCorrected) { + context->aCorrectedFormula = bAddEqual ? "=" + context->aComp->GetCorrectedFormula() + : context->aComp->GetCorrectedFormula(); + if (context->aCorrectedFormula.getLength() == 1) { + // empty formula, just '=' + if (context->pArrFirst) + context->pArr = context->pArrFirst; + } + else + { + runAutoCorrectQueryAsync(context); + return; + } + } + finalizeFormulaProcessing(context); + } + + void runAutoCorrectQueryAsync(std::shared_ptr<FormulaProcessingContext> context) + { + auto aQueryBox = std::make_shared<AutoCorrectQuery>(context->GetViewData().GetDialogParent(), context->aCorrectedFormula); + aQueryBox->runAsync(aQueryBox, [context] (int nResult) + { + if (nResult == RET_YES) { + context->aFormula = context->aCorrectedFormula; + parseAndCorrectFormula(context); + } else { + if (context->pArrFirst) + context->pArr = context->pArrFirst; + + finalizeFormulaProcessing(context); + } + }); + } +} + +// actual functions + +// input - undo OK +void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, + const OUString& rString, + const EditTextObject* pData, + bool bMatrixExpand ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData rMark(GetViewData().GetMarkData()); + bool bRecord = rDoc.IsUndoEnabled(); + SCTAB i; + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocFunc &rFunc = GetViewData().GetDocFunc(); + std::shared_ptr<ScDocShellModificator> xModificator = std::make_shared<ScDocShellModificator>(*pDocSh); + + ScEditableTester aTester( rDoc, nCol,nRow, nCol,nRow, rMark ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there + return; + } + + if ( bRecord ) + rFunc.EnterListAction( STR_UNDO_ENTERDATA ); + + bool bFormula = false; + + // do not check formula if it is a text cell + sal_uInt32 format = rDoc.GetNumberFormat( nCol, nRow, nTab ); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + // a single '=' character is handled as string (needed for special filters) + if ( pFormatter->GetType(format) != SvNumFormatType::TEXT && rString.getLength() > 1 ) + { + if ( rString[0] == '=' ) + { + // handle as formula + bFormula = true; + } + else if ( rString[0] == '+' || rString[0] == '-' ) + { + // if there is more than one leading '+' or '-' character, remove the additional ones + sal_Int32 nIndex = 1; + sal_Int32 nLen = rString.getLength(); + while ( nIndex < nLen && ( rString[ nIndex ] == '+' || rString[ nIndex ] == '-' ) ) + { + ++nIndex; + } + OUString aString = rString.replaceAt( 1, nIndex - 1, u"" ); + + // if the remaining part without the leading '+' or '-' character + // is non-empty and not a number, handle as formula + if ( aString.getLength() > 1 ) + { + double fNumber = 0; + if ( !pFormatter->IsNumberFormat( aString, format, fNumber ) ) + { + bFormula = true; + } + } + } + } + + bool bNumFmtChanged = false; + if ( bFormula ) + { // formula, compile with autoCorrection + i = rMark.GetFirstSelected(); + auto aPosPtr = std::make_shared<ScAddress>(nCol, nRow, i); + auto aCompPtr = std::make_shared<ScCompiler>(rDoc, *aPosPtr, rDoc.GetGrammar(), true, false); + + //2do: enable/disable autoCorrection via calcoptions + aCompPtr->SetAutoCorrection( true ); + if ( rString[0] == '+' || rString[0] == '-' ) + { + aCompPtr->SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_BREAK ); + } + + OUString aFormula( rString ); + std::shared_ptr< ScTokenArray > pArr; + + FormulaProcessingContext context_instance{ + aPosPtr, aCompPtr, xModificator, pArr, nullptr, pData, + rMark, *this, OUString(), aFormula, rString, nCol, + nRow, nTab, bMatrixExpand, bNumFmtChanged, bRecord + }; + + std::shared_ptr<FormulaProcessingContext> context = std::make_shared<FormulaProcessingContext>(context_instance); + + parseAndCorrectFormula(context); } else { @@ -666,24 +764,8 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, bNumFmtChanged = true; } } + performAutoFormatAndUpdate(rString, rMark, nCol, nRow, nTab, bNumFmtChanged, bRecord, xModificator, *this); } - - bool bAutoFormat = TestFormatArea(nCol, nRow, nTab, bNumFmtChanged); - - if (bAutoFormat) - DoAutoAttributes(nCol, nRow, nTab, bNumFmtChanged); - - pDocSh->UpdateOle(GetViewData()); - - const OUString aType(rString.isEmpty() ? u"delete-content" : u"cell-change"); - HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow, aType); - - if ( bRecord ) - rFunc.EndListAction(); - - aModificator.SetDocumentModified(); - lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), pDocSh ); - lcl_PostRepaintSparkLine(rDoc.GetSparklineList(nTab), ScRange(nCol, nRow, nTab), pDocSh); } // enter value in single cell (on nTab only)