diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /formula/source | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'formula/source')
-rw-r--r-- | formula/source/core/api/FormulaCompiler.cxx | 3119 | ||||
-rw-r--r-- | formula/source/core/api/FormulaOpCodeMapperObj.cxx | 104 | ||||
-rw-r--r-- | formula/source/core/api/grammar.cxx | 87 | ||||
-rw-r--r-- | formula/source/core/api/token.cxx | 2033 | ||||
-rw-r--r-- | formula/source/core/api/vectortoken.cxx | 109 | ||||
-rw-r--r-- | formula/source/core/resource/core_resource.cxx | 23 | ||||
-rw-r--r-- | formula/source/ui/dlg/ControlHelper.hxx | 106 | ||||
-rw-r--r-- | formula/source/ui/dlg/FormulaHelper.cxx | 416 | ||||
-rw-r--r-- | formula/source/ui/dlg/formula.cxx | 1961 | ||||
-rw-r--r-- | formula/source/ui/dlg/funcpage.cxx | 267 | ||||
-rw-r--r-- | formula/source/ui/dlg/funcpage.hxx | 93 | ||||
-rw-r--r-- | formula/source/ui/dlg/funcutl.cxx | 486 | ||||
-rw-r--r-- | formula/source/ui/dlg/parawin.cxx | 600 | ||||
-rw-r--r-- | formula/source/ui/dlg/parawin.hxx | 145 | ||||
-rw-r--r-- | formula/source/ui/dlg/structpg.cxx | 154 | ||||
-rw-r--r-- | formula/source/ui/dlg/structpg.hxx | 74 |
16 files changed, 9777 insertions, 0 deletions
diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx new file mode 100644 index 0000000000..dc9c73d793 --- /dev/null +++ b/formula/source/core/api/FormulaCompiler.cxx @@ -0,0 +1,3119 @@ +/* -*- 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 <sal/macros.h> +#include <sal/log.hxx> +#include <rtl/math.hxx> +#include <formula/FormulaCompiler.hxx> +#include <formula/errorcodes.hxx> +#include <formula/token.hxx> +#include <formula/tokenarray.hxx> +#include <o3tl/string_view.hxx> +#include <core_resource.hxx> +#include <core_resource.hrc> + +#include <svl/zforlist.hxx> +#include <unotools/charclass.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp> +#include <com/sun/star/sheet/FormulaMapGroup.hpp> +#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp> +#include <algorithm> +#include <mutex> + +namespace formula +{ + using namespace ::com::sun::star; + + static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" }; + +namespace { + +class FormulaCompilerRecursionGuard +{ + private: + short& rRecursion; + public: + explicit FormulaCompilerRecursionGuard( short& rRec ) + : rRecursion( rRec ) { ++rRecursion; } + ~FormulaCompilerRecursionGuard() { --rRecursion; } +}; + +SvNumFormatType lcl_GetRetFormat( OpCode eOpCode ) +{ + switch (eOpCode) + { + case ocEqual: + case ocNotEqual: + case ocLess: + case ocGreater: + case ocLessEqual: + case ocGreaterEqual: + case ocAnd: + case ocOr: + case ocXor: + case ocNot: + case ocTrue: + case ocFalse: + case ocIsEmpty: + case ocIsString: + case ocIsNonString: + case ocIsLogical: + case ocIsRef: + case ocIsValue: + case ocIsFormula: + case ocIsNA: + case ocIsErr: + case ocIsError: + case ocIsEven: + case ocIsOdd: + case ocExact: + return SvNumFormatType::LOGICAL; + case ocGetActDate: + case ocGetDate: + case ocEasterSunday : + return SvNumFormatType::DATE; + case ocGetActTime: + return SvNumFormatType::DATETIME; + case ocGetTime: + return SvNumFormatType::TIME; + case ocNPV: + case ocPV: + case ocSYD: + case ocDDB: + case ocDB: + case ocVBD: + case ocSLN: + case ocPMT: + case ocFV: + case ocIpmt: + case ocPpmt: + case ocCumIpmt: + case ocCumPrinc: + return SvNumFormatType::CURRENCY; + case ocRate: + case ocIRR: + case ocMIRR: + case ocRRI: + case ocEffect: + case ocNominal: + case ocPercentSign: + return SvNumFormatType::PERCENT; + default: + return SvNumFormatType::NUMBER; + } +} + +void lclPushOpCodeMapEntry( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec, + const OUString* pTable, sal_uInt16 nOpCode ) +{ + sheet::FormulaOpCodeMapEntry aEntry; + aEntry.Token.OpCode = nOpCode; + aEntry.Name = pTable[nOpCode]; + rVec.push_back( aEntry); +} + +void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec, + const OUString* pTable, sal_uInt16 nOpCodeBeg, sal_uInt16 nOpCodeEnd ) +{ + for (sal_uInt16 nOpCode = nOpCodeBeg; nOpCode < nOpCodeEnd; ++nOpCode) + lclPushOpCodeMapEntry( rVec, pTable, nOpCode ); +} + +void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec, + const OUString* pTable, const sal_uInt16* pnOpCodes, size_t nCount ) +{ + for (const sal_uInt16* pnEnd = pnOpCodes + nCount; pnOpCodes < pnEnd; ++pnOpCodes) + lclPushOpCodeMapEntry( rVec, pTable, *pnOpCodes ); +} + +CharClass* createCharClassIfNonEnglishUI() +{ + const LanguageTag& rLanguageTag( Application::GetSettings().GetUILanguageTag()); + if (rLanguageTag.getLanguage() == "en") + return nullptr; + return new CharClass( ::comphelper::getProcessComponentContext(), rLanguageTag); +} + +class OpCodeList +{ +public: + + OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&, + FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE ); + OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&, + FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE ); + +private: + bool getOpCodeString( OUString& rStr, sal_uInt16 nOp ); + void putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp, const CharClass* pCharClass ); + +private: + FormulaCompiler::SeparatorType meSepType; + const std::pair<const char*, int>* mpSymbols1; + const std::pair<TranslateId, int>* mpSymbols2; +}; + +OpCodeList::OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap, + FormulaCompiler::SeparatorType eSepType) + : meSepType(eSepType) + , mpSymbols1(pSymbols) + , mpSymbols2(nullptr) +{ + std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI()); + const CharClass* pCharClass = xCharClass.get(); + if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE) + { + for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i) + { + putDefaultOpCode( xMap, i, pCharClass); + } + } + else + { + for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i) + { + OUString aOpStr; + if ( getOpCodeString( aOpStr, i) ) + xMap->putOpCode( aOpStr, OpCode(i), pCharClass); + else + putDefaultOpCode( xMap, i, pCharClass); + } + } +} + +OpCodeList::OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap, + FormulaCompiler::SeparatorType eSepType) + : meSepType(eSepType) + , mpSymbols1(nullptr) + , mpSymbols2(pSymbols) +{ + std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI()); + const CharClass* pCharClass = xCharClass.get(); + if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE) + { + for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i) + { + putDefaultOpCode( xMap, i, pCharClass); + } + } + else + { + for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i) + { + OUString aOpStr; + if ( getOpCodeString( aOpStr, i) ) + xMap->putOpCode( aOpStr, OpCode(i), pCharClass); + else + putDefaultOpCode( xMap, i, pCharClass); + } + } +} + +bool OpCodeList::getOpCodeString( OUString& rStr, sal_uInt16 nOp ) +{ + switch (nOp) + { + case SC_OPCODE_SEP: + { + if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE) + { + rStr = ";"; + return true; + } + } + break; + case SC_OPCODE_ARRAY_COL_SEP: + { + if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE) + { + rStr = ";"; + return true; + } + } + break; + case SC_OPCODE_ARRAY_ROW_SEP: + { + if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE) + { + rStr = "|"; + return true; + } + } + break; + } + + return false; +} + +void OpCodeList::putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp, + const CharClass* pCharClass ) +{ + OUString sKey; + if (mpSymbols1) + { + const char* pKey = nullptr; + for (const std::pair<const char*, int>* pSymbol = mpSymbols1; pSymbol->first; ++pSymbol) + { + if (nOp == pSymbol->second) + { + pKey = pSymbol->first; + break; + } + } + if (!pKey) + return; + sKey = OUString::createFromAscii(pKey); + } + else if (mpSymbols2) + { + TranslateId pKey; + for (const std::pair<TranslateId, int>* pSymbol = mpSymbols2; pSymbol->first; ++pSymbol) + { + if (nOp == pSymbol->second) + { + pKey = pSymbol->first; + break; + } + } + if (!pKey) + return; + sKey = ForResId(pKey); + } + xMap->putOpCode(sKey, OpCode(nOp), pCharClass); +} + +// static +const sal_Unicode* lcl_UnicodeStrChr( const sal_Unicode* pStr, sal_Unicode c ) +{ + if ( !pStr ) + return nullptr; + while ( *pStr ) + { + if ( *pStr == c ) + return pStr; + pStr++; + } + return nullptr; +} + +struct OpCodeMapData +{ + FormulaCompiler::NonConstOpCodeMapPtr mxSymbolMap; + std::mutex maMtx; +}; + + +bool isPotentialRangeLeftOp( OpCode eOp ) +{ + switch (eOp) + { + case ocClose: + return true; + default: + return false; + } +} + +bool isRangeResultFunction( OpCode eOp ) +{ + switch (eOp) + { + case ocIndirect: + case ocOffset: + return true; + default: + return false; + } +} + +bool isRangeResultOpCode( OpCode eOp ) +{ + switch (eOp) + { + case ocRange: + case ocUnion: + case ocIntersect: + case ocIndirect: + case ocOffset: + return true; + default: + return false; + } +} + +/** + @param pToken + MUST be a valid token, caller has to ensure. + + @param bRight + If bRPN==false, bRight==false means opcodes for left side are + checked, bRight==true means opcodes for right side. If bRPN==true + it doesn't matter except for the ocSep converted to ocUnion case. + */ +bool isPotentialRangeType( FormulaToken const * pToken, bool bRPN, bool bRight ) +{ + switch (pToken->GetType()) + { + case svByte: // could be range result, but only a few + if (bRPN) + return isRangeResultOpCode( pToken->GetOpCode()); + else if (bRight) + return isRangeResultFunction( pToken->GetOpCode()); + else + return isPotentialRangeLeftOp( pToken->GetOpCode()); + case svSingleRef: + case svDoubleRef: + case svIndex: // could be range + //case svRefList: // um..what? + case svExternalSingleRef: + case svExternalDoubleRef: + case svExternalName: // could be range + return true; + case svSep: + // A special case if a previous ocSep was converted to ocUnion it + // stays svSep instead of svByte. + return bRPN && !bRight && pToken->GetOpCode() == ocUnion; + default: + // Separators are not part of RPN and right opcodes need to be + // other StackVar types or functions and thus svByte. + return !bRPN && !bRight && isPotentialRangeLeftOp( pToken->GetOpCode()); + } +} + +bool isIntersectable( FormulaToken** pCode1, FormulaToken** pCode2 ) +{ + FormulaToken* pToken1 = *pCode1; + FormulaToken* pToken2 = *pCode2; + if (pToken1 && pToken2) + return isPotentialRangeType( pToken1, true, false) && isPotentialRangeType( pToken2, true, true); + return false; +} + +bool isAdjacentRpnEnd( sal_uInt16 nPC, + FormulaToken const * const * const pCode, + FormulaToken const * const * const pCode1, + FormulaToken const * const * const pCode2 ) +{ + return nPC >= 2 && pCode1 && pCode2 && + (pCode2 - pCode1 == 1) && (pCode - pCode2 == 1) && + (*pCode1 != nullptr) && (*pCode2 != nullptr); +} + +bool isAdjacentOrGapRpnEnd( sal_uInt16 nPC, + FormulaToken const * const * const pCode, + FormulaToken const * const * const pCode1, + FormulaToken const * const * const pCode2 ) +{ + return nPC >= 2 && pCode1 && pCode2 && + (pCode2 > pCode1) && (pCode - pCode2 == 1) && + (*pCode1 != nullptr) && (*pCode2 != nullptr); +} + + +} // namespace + + +void FormulaCompiler::OpCodeMap::putExternal( const OUString & rSymbol, const OUString & rAddIn ) +{ + // Different symbols may map to the same AddIn, but the same AddIn may not + // map to different symbols, the first pair wins. Same symbol of course may + // not map to different AddIns, again the first pair wins and also the + // AddIn->symbol mapping is not inserted in other cases. + bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second; + SAL_WARN_IF( !bOk, "formula.core", "OpCodeMap::putExternal: symbol not inserted, " << rSymbol << " -> " << rAddIn); + if (bOk) + { + bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second; + // Failed insertion of the AddIn is ok for different symbols mapping to + // the same AddIn. Make this INFO only. + SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternal: AddIn not inserted, " << rAddIn << " -> " << rSymbol); + } +} + +void FormulaCompiler::OpCodeMap::putExternalSoftly( const OUString & rSymbol, const OUString & rAddIn ) +{ + // Same as putExternal() but no warning, instead info whether inserted or not. + bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second; + SAL_INFO( "formula.core", "OpCodeMap::putExternalSoftly: symbol " << (bOk ? "" : "not ") << "inserted, " << rSymbol << " -> " << rAddIn); + if (bOk) + { + bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second; + SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternalSoftly: AddIn not inserted, " << rAddIn << " -> " << rSymbol); + } +} + +uno::Sequence< sheet::FormulaToken > FormulaCompiler::OpCodeMap::createSequenceOfFormulaTokens( + const FormulaCompiler& rCompiler, const uno::Sequence< OUString >& rNames ) const +{ + const sal_Int32 nLen = rNames.getLength(); + uno::Sequence< sheet::FormulaToken > aTokens( nLen); + sheet::FormulaToken* pToken = aTokens.getArray(); + OUString const * pName = rNames.getConstArray(); + OUString const * const pStop = pName + nLen; + for ( ; pName < pStop; ++pName, ++pToken) + { + OpCodeHashMap::const_iterator iLook( maHashMap.find( *pName)); + if (iLook != maHashMap.end()) + pToken->OpCode = (*iLook).second; + else + { + OUString aIntName; + if (hasExternals()) + { + ExternalHashMap::const_iterator iExt( maExternalHashMap.find( *pName)); + if (iExt != maExternalHashMap.end()) + aIntName = (*iExt).second; + // Check for existence not needed here, only name-mapping is of + // interest. + } + if (aIntName.isEmpty()) + aIntName = rCompiler.FindAddInFunction(*pName, !isEnglish()); // bLocalFirst=false for english + if (aIntName.isEmpty()) + pToken->OpCode = getOpCodeUnknown(); + else + { + pToken->OpCode = ocExternal; + pToken->Data <<= aIntName; + } + } + } + return aTokens; +} + +uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::createSequenceOfAvailableMappings( + const FormulaCompiler& rCompiler, const sal_Int32 nGroups ) const +{ + using namespace sheet; + + // Unfortunately uno::Sequence can't grow without cumbersome reallocs. As + // we don't know in advance how many elements it will have we use a + // temporary vector to add elements and then copy to Sequence :-( + ::std::vector< FormulaOpCodeMapEntry > aVec; + + if (nGroups == FormulaMapGroup::SPECIAL) + { + // Use specific order, keep in sync with + // offapi/com/sun/star/sheet/FormulaMapGroupSpecialOffset.idl + static const struct + { + sal_Int32 nOff; + OpCode eOp; + } aMap[] = { + { FormulaMapGroupSpecialOffset::PUSH , ocPush } , + { FormulaMapGroupSpecialOffset::CALL , ocCall } , + { FormulaMapGroupSpecialOffset::STOP , ocStop } , + { FormulaMapGroupSpecialOffset::EXTERNAL , ocExternal } , + { FormulaMapGroupSpecialOffset::NAME , ocName } , + { FormulaMapGroupSpecialOffset::NO_NAME , ocNoName } , + { FormulaMapGroupSpecialOffset::MISSING , ocMissing } , + { FormulaMapGroupSpecialOffset::BAD , ocBad } , + { FormulaMapGroupSpecialOffset::SPACES , ocSpaces } , + { FormulaMapGroupSpecialOffset::MAT_REF , ocMatRef } , + { FormulaMapGroupSpecialOffset::DB_AREA , ocDBArea } , + /* TODO: { FormulaMapGroupSpecialOffset::TABLE_REF , ocTableRef } , */ + { FormulaMapGroupSpecialOffset::MACRO , ocMacro } , + { FormulaMapGroupSpecialOffset::COL_ROW_NAME , ocColRowName } , + { FormulaMapGroupSpecialOffset::WHITESPACE , ocWhitespace } + }; + const size_t nCount = SAL_N_ELEMENTS(aMap); + // Preallocate vector elements. + FormulaOpCodeMapEntry aEntry; + aEntry.Token.OpCode = getOpCodeUnknown(); + aVec.resize(nCount, aEntry); + + for (auto& i : aMap) + { + size_t nIndex = static_cast< size_t >( i.nOff ); + if (aVec.size() <= nIndex) + { + // The offsets really should be aligned with the size, so if + // the vector was preallocated above this code to resize it is + // just a measure in case the table isn't in sync with the API, + // usually it isn't executed. + aEntry.Token.OpCode = getOpCodeUnknown(); + aVec.resize( nIndex + 1, aEntry ); + } + aEntry.Token.OpCode = i.eOp; + aVec[nIndex] = aEntry; + } + } + else + { + /* FIXME: Once we support error constants in formulas we'll need a map + * group for that, e.g. FormulaMapGroup::ERROR_CONSTANTS, and fill + * SC_OPCODE_START_ERRORS to SC_OPCODE_STOP_ERRORS. */ + + // Anything else but SPECIAL. + if ((nGroups & FormulaMapGroup::SEPARATORS) != 0) + { + static const sal_uInt16 aOpCodes[] = { + SC_OPCODE_OPEN, + SC_OPCODE_CLOSE, + SC_OPCODE_SEP, + }; + lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) ); + } + if ((nGroups & FormulaMapGroup::ARRAY_SEPARATORS) != 0) + { + static const sal_uInt16 aOpCodes[] = { + SC_OPCODE_ARRAY_OPEN, + SC_OPCODE_ARRAY_CLOSE, + SC_OPCODE_ARRAY_ROW_SEP, + SC_OPCODE_ARRAY_COL_SEP + }; + lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) ); + } + if ((nGroups & FormulaMapGroup::UNARY_OPERATORS) != 0) + { + // Due to the nature of the percent operator following its operand + // it isn't sorted into unary operators for compiler interna. + lclPushOpCodeMapEntry( aVec, mpTable.get(), ocPercentSign ); + // "+" can be used as unary operator too, push only if binary group is not set + if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) == 0) + lclPushOpCodeMapEntry( aVec, mpTable.get(), ocAdd ); + // regular unary operators + for (sal_uInt16 nOp = SC_OPCODE_START_UN_OP; nOp < SC_OPCODE_STOP_UN_OP && nOp < mnSymbols; ++nOp) + { + lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp ); + } + } + if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) != 0) + { + for (sal_uInt16 nOp = SC_OPCODE_START_BIN_OP; nOp < SC_OPCODE_STOP_BIN_OP && nOp < mnSymbols; ++nOp) + { + switch (nOp) + { + // AND and OR in fact are functions but for legacy reasons + // are sorted into binary operators for compiler interna. + case SC_OPCODE_AND : + case SC_OPCODE_OR : + break; // nothing, + default: + lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp ); + } + } + } + if ((nGroups & FormulaMapGroup::FUNCTIONS) != 0) + { + // Function names are not consecutive, skip the gaps between + // functions with no parameter, functions with 1 parameter + lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_NO_PAR, + ::std::min< sal_uInt16 >( SC_OPCODE_STOP_NO_PAR, mnSymbols ) ); + lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_1_PAR, + ::std::min< sal_uInt16 >( SC_OPCODE_STOP_1_PAR, mnSymbols ) ); + // Additional functions not within range of functions. + static const sal_uInt16 aOpCodes[] = { + SC_OPCODE_IF, + SC_OPCODE_IF_ERROR, + SC_OPCODE_IF_NA, + SC_OPCODE_CHOOSE, + SC_OPCODE_AND, + SC_OPCODE_OR + }; + lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) ); + // functions with 2 or more parameters. + for (sal_uInt16 nOp = SC_OPCODE_START_2_PAR; nOp < SC_OPCODE_STOP_2_PAR && nOp < mnSymbols; ++nOp) + { + switch (nOp) + { + // NO_NAME is in SPECIAL. + case SC_OPCODE_NO_NAME : + break; // nothing, + default: + lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp ); + } + } + // If AddIn functions are present in this mapping, use them, and only those. + if (hasExternals()) + { + for (auto const& elem : maExternalHashMap) + { + FormulaOpCodeMapEntry aEntry; + aEntry.Name = elem.first; + aEntry.Token.Data <<= elem.second; + aEntry.Token.OpCode = ocExternal; + aVec.push_back( aEntry); + } + } + else + { + rCompiler.fillAddInToken( aVec, isEnglish()); + } + } + } + return uno::Sequence< FormulaOpCodeMapEntry >(aVec.data(), aVec.size()); +} + + +void FormulaCompiler::OpCodeMap::putOpCode( const OUString & rStr, const OpCode eOp, const CharClass* pCharClass ) +{ + if (0 < eOp && sal_uInt16(eOp) < mnSymbols) + { + bool bPutOp = mpTable[eOp].isEmpty(); + bool bRemoveFromMap = false; + if (!bPutOp) + { + switch (eOp) + { + // These OpCodes are meant to overwrite and also remove an + // existing mapping. + case ocCurrency: + bPutOp = true; + bRemoveFromMap = true; + break; + // These separator OpCodes are meant to overwrite and also + // remove an existing mapping if it is not used for one of the + // other separators. + case ocArrayColSep: + bPutOp = true; + bRemoveFromMap = (mpTable[ocArrayRowSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]); + break; + case ocArrayRowSep: + bPutOp = true; + bRemoveFromMap = (mpTable[ocArrayColSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]); + break; + // For ocSep keep the ";" in map but remove any other if it is + // not used for ocArrayColSep or ocArrayRowSep. + case ocSep: + bPutOp = true; + bRemoveFromMap = (mpTable[eOp] != ";" && + mpTable[ocArrayColSep] != mpTable[eOp] && + mpTable[ocArrayRowSep] != mpTable[eOp]); + break; + // These OpCodes are known to be duplicates in the Excel + // external API mapping because of different parameter counts + // in different BIFF versions. Names are identical and entries + // are ignored. + case ocLinest: + case ocTrend: + case ocLogest: + case ocGrowth: + case ocTrunc: + case ocFixed: + case ocGetDayOfWeek: + case ocHLookup: + case ocVLookup: + case ocGetDiffDate360: + if (rStr == mpTable[eOp]) + return; + [[fallthrough]]; + // These OpCodes are known to be added to an existing mapping, + // but only for the OOXML external API mapping. This is *not* + // FormulaLanguage::OOXML. Keep the first + // (correct) definition for the OpCode, all following are + // additional alias entries in the map. + case ocErrorType: + case ocMultiArea: + case ocBackSolver: + case ocEasterSunday: + case ocCurrent: + case ocStyle: + if (mbEnglish && + FormulaGrammar::extractFormulaLanguage( meGrammar) == FormulaGrammar::GRAM_EXTERNAL) + { + // Both bPutOp and bRemoveFromMap stay false. + break; + } + [[fallthrough]]; + default: + SAL_WARN("formula.core", + "OpCodeMap::putOpCode: reusing OpCode " << static_cast<sal_uInt16>(eOp) + << ", replacing '" << mpTable[eOp] << "' with '" << rStr << "' in " + << (mbEnglish ? "" : "non-") << "English map 0x" << ::std::hex << meGrammar); + } + } + + // Case preserving opcode -> string, upper string -> opcode + if (bRemoveFromMap) + { + OUString aUpper( pCharClass ? pCharClass->uppercase( mpTable[eOp]) : rStr.toAsciiUpperCase()); + // Ensure we remove a mapping only for the requested OpCode. + OpCodeHashMap::const_iterator it( maHashMap.find( aUpper)); + if (it != maHashMap.end() && (*it).second == eOp) + maHashMap.erase( it); + } + if (bPutOp) + mpTable[eOp] = rStr; + OUString aUpper( pCharClass ? pCharClass->uppercase( rStr) : rStr.toAsciiUpperCase()); + maHashMap.emplace(aUpper, eOp); + } + else + { + SAL_WARN( "formula.core", "OpCodeMap::putOpCode: OpCode out of range"); + } +} + + +FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr, bool bComputeII, bool bMatrixFlag ) + : + nCurrentFactorParam(0), + pArr( &rArr ), + maArrIterator( rArr ), + pCode( nullptr ), + pStack( nullptr ), + eLastOp( ocPush ), + nRecursion( 0 ), + nNumFmt( SvNumFormatType::UNDEFINED ), + pc( 0 ), + meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ), + bAutoCorrect( false ), + bCorrected( false ), + glSubTotal( false ), + needsRPNTokenCheck( false ), + mbJumpCommandReorder(true), + mbStopOnError(true), + mbComputeII(bComputeII), + mbMatrixFlag(bMatrixFlag) +{ +} + +FormulaTokenArray FormulaCompiler::smDummyTokenArray; + +FormulaCompiler::FormulaCompiler(bool bComputeII, bool bMatrixFlag) + : + nCurrentFactorParam(0), + pArr( nullptr ), + maArrIterator( smDummyTokenArray ), + pCode( nullptr ), + pStack( nullptr ), + eLastOp( ocPush ), + nRecursion(0), + nNumFmt( SvNumFormatType::UNDEFINED ), + pc( 0 ), + meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ), + bAutoCorrect( false ), + bCorrected( false ), + glSubTotal( false ), + needsRPNTokenCheck( false ), + mbJumpCommandReorder(true), + mbStopOnError(true), + mbComputeII(bComputeII), + mbMatrixFlag(bMatrixFlag) +{ +} + +FormulaCompiler::~FormulaCompiler() +{ +} + +FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetOpCodeMap( const sal_Int32 nLanguage ) const +{ + const bool bTemporary = !HasOpCodeMap(nLanguage); + OpCodeMapPtr xMap = GetFinalOpCodeMap(nLanguage); + if (bTemporary) + const_cast<FormulaCompiler*>(this)->DestroyOpCodeMap(nLanguage); + return xMap; +} + +FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetFinalOpCodeMap( const sal_Int32 nLanguage ) const +{ + FormulaCompiler::OpCodeMapPtr xMap; + using namespace sheet; + switch (nLanguage) + { + case FormulaLanguage::ODFF : + if (!mxSymbolsODFF) + InitSymbolsODFF( InitSymbols::INIT); + xMap = mxSymbolsODFF; + break; + case FormulaLanguage::ODF_11 : + if (!mxSymbolsPODF) + InitSymbolsPODF( InitSymbols::INIT); + xMap = mxSymbolsPODF; + break; + case FormulaLanguage::ENGLISH : + if (!mxSymbolsEnglish) + InitSymbolsEnglish( InitSymbols::INIT); + xMap = mxSymbolsEnglish; + break; + case FormulaLanguage::NATIVE : + if (!mxSymbolsNative) + InitSymbolsNative( InitSymbols::INIT); + xMap = mxSymbolsNative; + break; + case FormulaLanguage::XL_ENGLISH: + if (!mxSymbolsEnglishXL) + InitSymbolsEnglishXL( InitSymbols::INIT); + xMap = mxSymbolsEnglishXL; + break; + case FormulaLanguage::OOXML: + if (!mxSymbolsOOXML) + InitSymbolsOOXML( InitSymbols::INIT); + xMap = mxSymbolsOOXML; + break; + case FormulaLanguage::API : + if (!mxSymbolsAPI) + InitSymbolsAPI( InitSymbols::INIT); + xMap = mxSymbolsAPI; + break; + default: + ; // nothing, NULL map returned + } + return xMap; +} + +void FormulaCompiler::DestroyOpCodeMap( const sal_Int32 nLanguage ) +{ + using namespace sheet; + switch (nLanguage) + { + case FormulaLanguage::ODFF : + InitSymbolsODFF( InitSymbols::DESTROY); + break; + case FormulaLanguage::ODF_11 : + InitSymbolsPODF( InitSymbols::DESTROY); + break; + case FormulaLanguage::ENGLISH : + InitSymbolsEnglish( InitSymbols::DESTROY); + break; + case FormulaLanguage::NATIVE : + InitSymbolsNative( InitSymbols::DESTROY); + break; + case FormulaLanguage::XL_ENGLISH: + InitSymbolsEnglishXL( InitSymbols::DESTROY); + break; + case FormulaLanguage::OOXML: + InitSymbolsOOXML( InitSymbols::DESTROY); + break; + case FormulaLanguage::API : + InitSymbolsAPI( InitSymbols::DESTROY); + break; + default: + ; // nothing + } +} + +bool FormulaCompiler::HasOpCodeMap( const sal_Int32 nLanguage ) const +{ + using namespace sheet; + switch (nLanguage) + { + case FormulaLanguage::ODFF : + return InitSymbolsODFF( InitSymbols::ASK); + case FormulaLanguage::ODF_11 : + return InitSymbolsPODF( InitSymbols::ASK); + case FormulaLanguage::ENGLISH : + return InitSymbolsEnglish( InitSymbols::ASK); + case FormulaLanguage::NATIVE : + return InitSymbolsNative( InitSymbols::ASK); + case FormulaLanguage::XL_ENGLISH: + return InitSymbolsEnglishXL( InitSymbols::ASK); + case FormulaLanguage::OOXML: + return InitSymbolsOOXML( InitSymbols::ASK); + case FormulaLanguage::API : + return InitSymbolsAPI( InitSymbols::ASK); + default: + ; // nothing + } + return false; +} + +OUString FormulaCompiler::FindAddInFunction( const OUString& /*rUpperName*/, bool /*bLocalFirst*/ ) const +{ + return OUString(); +} + +FormulaCompiler::OpCodeMapPtr FormulaCompiler::CreateOpCodeMap( + const uno::Sequence< + const sheet::FormulaOpCodeMapEntry > & rMapping, + bool bEnglish ) +{ + using sheet::FormulaOpCodeMapEntry; + // Filter / API maps are never Core + NonConstOpCodeMapPtr xMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, false, + FormulaGrammar::mergeToGrammar( FormulaGrammar::setEnglishBit( + FormulaGrammar::GRAM_EXTERNAL, bEnglish), FormulaGrammar::CONV_UNSPECIFIED)); + std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI()); + const CharClass* pCharClass = xCharClass.get(); + for (auto const& rMapEntry : rMapping) + { + OpCode eOp = OpCode(rMapEntry.Token.OpCode); + if (eOp != ocExternal) + xMap->putOpCode( rMapEntry.Name, eOp, pCharClass); + else + { + OUString aExternalName; + if (rMapEntry.Token.Data >>= aExternalName) + xMap->putExternal( rMapEntry.Name, aExternalName); + else + { + SAL_WARN( "formula.core", "FormulaCompiler::CreateOpCodeMap: no Token.Data external name"); + } + } + } + return xMap; +} + +static bool lcl_fillNativeSymbols( FormulaCompiler::NonConstOpCodeMapPtr& xMap, FormulaCompiler::InitSymbols eWhat = FormulaCompiler::InitSymbols::INIT ) +{ + static OpCodeMapData aSymbolMap; + std::unique_lock aGuard(aSymbolMap.maMtx); + + if (eWhat == FormulaCompiler::InitSymbols::ASK) + { + return bool(aSymbolMap.mxSymbolMap); + } + else if (eWhat == FormulaCompiler::InitSymbols::DESTROY) + { + aSymbolMap.mxSymbolMap.reset(); + } + else if (!aSymbolMap.mxSymbolMap) + { + // Core + aSymbolMap.mxSymbolMap = + std::make_shared<FormulaCompiler::OpCodeMap>( + SC_OPCODE_LAST_OPCODE_ID + 1, true, FormulaGrammar::GRAM_NATIVE_UI); + OpCodeList aOpCodeListSymbols(RID_STRLIST_FUNCTION_NAMES_SYMBOLS, aSymbolMap.mxSymbolMap); + OpCodeList aOpCodeListNative(RID_STRLIST_FUNCTION_NAMES, aSymbolMap.mxSymbolMap); + // No AddInMap for native core mapping. + } + + xMap = aSymbolMap.mxSymbolMap; + + return true; +} + +const OUString& FormulaCompiler::GetNativeSymbol( OpCode eOp ) +{ + NonConstOpCodeMapPtr xSymbolsNative; + lcl_fillNativeSymbols( xSymbolsNative); + return xSymbolsNative->getSymbol( eOp ); +} + +sal_Unicode FormulaCompiler::GetNativeSymbolChar( OpCode eOp ) +{ + return GetNativeSymbol(eOp)[0]; +} + +bool FormulaCompiler::InitSymbolsNative( FormulaCompiler::InitSymbols eWhat ) const +{ + return lcl_fillNativeSymbols( mxSymbolsNative, eWhat); +} + +bool FormulaCompiler::InitSymbolsEnglish( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap); + mxSymbolsEnglish = aMap.mxSymbolMap; + return true; +} + +bool FormulaCompiler::InitSymbolsPODF( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF, FormulaGrammar::GRAM_PODF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE); + mxSymbolsPODF = aMap.mxSymbolMap; + return true; +} + +bool FormulaCompiler::InitSymbolsAPI( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_API, FormulaGrammar::GRAM_API, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE); + mxSymbolsAPI = aMap.mxSymbolMap; + return true; +} + +bool FormulaCompiler::InitSymbolsODFF( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF, FormulaGrammar::GRAM_ODFF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE); + mxSymbolsODFF = aMap.mxSymbolMap; + return true; +} + +bool FormulaCompiler::InitSymbolsEnglishXL( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap); + mxSymbolsEnglishXL = aMap.mxSymbolMap; + if (eWhat != InitSymbols::INIT) + return true; + + // TODO: For now, just replace the separators to the Excel English + // variants. Later, if we want to properly map Excel functions with Calc + // functions, we'll need to do a little more work here. + mxSymbolsEnglishXL->putOpCode( OUString(','), ocSep, nullptr); + mxSymbolsEnglishXL->putOpCode( OUString(','), ocArrayColSep, nullptr); + mxSymbolsEnglishXL->putOpCode( OUString(';'), ocArrayRowSep, nullptr); + + return true; +} + +bool FormulaCompiler::InitSymbolsOOXML( FormulaCompiler::InitSymbols eWhat ) const +{ + static OpCodeMapData aMap; + std::unique_lock aGuard(aMap.maMtx); + if (eWhat == InitSymbols::ASK) + return bool(aMap.mxSymbolMap); + else if (eWhat == InitSymbols::DESTROY) + aMap.mxSymbolMap.reset(); + else if (!aMap.mxSymbolMap) + loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML, FormulaGrammar::GRAM_OOXML, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE); + mxSymbolsOOXML = aMap.mxSymbolMap; + return true; +} + + +void FormulaCompiler::loadSymbols(const std::pair<const char*, int>* pSymbols, FormulaGrammar::Grammar eGrammar, + NonConstOpCodeMapPtr& rxMap, SeparatorType eSepType) const +{ + if ( rxMap ) + return; + + // not Core + rxMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, eGrammar != FormulaGrammar::GRAM_ODFF, eGrammar ); + OpCodeList aOpCodeList(pSymbols, rxMap, eSepType); + + fillFromAddInMap( rxMap, eGrammar); + // Fill from collection for AddIns not already present. + if (FormulaGrammar::GRAM_ENGLISH == eGrammar) + fillFromAddInCollectionEnglishName( rxMap); + else + { + fillFromAddInCollectionUpperName( rxMap); + if (FormulaGrammar::GRAM_API == eGrammar) + { + // Add known but not in AddInMap English names, e.g. from the + // PricingFunctions AddIn or any user supplied AddIn. + fillFromAddInCollectionEnglishName( rxMap); + } + } +} + +void FormulaCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& /*xMap */) const +{ +} + +void FormulaCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& /*xMap */) const +{ +} + +void FormulaCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& /*xMap*/, FormulaGrammar::Grammar /*_eGrammar */) const +{ +} + +OpCode FormulaCompiler::GetEnglishOpCode( const OUString& rName ) const +{ + FormulaCompiler::OpCodeMapPtr xMap = GetOpCodeMap( sheet::FormulaLanguage::ENGLISH); + + formula::OpCodeHashMap::const_iterator iLook( xMap->getHashMap().find( rName ) ); + bool bFound = (iLook != xMap->getHashMap().end()); + return bFound ? (*iLook).second : ocNone; +} + +bool FormulaCompiler::IsOpCodeVolatile( OpCode eOp ) +{ + bool bRet = false; + switch (eOp) + { + // no parameters: + case ocRandom: + case ocGetActDate: + case ocGetActTime: + // one parameter: + case ocFormula: + case ocInfo: + // more than one parameters: + // ocIndirect otherwise would have to do + // StopListening and StartListening on a reference for every + // interpreted value. + case ocIndirect: + // ocOffset results in indirect references. + case ocOffset: + // ocDebugVar shows internal value that may change as the internal state changes. + case ocDebugVar: + bRet = true; + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp ) +{ + switch (eOp) + { + case ocIf: + case ocIfError: + case ocIfNA: + case ocChoose: + return true; + default: + ; + } + return false; +} + +// Remove quotes, escaped quotes are unescaped. +bool FormulaCompiler::DeQuote( OUString& rStr ) +{ + sal_Int32 nLen = rStr.getLength(); + if ( nLen > 1 && rStr[0] == '\'' && rStr[ nLen-1 ] == '\'' ) + { + rStr = rStr.copy( 1, nLen-2 ); + rStr = rStr.replaceAll( "''", "'" ); + return true; + } + return false; +} + +void FormulaCompiler::fillAddInToken( + ::std::vector< sheet::FormulaOpCodeMapEntry >& /*_rVec*/, + bool /*_bIsEnglish*/) const +{ +} + +bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode ) +{ + switch (eOpCode) + { + case ocDde : + case ocGrowth : + case ocTrend : + case ocLogest : + case ocLinest : + case ocFrequency : + case ocMatTrans : + case ocMatMult : + case ocMatInv : + case ocMatrixUnit : + case ocModalValue_Multi : + case ocFourier : + return true; + default: + { + // added to avoid warnings + } + } + return false; +} + + +void FormulaCompiler::OpCodeMap::putCopyOpCode( const OUString& rSymbol, OpCode eOp ) +{ + SAL_WARN_IF( !mpTable[eOp].isEmpty() && rSymbol.isEmpty(), "formula.core", + "OpCodeMap::putCopyOpCode: NOT replacing OpCode " << static_cast<sal_uInt16>(eOp) + << " '" << mpTable[eOp] << "' with empty name!"); + if (!mpTable[eOp].isEmpty() && rSymbol.isEmpty()) + maHashMap.emplace(mpTable[eOp], eOp); + else + { + mpTable[eOp] = rSymbol; + maHashMap.emplace(rSymbol, eOp); + } +} + +void FormulaCompiler::OpCodeMap::copyFrom( const OpCodeMap& r ) +{ + maHashMap = OpCodeHashMap( mnSymbols); + + sal_uInt16 n = r.getSymbolCount(); + SAL_WARN_IF( n != mnSymbols, "formula.core", + "OpCodeMap::copyFrom: unequal size, this: " << mnSymbols << " that: " << n); + if (n > mnSymbols) + n = mnSymbols; + + // OpCode 0 (ocPush) should never be in a map. + SAL_WARN_IF( !mpTable[0].isEmpty() || !r.mpTable[0].isEmpty(), "formula.core", + "OpCodeMap::copyFrom: OpCode 0 assigned, this: '" + << mpTable[0] << "' that: '" << r.mpTable[0] << "'"); + + // For bOverrideKnownBad when copying from the English core map (ODF 1.1 + // and API) to the native map (UI "use English function names") replace the + // known bad legacy function names with correct ones. + if (r.mbCore && + FormulaGrammar::extractFormulaLanguage( meGrammar) == sheet::FormulaLanguage::NATIVE && + FormulaGrammar::extractFormulaLanguage( r.meGrammar) == sheet::FormulaLanguage::ENGLISH) + { + for (sal_uInt16 i = 1; i < n; ++i) + { + OUString aSymbol; + OpCode eOp = OpCode(i); + switch (eOp) + { + case ocRRI: + aSymbol = "RRI"; + break; + case ocTableOp: + aSymbol = "MULTIPLE.OPERATIONS"; + break; + default: + aSymbol = r.mpTable[i]; + } + putCopyOpCode( aSymbol, eOp); + } + } + else + { + for (sal_uInt16 i = 1; i < n; ++i) + { + OpCode eOp = OpCode(i); + const OUString& rSymbol = r.mpTable[i]; + putCopyOpCode( rSymbol, eOp); + } + } + + // This was meant to copy to native map that does not have AddIn symbols + // but needs them from the source map. It is unclear what should happen if + // the destination already had externals, so do it only if it doesn't. + if (!hasExternals()) + { + maExternalHashMap = r.maExternalHashMap; + maReverseExternalHashMap = r.maReverseExternalHashMap; + mbCore = r.mbCore; + if (mbEnglish != r.mbEnglish) + { + // For now keep mbEnglishLocale setting, which is false for a + // non-English native map we're copying to. + /* TODO: + if (!mbEnglish && r.mbEnglish) + mbEnglishLocale = "getUseEnglishLocaleFromConfiguration()"; + or set from outside i.e. via ScCompiler. + */ + mbEnglish = r.mbEnglish; + } + } +} + + +FormulaError FormulaCompiler::GetErrorConstant( const OUString& rName ) const +{ + FormulaError nError = FormulaError::NONE; + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); + if (iLook != mxSymbols->getHashMap().end()) + { + switch ((*iLook).second) + { + // Not all may make sense in a formula, but these we know as + // opcodes. + case ocErrNull: + nError = FormulaError::NoCode; + break; + case ocErrDivZero: + nError = FormulaError::DivisionByZero; + break; + case ocErrValue: + nError = FormulaError::NoValue; + break; + case ocErrRef: + nError = FormulaError::NoRef; + break; + case ocErrName: + nError = FormulaError::NoName; + break; + case ocErrNum: + nError = FormulaError::IllegalFPOperation; + break; + case ocErrNA: + nError = FormulaError::NotAvailable; + break; + default: + ; // nothing + } + } + else + { + // Per convention recognize detailed "#ERRxxx!" constants, always + // untranslated. Error numbers are sal_uInt16 so at most 5 decimal + // digits. + if (rName.startsWithIgnoreAsciiCase("#ERR") && rName.getLength() <= 10 && rName[rName.getLength()-1] == '!') + { + sal_uInt32 nErr = o3tl::toUInt32(rName.subView( 4, rName.getLength() - 5)); + if (0 < nErr && nErr <= SAL_MAX_UINT16 && isPublishedFormulaError(static_cast<FormulaError>(nErr))) + nError = static_cast<FormulaError>(nErr); + } + } + return nError; +} + +void FormulaCompiler::EnableJumpCommandReorder( bool bEnable ) +{ + mbJumpCommandReorder = bEnable; +} + +void FormulaCompiler::EnableStopOnError( bool bEnable ) +{ + mbStopOnError = bEnable; +} + +void FormulaCompiler::AppendErrorConstant( OUStringBuffer& rBuffer, FormulaError nError ) const +{ + OpCode eOp; + switch (nError) + { + case FormulaError::NoCode: + eOp = ocErrNull; + break; + case FormulaError::DivisionByZero: + eOp = ocErrDivZero; + break; + case FormulaError::NoValue: + eOp = ocErrValue; + break; + case FormulaError::NoRef: + eOp = ocErrRef; + break; + case FormulaError::NoName: + eOp = ocErrName; + break; + case FormulaError::IllegalFPOperation: + eOp = ocErrNum; + break; + case FormulaError::NotAvailable: + eOp = ocErrNA; + break; + default: + { + // Per convention create detailed "#ERRxxx!" constants, always + // untranslated. + rBuffer.append("#ERR"); + rBuffer.append(static_cast<sal_Int32>(nError)); + rBuffer.append('!'); + return; + } + } + rBuffer.append( mxSymbols->getSymbol( eOp)); +} + +constexpr short nRecursionMax = 100; + +bool FormulaCompiler::GetToken() +{ + FormulaCompilerRecursionGuard aRecursionGuard( nRecursion ); + if ( nRecursion > nRecursionMax ) + { + SetError( FormulaError::StackOverflow ); + mpLastToken = mpToken = new FormulaByteToken( ocStop ); + return false; + } + if ( bAutoCorrect && !pStack ) + { // don't merge stacked subroutine code into entered formula + aCorrectedFormula += aCorrectedSymbol; + aCorrectedSymbol.clear(); + } + bool bStop = false; + if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError) + bStop = true; + else + { + FormulaTokenRef pSpacesToken; + short nWasColRowName; + if ( pArr->OpCodeBefore( maArrIterator.GetIndex() ) == ocColRowName ) + nWasColRowName = 1; + else + nWasColRowName = 0; + OpCode eTmpOp; + mpToken = maArrIterator.Next(); + while (mpToken && ((eTmpOp = mpToken->GetOpCode()) == ocSpaces || eTmpOp == ocWhitespace)) + { + if (eTmpOp == ocSpaces) + { + // For significant whitespace remember last ocSpaces token. + // Usually there's only one even for multiple spaces. + pSpacesToken = mpToken; + if ( nWasColRowName ) + nWasColRowName++; + } + if ( bAutoCorrect && !pStack ) + CreateStringFromToken( aCorrectedFormula, mpToken.get() ); + mpToken = maArrIterator.Next(); + } + if ( bAutoCorrect && !pStack && mpToken ) + CreateStringFromToken( aCorrectedSymbol, mpToken.get() ); + if( !mpToken ) + { + if( pStack ) + { + PopTokenArray(); + // mpLastToken was popped as well and corresponds to the + // then current last token during PushTokenArray(), e.g. for + // HandleRange(). + return GetToken(); + } + else + bStop = true; + } + else + { + if ( nWasColRowName >= 2 && mpToken->GetOpCode() == ocColRowName ) + { // convert an ocSpaces to ocIntersect in RPN + mpLastToken = mpToken = new FormulaByteToken( ocIntersect ); + maArrIterator.StepBack(); // we advanced to the second ocColRowName, step back + } + else if (pSpacesToken && FormulaGrammar::isExcelSyntax( meGrammar) && + mpLastToken && mpToken && + isPotentialRangeType( mpLastToken.get(), false, false) && + isPotentialRangeType( mpToken.get(), false, true)) + { + // Let IntersectionLine() <- Factor() decide how to treat this, + // once the actual arguments are determined in RPN. + mpLastToken = mpToken = pSpacesToken; + maArrIterator.StepBack(); // step back from next non-spaces token + return true; + } + } + } + if( bStop ) + { + mpLastToken = mpToken = new FormulaByteToken( ocStop ); + return false; + } + + // Remember token for next round and any PushTokenArray() calls that may + // occur in handlers. + mpLastToken = mpToken; + + if ( mpToken->IsExternalRef() ) + { + return HandleExternalReference(*mpToken); + } + else + { + switch (mpToken->GetOpCode()) + { + case ocSubTotal: + case ocAggregate: + glSubTotal = true; + break; + case ocName: + if( HandleRange()) + { + // Expanding ocName might have introduced tokens such as ocStyle that prevent formula threading, + // but those wouldn't be present in the raw tokens array, so ensure RPN tokens will be checked too. + needsRPNTokenCheck = true; + return true; + } + return false; + case ocColRowName: + return HandleColRowName(); + case ocDBArea: + return HandleDbData(); + case ocTableRef: + return HandleTableRef(); + case ocPush: + if( mbComputeII ) + HandleIIOpCode(mpToken.get(), nullptr, 0); + break; + default: + ; // nothing + } + } + return true; +} + + +// RPN creation by recursion +void FormulaCompiler::Factor() +{ + if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError) + return; + + CurrentFactor pFacToken( this ); + + OpCode eOp = mpToken->GetOpCode(); + if (eOp == ocPush || eOp == ocColRowNameAuto || eOp == ocMatRef || eOp == ocDBArea + || eOp == ocTableRef + || (!mbJumpCommandReorder && ((eOp == ocName) || (eOp == ocColRowName) || (eOp == ocBad))) + ) + { + PutCode( mpToken ); + eOp = NextToken(); + if( eOp == ocOpen ) + { + // PUSH( is an error that may be caused by an unknown function. + SetError( + ( mpToken->GetType() == svString + || mpToken->GetType() == svSingleRef ) + ? FormulaError::NoName : FormulaError::OperatorExpected ); + if ( bAutoCorrect && !pStack ) + { // assume multiplication + aCorrectedFormula += mxSymbols->getSymbol( ocMul); + bCorrected = true; + NextToken(); + eOp = Expression(); + if( eOp != ocClose ) + SetError( FormulaError::PairExpected); + else + NextToken(); + } + } + } + else if( eOp == ocOpen ) + { + NextToken(); + eOp = Expression(); + while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)) + { // range list (A1;A2) converted to (A1~A2) + pFacToken = mpToken; + NextToken(); + CheckSetForceArrayParameter( mpToken, 0); + eOp = Expression(); + // Do not ignore error here, regardless of mbStopOnError, to not + // change the formula expression in case of an unexpected state. + if (pArr->GetCodeError() == FormulaError::NONE && pc >= 2) + { + // Left and right operands must be reference or function + // returning reference to form a range list. + const FormulaToken* p = pCode[-2]; + if (p && isPotentialRangeType( p, true, false)) + { + p = pCode[-1]; + if (p && isPotentialRangeType( p, true, true)) + { + pFacToken->NewOpCode( ocUnion, FormulaToken::PrivateAccess()); + // XXX NOTE: the token's eType is still svSep here! + PutCode( pFacToken); + } + } + } + } + if (eOp != ocClose) + SetError( FormulaError::PairExpected); + else + NextToken(); + + /* TODO: if no conversion to ocUnion is involved this could collect + * such expression as a list or (matrix) vector to be passed as + * argument for one parameter (which in fact the ocUnion svRefList is a + * special case of), which would require a new StackVar type and needed + * to be handled by the interpreter for functions that could support it + * (i.e. already handle VAR_ARGS or svRefList parameters). This is also + * not defined by ODF. + * Does Excel handle =SUM((1;2))? + * As is, the interpreter catches extraneous uncalculated + * subexpressions like 1 of (1;2) as error. */ + } + else + { + if( nNumFmt == SvNumFormatType::UNDEFINED ) + nNumFmt = lcl_GetRetFormat( eOp ); + + if ( IsOpCodeVolatile( eOp) ) + pArr->SetExclusiveRecalcModeAlways(); + else + { + switch( eOp ) + { + // Functions recalculated on every document load. + // ONLOAD_LENIENT here to be able to distinguish and not + // force a recalc (if not in an ALWAYS or ONLOAD_MUST + // context) but keep an imported result from for example + // OOXML a DDE call. Will be recalculated for ODFF. + case ocConvertOOo : + case ocDde: + case ocMacro: + case ocWebservice: + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + break; + // RANDBETWEEN() is volatile like RAND(). Other Add-In + // functions may have to be recalculated or not, we don't + // know, classify as ONLOAD_LENIENT. + case ocExternal: + if (mpToken->GetExternal() == "com.sun.star.sheet.addin.Analysis.getRandbetween") + pArr->SetExclusiveRecalcModeAlways(); + else + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + break; + // If the referred cell is moved the value changes. + case ocColumn : + case ocRow : + pArr->SetRecalcModeOnRefMove(); + break; + // ocCell needs recalc on move for some possible type values. + // And recalc mode on load, tdf#60645 + case ocCell : + pArr->SetRecalcModeOnRefMove(); + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST ); + break; + case ocHyperLink : + // Cell with hyperlink needs to be calculated on load to + // get its matrix result generated. + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST ); + pArr->SetHyperLink( true); + break; + default: + ; // nothing + } + } + if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR) + { + pFacToken = mpToken; + eOp = NextToken(); + if (eOp != ocOpen) + { + SetError( FormulaError::PairExpected); + PutCode( pFacToken ); + } + else + { + eOp = NextToken(); + if (eOp != ocClose) + SetError( FormulaError::PairExpected); + PutCode( pFacToken); + NextToken(); + } + } + else if (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR) + { + if (eOp == ocIsoWeeknum && FormulaGrammar::isODFF( meGrammar )) + { + // tdf#50950 ocIsoWeeknum can have 2 arguments when saved by older versions of Calc; + // the opcode then has to be changed to ocWeek for backward compatibility + pFacToken = mpToken; + eOp = NextToken(); + bool bNoParam = false; + if (eOp == ocOpen) + { + eOp = NextToken(); + if (eOp == ocClose) + bNoParam = true; + else + { + CheckSetForceArrayParameter( mpToken, 0); + eOp = Expression(); + } + } + else + SetError( FormulaError::PairExpected); + sal_uInt32 nSepCount = 0; + const sal_uInt16 nSepPos = maArrIterator.GetIndex() - 1; // separator position, if any + if( !bNoParam ) + { + nSepCount++; + while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)) + { + NextToken(); + CheckSetForceArrayParameter( mpToken, nSepCount); + nSepCount++; + if (nSepCount > FORMULA_MAXPARAMS) + SetError( FormulaError::CodeOverflow); + eOp = Expression(); + } + } + if (eOp != ocClose) + SetError( FormulaError::PairExpected); + else + NextToken(); + pFacToken->SetByte( nSepCount ); + if (nSepCount == 2) + { + // An old mode!=1 indicates ISO week, remove argument if + // literal double value and keep function. Anything else + // can not be resolved, there exists no "like ISO but week + // starts on Sunday" mode in WEEKNUM and for an expression + // we can't determine. + // Current index is nSepPos+3 if expression stops, or + // nSepPos+4 if expression continues after the call because + // we just called NextToken() to move away from it. + if (pc >= 2 && (maArrIterator.GetIndex() == nSepPos + 3 || maArrIterator.GetIndex() == nSepPos + 4) && + pArr->TokenAt(nSepPos+1)->GetType() == svDouble && + pArr->TokenAt(nSepPos+1)->GetDouble() != 1.0 && + pArr->TokenAt(nSepPos+2)->GetOpCode() == ocClose && + pArr->RemoveToken( nSepPos, 2) == 2) + { + maArrIterator.AfterRemoveToken( nSepPos, 2); + // Remove the ocPush/svDouble just removed also from + // the compiler local RPN array. + --pCode; --pc; + (*pCode)->DecRef(); // may be dead now + pFacToken->SetByte( nSepCount - 1 ); + } + else + { + // For the remaining two arguments cases use the + // compatibility function. + pFacToken->NewOpCode( ocWeeknumOOo, FormulaToken::PrivateAccess()); + } + } + PutCode( pFacToken ); + } + else + { + // standard handling of 1-parameter opcodes + pFacToken = mpToken; + eOp = NextToken(); + if( nNumFmt == SvNumFormatType::UNDEFINED && eOp == ocNot ) + nNumFmt = SvNumFormatType::LOGICAL; + if (eOp == ocOpen) + { + NextToken(); + CheckSetForceArrayParameter( mpToken, 0); + eOp = Expression(); + } + else + SetError( FormulaError::PairExpected); + if (eOp != ocClose) + SetError( FormulaError::PairExpected); + else if ( pArr->GetCodeError() == FormulaError::NONE ) + { + pFacToken->SetByte( 1 ); + if (mbComputeII) + { + FormulaToken** pArg = pCode - 1; + HandleIIOpCode(pFacToken, &pArg, 1); + } + } + PutCode( pFacToken ); + NextToken(); + } + } + else if ((SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) + || eOp == ocExternal + || eOp == ocMacro + || eOp == ocAnd + || eOp == ocOr + || eOp == ocBad + || ( eOp >= ocInternalBegin && eOp <= ocInternalEnd ) + || (!mbJumpCommandReorder && IsOpCodeJumpCommand(eOp))) + { + pFacToken = mpToken; + OpCode eMyLastOp = eOp; + eOp = NextToken(); + bool bNoParam = false; + bool bBadName = false; + if (eOp == ocOpen) + { + eOp = NextToken(); + if (eOp == ocClose) + bNoParam = true; + else + { + CheckSetForceArrayParameter( mpToken, 0); + eOp = Expression(); + } + } + else if (eMyLastOp == ocBad) + { + // Just a bad name, not an unknown function, no parameters, no + // closing expected. + bBadName = true; + bNoParam = true; + } + else + SetError( FormulaError::PairExpected); + sal_uInt32 nSepCount = 0; + if( !bNoParam ) + { + bool bDoIICompute = mbComputeII; + // Array of FormulaToken double pointers to collect the parameters of II opcodes. + FormulaToken*** pArgArray = nullptr; + if (bDoIICompute) + { + pArgArray = static_cast<FormulaToken***>(alloca(sizeof(FormulaToken**)*FORMULA_MAXPARAMSII)); + if (!pArgArray) + bDoIICompute = false; + } + + nSepCount++; + + if (bDoIICompute) + pArgArray[nSepCount-1] = pCode - 1; // Add first argument + + while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)) + { + NextToken(); + CheckSetForceArrayParameter( mpToken, nSepCount); + nSepCount++; + if (nSepCount > FORMULA_MAXPARAMS) + SetError( FormulaError::CodeOverflow); + eOp = Expression(); + if (bDoIICompute && nSepCount <= FORMULA_MAXPARAMSII) + pArgArray[nSepCount - 1] = pCode - 1; // Add rest of the arguments + } + if (bDoIICompute) + HandleIIOpCode(pFacToken, pArgArray, + std::min(nSepCount, static_cast<sal_uInt32>(FORMULA_MAXPARAMSII))); + } + bool bDone = false; + if (bBadName) + ; // nothing, keep current token for return + else if (eOp != ocClose) + SetError( FormulaError::PairExpected); + else + { + NextToken(); + bDone = true; + } + // Jumps are just normal functions for the FunctionAutoPilot tree view + if (!mbJumpCommandReorder && pFacToken->GetType() == svJump) + pFacToken = new FormulaFAPToken( pFacToken->GetOpCode(), nSepCount, pFacToken ); + else + pFacToken->SetByte( nSepCount ); + PutCode( pFacToken ); + + if (bDone) + AnnotateOperands(); + } + else if (IsOpCodeJumpCommand(eOp)) + { + // the PC counters are -1 + pFacToken = mpToken; + switch (eOp) + { + case ocIf: + pFacToken->GetJump()[ 0 ] = 3; // if, else, behind + break; + case ocChoose: + pFacToken->GetJump()[ 0 ] = FORMULA_MAXJUMPCOUNT + 1; + break; + case ocIfError: + case ocIfNA: + pFacToken->GetJump()[ 0 ] = 2; // if, behind + break; + default: + SAL_WARN("formula.core","Jump OpCode: " << +eOp); + assert(!"FormulaCompiler::Factor: someone forgot to add a jump count case"); + } + eOp = NextToken(); + if (eOp == ocOpen) + { + NextToken(); + CheckSetForceArrayParameter( mpToken, 0); + eOp = Expression(); + } + else + SetError( FormulaError::PairExpected); + PutCode( pFacToken ); + // During AutoCorrect (since pArr->GetCodeError() is + // ignored) an unlimited ocIf would crash because + // ScRawToken::Clone() allocates the JumpBuffer according to + // nJump[0]*2+2, which is 3*2+2 on ocIf and 2*2+2 ocIfError and ocIfNA. + short nJumpMax; + OpCode eFacOpCode = pFacToken->GetOpCode(); + switch (eFacOpCode) + { + case ocIf: + nJumpMax = 3; + break; + case ocChoose: + nJumpMax = FORMULA_MAXJUMPCOUNT; + break; + case ocIfError: + case ocIfNA: + nJumpMax = 2; + break; + case ocStop: + // May happen only if PutCode(pFacToken) ran into overflow. + nJumpMax = 0; + assert(pc == FORMULA_MAXTOKENS && pArr->GetCodeError() != FormulaError::NONE); + break; + default: + nJumpMax = 0; + SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode); + assert(!"FormulaCompiler::Factor: someone forgot to add a jump max case"); + } + short nJumpCount = 0; + while ( (nJumpCount < (FORMULA_MAXJUMPCOUNT - 1)) && (eOp == ocSep) + && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)) + { + if ( ++nJumpCount <= nJumpMax ) + pFacToken->GetJump()[nJumpCount] = pc-1; + NextToken(); + CheckSetForceArrayParameter( mpToken, nJumpCount - 1); + eOp = Expression(); + // ocSep or ocClose terminate the subexpression + PutCode( mpToken ); + } + if (eOp != ocClose) + SetError( FormulaError::PairExpected); + else + { + NextToken(); + // always limit to nJumpMax, no arbitrary overwrites + if ( ++nJumpCount <= nJumpMax ) + pFacToken->GetJump()[ nJumpCount ] = pc-1; + eFacOpCode = pFacToken->GetOpCode(); + bool bLimitOk; + switch (eFacOpCode) + { + case ocIf: + bLimitOk = (nJumpCount <= 3); + break; + case ocChoose: + bLimitOk = (nJumpCount < FORMULA_MAXJUMPCOUNT); + break; + case ocIfError: + case ocIfNA: + bLimitOk = (nJumpCount <= 2); + break; + case ocStop: + // May happen only if PutCode(pFacToken) ran into overflow. + // This may had resulted from a stacked token array and + // error wasn't propagated so assert only the program + // counter. + bLimitOk = false; + assert(pc == FORMULA_MAXTOKENS); + break; + default: + bLimitOk = false; + SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode); + assert(!"FormulaCompiler::Factor: someone forgot to add a jump limit case"); + } + if (bLimitOk) + pFacToken->GetJump()[ 0 ] = nJumpCount; + else + SetError( FormulaError::IllegalParameter); + } + } + else if ( eOp == ocMissing ) + { + PutCode( mpToken ); + NextToken(); + } + else if ( eOp == ocClose ) + { + SetError( FormulaError::ParameterExpected ); + } + else if ( eOp == ocSep ) + { // Subsequent ocSep + SetError( FormulaError::ParameterExpected ); + if ( bAutoCorrect && !pStack ) + { + aCorrectedSymbol.clear(); + bCorrected = true; + } + } + else if ( mpToken->IsExternalRef() ) + { + PutCode( mpToken); + NextToken(); + } + else + { + SetError( FormulaError::UnknownToken ); + if ( bAutoCorrect && !pStack ) + { + if ( eOp == ocStop ) + { // trailing operator w/o operand + sal_Int32 nLen = aCorrectedFormula.getLength(); + if ( nLen ) + aCorrectedFormula = aCorrectedFormula.copy( 0, nLen - 1 ); + aCorrectedSymbol.clear(); + bCorrected = true; + } + } + } + } +} + +void FormulaCompiler::RangeLine() +{ + Factor(); + while (mpToken->GetOpCode() == ocRange) + { + FormulaToken** pCode1 = pCode - 1; + FormulaTokenRef p = mpToken; + NextToken(); + Factor(); + FormulaToken** pCode2 = pCode - 1; + if (!MergeRangeReference( pCode1, pCode2)) + PutCode(p); + } +} + +void FormulaCompiler::IntersectionLine() +{ + RangeLine(); + while (mpToken->GetOpCode() == ocIntersect || mpToken->GetOpCode() == ocSpaces) + { + sal_uInt16 nCodeIndex = maArrIterator.GetIndex() - 1; + FormulaToken** pCode1 = pCode - 1; + FormulaTokenRef p = mpToken; + NextToken(); + RangeLine(); + FormulaToken** pCode2 = pCode - 1; + if (p->GetOpCode() == ocSpaces) + { + // Convert to intersection if both left and right are references or + // functions (potentially returning references, if not then a space + // or no space would be a syntax error anyway), not other operators + // or operands. Else discard. + if (isAdjacentOrGapRpnEnd( pc, pCode, pCode1, pCode2) && isIntersectable( pCode1, pCode2)) + { + FormulaTokenRef pIntersect( new FormulaByteToken( ocIntersect)); + // Replace ocSpaces with ocIntersect so that when switching + // formula syntax the correct operator string is created. + // coverity[freed_arg : FALSE] - FormulaTokenRef has a ref so ReplaceToken won't delete pIntersect + pArr->ReplaceToken( nCodeIndex, pIntersect.get(), FormulaTokenArray::ReplaceMode::CODE_ONLY); + PutCode( pIntersect); + } + } + else + { + PutCode(p); + } + } +} + +void FormulaCompiler::UnionLine() +{ + IntersectionLine(); + while (mpToken->GetOpCode() == ocUnion) + { + FormulaTokenRef p = mpToken; + NextToken(); + IntersectionLine(); + PutCode(p); + } +} + +void FormulaCompiler::UnaryLine() +{ + if( mpToken->GetOpCode() == ocAdd ) + GetToken(); + else if (SC_OPCODE_START_UN_OP <= mpToken->GetOpCode() && + mpToken->GetOpCode() < SC_OPCODE_STOP_UN_OP) + { + FormulaTokenRef p = mpToken; + NextToken(); + UnaryLine(); + if (mbComputeII) + { + FormulaToken** pArg = pCode - 1; + HandleIIOpCode(p.get(), &pArg, 1); + } + PutCode( p ); + } + else + UnionLine(); +} + +void FormulaCompiler::PostOpLine() +{ + UnaryLine(); + while ( mpToken->GetOpCode() == ocPercentSign ) + { // this operator _follows_ its operand + if (mbComputeII) + { + FormulaToken** pArg = pCode - 1; + HandleIIOpCode(mpToken.get(), &pArg, 1); + } + PutCode( mpToken ); + NextToken(); + } +} + +void FormulaCompiler::PowLine() +{ + PostOpLine(); + while (mpToken->GetOpCode() == ocPow) + { + FormulaTokenRef p = mpToken; + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + PostOpLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } +} + +void FormulaCompiler::MulDivLine() +{ + PowLine(); + while (mpToken->GetOpCode() == ocMul || mpToken->GetOpCode() == ocDiv) + { + FormulaTokenRef p = mpToken; + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + PowLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } +} + +void FormulaCompiler::AddSubLine() +{ + MulDivLine(); + while (mpToken->GetOpCode() == ocAdd || mpToken->GetOpCode() == ocSub) + { + FormulaTokenRef p = mpToken; + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + MulDivLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } +} + +void FormulaCompiler::ConcatLine() +{ + AddSubLine(); + while (mpToken->GetOpCode() == ocAmpersand) + { + FormulaTokenRef p = mpToken; + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + AddSubLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } +} + +void FormulaCompiler::CompareLine() +{ + ConcatLine(); + while (mpToken->GetOpCode() >= ocEqual && mpToken->GetOpCode() <= ocGreaterEqual) + { + FormulaTokenRef p = mpToken; + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + ConcatLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } +} + +OpCode FormulaCompiler::Expression() +{ + FormulaCompilerRecursionGuard aRecursionGuard( nRecursion ); + if ( nRecursion > nRecursionMax ) + { + SetError( FormulaError::StackOverflow ); + return ocStop; //! generate token instead? + } + CompareLine(); + while (mpToken->GetOpCode() == ocAnd || mpToken->GetOpCode() == ocOr) + { + FormulaTokenRef p = mpToken; + mpToken->SetByte( 2 ); // 2 parameters! + FormulaToken** pArgArray[2]; + if (mbComputeII) + pArgArray[0] = pCode - 1; // Add first argument + NextToken(); + CompareLine(); + if (mbComputeII) + { + pArgArray[1] = pCode - 1; // Add second argument + HandleIIOpCode(p.get(), pArgArray, 2); + } + PutCode(p); + } + return mpToken->GetOpCode(); +} + + +void FormulaCompiler::SetError( FormulaError /*nError*/ ) +{ +} + +FormulaTokenRef FormulaCompiler::ExtendRangeReference( FormulaToken & /*rTok1*/, FormulaToken & /*rTok2*/ ) +{ + return FormulaTokenRef(); +} + +bool FormulaCompiler::MergeRangeReference( FormulaToken * * const pCode1, FormulaToken * const * const pCode2 ) +{ + if (!isAdjacentRpnEnd( pc, pCode, pCode1, pCode2)) + return false; + + FormulaToken *p1 = *pCode1, *p2 = *pCode2; + FormulaTokenRef p = ExtendRangeReference( *p1, *p2); + if (!p) + return false; + + p->IncRef(); + p1->DecRef(); + p2->DecRef(); + *pCode1 = p.get(); + --pCode; + --pc; + + return true; +} + +bool FormulaCompiler::CompileTokenArray() +{ + glSubTotal = false; + bCorrected = false; + needsRPNTokenCheck = false; + if (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError) + { + if ( bAutoCorrect ) + { + aCorrectedFormula.clear(); + aCorrectedSymbol.clear(); + } + pArr->DelRPN(); + maArrIterator.Reset(); + pStack = nullptr; + FormulaToken* pDataArray[ FORMULA_MAXTOKENS + 1 ]; + // Code in some places refers to the last token as 'pCode - 1', which may + // point before the first element if the expression is bad. So insert a dummy + // node in that place which will make that token be nullptr. + pDataArray[ 0 ] = nullptr; + FormulaToken** pData = pDataArray + 1; + pCode = pData; + bool bWasForced = pArr->IsRecalcModeForced(); + if ( bWasForced && bAutoCorrect ) + aCorrectedFormula = "="; + pArr->ClearRecalcMode(); + maArrIterator.Reset(); + eLastOp = ocOpen; + pc = 0; + NextToken(); + OpCode eOp = Expression(); + // Some trailing garbage that doesn't form an expression? + if (eOp != ocStop) + SetError( FormulaError::OperatorExpected); + PostProcessCode(); + + FormulaError nErrorBeforePop = pArr->GetCodeError(); + + while( pStack ) + PopTokenArray(); + if( pc ) + { + pArr->CreateNewRPNArrayFromData( pData, pc ); + if( needsRPNTokenCheck ) + pArr->CheckAllRPNTokens(); + } + + // once an error, always an error + if( pArr->GetCodeError() == FormulaError::NONE && nErrorBeforePop != FormulaError::NONE ) + pArr->SetCodeError( nErrorBeforePop); + + if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError) + { + pArr->DelRPN(); + maArrIterator.Reset(); + pArr->SetHyperLink( false); + } + + if ( bWasForced ) + pArr->SetRecalcModeForced(); + } + if( nNumFmt == SvNumFormatType::UNDEFINED ) + nNumFmt = SvNumFormatType::NUMBER; + return glSubTotal; +} + +void FormulaCompiler::PopTokenArray() +{ + if( !pStack ) + return; + + FormulaArrayStack* p = pStack; + pStack = p->pNext; + // obtain special RecalcMode from SharedFormula + if ( pArr->IsRecalcModeAlways() ) + p->pArr->SetExclusiveRecalcModeAlways(); + else if ( !pArr->IsRecalcModeNormal() && p->pArr->IsRecalcModeNormal() ) + p->pArr->SetMaskedRecalcMode( pArr->GetRecalcMode() ); + p->pArr->SetCombinedBitsRecalcMode( pArr->GetRecalcMode() ); + if ( pArr->IsHyperLink() ) // fdo 87534 + p->pArr->SetHyperLink( true ); + if( p->bTemp ) + delete pArr; + pArr = p->pArr; + maArrIterator = FormulaTokenArrayPlainIterator(*pArr); + maArrIterator.Jump(p->nIndex); + mpLastToken = p->mpLastToken; + delete p; +} + +void FormulaCompiler::CreateStringFromTokenArray( OUString& rFormula ) +{ + OUStringBuffer aBuffer( pArr->GetLen() * 5 ); + CreateStringFromTokenArray( aBuffer ); + rFormula = aBuffer.makeStringAndClear(); +} + +void FormulaCompiler::CreateStringFromTokenArray( OUStringBuffer& rBuffer ) +{ + rBuffer.setLength(0); + if( !pArr->GetLen() ) + return; + + FormulaTokenArray* pSaveArr = pArr; + int nSaveIndex = maArrIterator.GetIndex(); + bool bODFF = FormulaGrammar::isODFF( meGrammar); + if (bODFF || FormulaGrammar::isPODF( meGrammar) ) + { + // Scan token array for missing args and re-write if present. + MissingConventionODF aConv( bODFF); + if (pArr->NeedsPodfRewrite( aConv)) + { + pArr = pArr->RewriteMissing( aConv ); + maArrIterator = FormulaTokenArrayPlainIterator( *pArr ); + } + } + else if ( FormulaGrammar::isOOXML( meGrammar ) ) + { + // Scan token array for missing args and rewrite if present. + if (pArr->NeedsOoxmlRewrite()) + { + MissingConventionOOXML aConv; + pArr = pArr->RewriteMissing( aConv ); + maArrIterator = FormulaTokenArrayPlainIterator( *pArr ); + } + } + + // At least one character per token, plus some are references, some are + // function names, some are numbers, ... + rBuffer.ensureCapacity( pArr->GetLen() * 5 ); + + if ( pArr->IsRecalcModeForced() ) + rBuffer.append( '='); + const FormulaToken* t = maArrIterator.First(); + while( t ) + t = CreateStringFromToken( rBuffer, t, true ); + + if (pSaveArr != pArr) + { + delete pArr; + pArr = pSaveArr; + maArrIterator = FormulaTokenArrayPlainIterator( *pArr ); + maArrIterator.Jump(nSaveIndex); + } +} + +const FormulaToken* FormulaCompiler::CreateStringFromToken( OUString& rFormula, const FormulaToken* pTokenP ) +{ + OUStringBuffer aBuffer; + const FormulaToken* p = CreateStringFromToken( aBuffer, pTokenP ); + rFormula += aBuffer; + return p; +} + +const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuffer, const FormulaToken* pTokenP, + bool bAllowArrAdvance ) +{ + bool bNext = true; + bool bSpaces = false; + const FormulaToken* t = pTokenP; + OpCode eOp = t->GetOpCode(); + if( eOp >= ocAnd && eOp <= ocOr ) + { + // AND, OR infix? + if ( bAllowArrAdvance ) + t = maArrIterator.Next(); + else + t = maArrIterator.PeekNext(); + bNext = false; + bSpaces = ( !t || t->GetOpCode() != ocOpen ); + } + if( bSpaces ) + rBuffer.append( ' '); + + if (eOp == ocSpaces || eOp == ocWhitespace) + { + bool bWriteSpaces = true; + if (eOp == ocSpaces && mxSymbols->isODFF()) + { + const FormulaToken* p = maArrIterator.PeekPrevNoSpaces(); + bool bIntersectionOp = (p && p->GetOpCode() == ocColRowName); + if (bIntersectionOp) + { + p = maArrIterator.PeekNextNoSpaces(); + bIntersectionOp = (p && p->GetOpCode() == ocColRowName); + } + if (bIntersectionOp) + { + rBuffer.append( "!!"); + bWriteSpaces = false; + } + } + if (bWriteSpaces) + { + // ODF v1.3 OpenFormula 5.14 Whitespace states "whitespace shall + // not separate a function name from its initial opening + // parenthesis". + // + // ECMA-376-1:2016 18.17.2 Syntax states "that no space characters + // shall separate a function-name from the left parenthesis (() + // that follows it." and Excel even chokes on it. + // + // Suppress/remove it in any case also in UI, it will not be + // preserved. + const FormulaToken* p = maArrIterator.PeekPrevNoSpaces(); + if (p && p->IsFunction()) + { + p = maArrIterator.PeekNextNoSpaces(); + if (p && p->GetOpCode() == ocOpen) + bWriteSpaces = false; + } + } + if (bWriteSpaces) + { + // most times it's just one blank + sal_uInt8 n = t->GetByte(); + for ( sal_uInt8 j=0; j<n; ++j ) + { + if (eOp == ocWhitespace) + rBuffer.append( t->GetChar()); + else + rBuffer.append( ' '); + } + } + } + else if( eOp >= ocInternalBegin && eOp <= ocInternalEnd ) + rBuffer.appendAscii( pInternal[ eOp - ocInternalBegin ] ); + else if (eOp == ocIntersect) + { + // Nasty, ugly, horrific, terrifying... + if (FormulaGrammar::isExcelSyntax( meGrammar)) + rBuffer.append(' '); + else + rBuffer.append( mxSymbols->getSymbol( eOp)); + } + else if( static_cast<sal_uInt16>(eOp) < mxSymbols->getSymbolCount()) // Keyword: + rBuffer.append( mxSymbols->getSymbol( eOp)); + else + { + SAL_WARN( "formula.core","unknown OpCode"); + rBuffer.append( GetNativeSymbol( ocErrName )); + } + if( bNext ) + { + if (t->IsExternalRef()) + { + CreateStringFromExternal( rBuffer, pTokenP); + } + else + { + switch( t->GetType() ) + { + case svDouble: + AppendDouble( rBuffer, t->GetDouble() ); + break; + + case svString: + if( eOp == ocBad || eOp == ocStringXML ) + rBuffer.append( t->GetString().getString()); + else + AppendString( rBuffer, t->GetString().getString() ); + break; + case svSingleRef: + CreateStringFromSingleRef( rBuffer, t); + break; + case svDoubleRef: + CreateStringFromDoubleRef( rBuffer, t); + break; + case svMatrix: + case svMatrixCell: + CreateStringFromMatrix( rBuffer, t ); + break; + + case svIndex: + CreateStringFromIndex( rBuffer, t ); + if (t->GetOpCode() == ocTableRef && bAllowArrAdvance && NeedsTableRefTransformation()) + { + // Suppress all TableRef related tokens, the resulting + // range was written by CreateStringFromIndex(). + const FormulaToken* const p = maArrIterator.PeekNext(); + if (p && p->GetOpCode() == ocTableRefOpen) + { + int nLevel = 0; + do + { + t = maArrIterator.Next(); + if (!t) + break; + + // Switch cases correspond with those in + // ScCompiler::HandleTableRef() + switch (t->GetOpCode()) + { + case ocTableRefOpen: + ++nLevel; + break; + case ocTableRefClose: + --nLevel; + break; + case ocTableRefItemAll: + case ocTableRefItemHeaders: + case ocTableRefItemData: + case ocTableRefItemTotals: + case ocTableRefItemThisRow: + case ocSep: + case ocPush: + case ocRange: + case ocSpaces: + case ocWhitespace: + break; + default: + nLevel = 0; + bNext = false; + } + } while (nLevel); + } + } + break; + case svExternal: + { + // mapped or translated name of AddIns + OUString aAddIn( t->GetExternal() ); + bool bMapped = mxSymbols->isPODF(); // ODF 1.1 directly uses programmatical name + if (!bMapped && mxSymbols->hasExternals()) + { + ExternalHashMap::const_iterator iLook = mxSymbols->getReverseExternalHashMap().find( aAddIn); + if (iLook != mxSymbols->getReverseExternalHashMap().end()) + { + aAddIn = (*iLook).second; + bMapped = true; + } + } + if (!bMapped && !mxSymbols->isEnglish()) + LocalizeString( aAddIn ); + rBuffer.append( aAddIn); + } + break; + case svError: + AppendErrorConstant( rBuffer, t->GetError()); + break; + case svByte: + case svJump: + case svFAP: + case svMissing: + case svSep: + break; // Opcodes + default: + SAL_WARN("formula.core", "FormulaCompiler::GetStringFromToken: unknown token type " << t->GetType()); + } // of switch + } + } + if( bSpaces ) + rBuffer.append( ' '); + if ( bAllowArrAdvance ) + { + if( bNext ) + t = maArrIterator.Next(); + return t; + } + return pTokenP; +} + + +void FormulaCompiler::AppendDouble( OUStringBuffer& rBuffer, double fVal ) const +{ + if ( mxSymbols->isEnglishLocale() ) + { + ::rtl::math::doubleToUStringBuffer( rBuffer, fVal, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', true ); + } + else + { + SvtSysLocale aSysLocale; + ::rtl::math::doubleToUStringBuffer( rBuffer, fVal, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, + aSysLocale.GetLocaleData().getNumDecimalSep()[0], + true ); + } +} + +void FormulaCompiler::AppendBoolean( OUStringBuffer& rBuffer, bool bVal ) const +{ + rBuffer.append( mxSymbols->getSymbol( bVal ? ocTrue : ocFalse ) ); +} + +void FormulaCompiler::AppendString( OUStringBuffer& rBuffer, const OUString & rStr ) +{ + rBuffer.append( '"'); + if ( lcl_UnicodeStrChr( rStr.getStr(), '"' ) == nullptr ) + rBuffer.append( rStr ); + else + { + OUString aStr = rStr.replaceAll( "\"", "\"\"" ); + rBuffer.append(aStr); + } + rBuffer.append( '"'); +} + +bool FormulaCompiler::NeedsTableRefTransformation() const +{ + // Currently only UI representations and OOXML export use Table structured + // references. Not defined in ODFF. + // Unnecessary to explicitly check for ODFF grammar as the ocTableRefOpen + // symbol is not defined there. + return mxSymbols->getSymbol( ocTableRefOpen).isEmpty() || FormulaGrammar::isPODF( meGrammar); +} + +void FormulaCompiler::UpdateSeparatorsNative( + const OUString& rSep, const OUString& rArrayColSep, const OUString& rArrayRowSep ) +{ + NonConstOpCodeMapPtr xSymbolsNative; + lcl_fillNativeSymbols( xSymbolsNative); + xSymbolsNative->putOpCode( rSep, ocSep, nullptr); + xSymbolsNative->putOpCode( rArrayColSep, ocArrayColSep, nullptr); + xSymbolsNative->putOpCode( rArrayRowSep, ocArrayRowSep, nullptr); +} + +void FormulaCompiler::ResetNativeSymbols() +{ + NonConstOpCodeMapPtr xSymbolsNative; + lcl_fillNativeSymbols( xSymbolsNative, InitSymbols::DESTROY); + lcl_fillNativeSymbols( xSymbolsNative); +} + +void FormulaCompiler::SetNativeSymbols( const OpCodeMapPtr& xMap ) +{ + NonConstOpCodeMapPtr xSymbolsNative; + lcl_fillNativeSymbols( xSymbolsNative); + xSymbolsNative->copyFrom( *xMap ); +} + + +OpCode FormulaCompiler::NextToken() +{ + if( !GetToken() ) + return ocStop; + OpCode eOp = mpToken->GetOpCode(); + // There must be an operator before a push + if ( (eOp == ocPush || eOp == ocColRowNameAuto) && + !( (eLastOp == ocOpen) || (eLastOp == ocSep) || + (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)) ) + SetError( FormulaError::OperatorExpected); + // Operator and Plus => operator + if (eOp == ocAdd && (eLastOp == ocOpen || eLastOp == ocSep || + (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP))) + { + FormulaCompilerRecursionGuard aRecursionGuard( nRecursion ); + eOp = NextToken(); + } + else + { + // Before an operator there must not be another operator, with the + // exception of AND and OR. + if ( eOp != ocAnd && eOp != ocOr && + (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP ) + && (eLastOp == ocOpen || eLastOp == ocSep || + (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP))) + { + SetError( FormulaError::VariableExpected); + if ( bAutoCorrect && !pStack ) + { + if ( eOp == eLastOp || eLastOp == ocOpen ) + { // throw away duplicated operator + aCorrectedSymbol.clear(); + bCorrected = true; + } + else + { + sal_Int32 nPos = aCorrectedFormula.getLength(); + if ( nPos ) + { + nPos--; + sal_Unicode c = aCorrectedFormula[ nPos ]; + switch ( eOp ) + { // swap operators + case ocGreater: + if ( c == mxSymbols->getSymbolChar( ocEqual) ) + { // >= instead of => + aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1, + rtl::OUStringChar( mxSymbols->getSymbolChar(ocGreater) ) ); + aCorrectedSymbol = OUString(c); + bCorrected = true; + } + break; + case ocLess: + if ( c == mxSymbols->getSymbolChar( ocEqual) ) + { // <= instead of =< + aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1, + rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) ); + aCorrectedSymbol = OUString(c); + bCorrected = true; + } + else if ( c == mxSymbols->getSymbolChar( ocGreater) ) + { // <> instead of >< + aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1, + rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) ); + aCorrectedSymbol = OUString(c); + bCorrected = true; + } + break; + case ocMul: + if ( c == mxSymbols->getSymbolChar( ocSub) ) + { // *- instead of -* + aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1, + rtl::OUStringChar( mxSymbols->getSymbolChar(ocMul) ) ); + aCorrectedSymbol = OUString(c); + bCorrected = true; + } + break; + case ocDiv: + if ( c == mxSymbols->getSymbolChar( ocSub) ) + { // /- instead of -/ + aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1, + rtl::OUStringChar( mxSymbols->getSymbolChar(ocDiv) ) ); + aCorrectedSymbol = OUString(c); + bCorrected = true; + } + break; + default: + ; // nothing + } + } + } + } + } + // Nasty, ugly, horrific, terrifying... significant whitespace... + if (eOp == ocSpaces && FormulaGrammar::isExcelSyntax( meGrammar)) + { + // Fake an intersection op as last op for the next round, but at + // least roughly check if it could make sense at all. + FormulaToken* pPrev = maArrIterator.PeekPrevNoSpaces(); + if (pPrev && isPotentialRangeType( pPrev, false, false)) + { + FormulaToken* pNext = maArrIterator.PeekNextNoSpaces(); + if (pNext && isPotentialRangeType( pNext, false, true)) + eLastOp = ocIntersect; + else + eLastOp = eOp; + } + else + eLastOp = eOp; + } + else + eLastOp = eOp; + } + return eOp; +} + +void FormulaCompiler::PutCode( FormulaTokenRef& p ) +{ + if( pc >= FORMULA_MAXTOKENS - 1 ) + { + if ( pc == FORMULA_MAXTOKENS - 1 ) + { + SAL_WARN("formula.core", "FormulaCompiler::PutCode - CodeOverflow with OpCode " << +p->GetOpCode()); + p = new FormulaByteToken( ocStop ); + p->IncRef(); + *pCode++ = p.get(); + ++pc; + } + SetError( FormulaError::CodeOverflow); + return; + } + if (pArr->GetCodeError() != FormulaError::NONE && mbJumpCommandReorder) + return; + ForceArrayOperator( p); + p->IncRef(); + *pCode++ = p.get(); + pc++; +} + + +bool FormulaCompiler::HandleExternalReference( const FormulaToken& /*_aToken*/) +{ + return true; +} + +bool FormulaCompiler::HandleRange() +{ + return true; +} + +bool FormulaCompiler::HandleColRowName() +{ + return true; +} + +bool FormulaCompiler::HandleDbData() +{ + return true; +} + +bool FormulaCompiler::HandleTableRef() +{ + return true; +} + +void FormulaCompiler::CreateStringFromSingleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const +{ +} + +void FormulaCompiler::CreateStringFromDoubleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const +{ +} + +void FormulaCompiler::CreateStringFromIndex( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const +{ +} + +void FormulaCompiler::CreateStringFromMatrix( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const +{ +} + +void FormulaCompiler::CreateStringFromExternal( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const +{ +} + +void FormulaCompiler::LocalizeString( OUString& /*rName*/ ) const +{ +} + +formula::ParamClass FormulaCompiler::GetForceArrayParameter( const FormulaToken* /*pToken*/, sal_uInt16 /*nParam*/ ) const +{ + return ParamClass::Unknown; +} + +void FormulaCompiler::ForceArrayOperator( FormulaTokenRef const & rCurr ) +{ + if (pCurrentFactorToken.get() == rCurr.get()) + return; + + const OpCode eOp = rCurr->GetOpCode(); + const StackVar eType = rCurr->GetType(); + const bool bInlineArray = (eOp == ocPush && eType == svMatrix); + + if (!bInlineArray) + { + if (rCurr->GetInForceArray() != ParamClass::Unknown) + // Already set, unnecessary to evaluate again. This happens by calls to + // CurrentFactor::operator=() while descending through Factor() and + // then ascending back (and down and up, ...), + // CheckSetForceArrayParameter() and later PutCode(). + return; + + if (!(eOp != ocPush && (eType == svByte || eType == svJump))) + return; + } + + // Return class for inline arrays and functions returning array/matrix. + // It's somewhat unclear what Excel actually does there and in + // ECMA-376-1:2016 OOXML mentions "call to ... shall be an array formula" + // only for FREQUENCY() and TRANSPOSE() but not for any other function + // returning array/matrix or inline arrays, though for the latter has one + // example in 18.17.2 Syntax: + // "SUM(SQRT({1,2,3,4})) returns 6.14 when entered normally". However, + // these need to be treated similar but not as ParamClass::ForceArray + // (which would contradict the example in + // https://bugs.documentfoundation.org/show_bug.cgi?id=122301#c19 and A6 of + // https://bugs.documentfoundation.org/show_bug.cgi?id=133260#c10 ). + // See also + // commit d0ded163d8e93dc5b10d7a7c9bdab1d0a6a50bac + // commit 5413c8871dec08eff19f514f5f391b946a45c86c + constexpr ParamClass eArrayReturn = ParamClass::ForceArrayReturn; + + if (bInlineArray) + { + // rCurr->SetInForceArray() can not be used with ocPush, but ocPush + // with svMatrix has an implicit ParamClass::ForceArrayReturn. + if (nCurrentFactorParam > 0 && pCurrentFactorToken + && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown + && GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1)) + == ParamClass::Value) + { + // Propagate to caller as if a function returning an array/matrix + // was called (see also below). + pCurrentFactorToken->SetInForceArray( eArrayReturn); + } + return; + } + + if (!pCurrentFactorToken) + { + if (mbMatrixFlag) + { + // An array/matrix formula acts as ForceArray on all top level + // operators and function calls, so that can be inherited properly + // below. + rCurr->SetInForceArray( ParamClass::ForceArray); + } + else if (pc >= 2 && SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP) + { + // Binary operators are not functions followed by arguments + // and need some peeking into RPN to inspect their operands. + // Note that array context is not forced if only one + // of the operands is an array like "={1;2}+A1:A2" returns #VALUE! + // if entered in column A and not input in array mode, because it + // involves a range reference with an implicit intersection. Check + // both arguments are arrays, or the other is ocPush without ranges + // for "={1;2}+3" or "={1;2}+A1". + // Note this does not catch "={1;2}+ABS(A1)" that could be forced + // to array, user still has to close in array mode. + // The IsMatrixFunction() is only necessary because not all + // functions returning matrix have ForceArrayReturn (yet?), see + // OOXML comment above. + + const OpCode eOp1 = pCode[-1]->GetOpCode(); + const OpCode eOp2 = pCode[-2]->GetOpCode(); + const bool b1 = (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp1)); + const bool b2 = (pCode[-2]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp2)); + if ((b1 && b2) + || (b1 && eOp2 == ocPush && pCode[-2]->GetType() != svDoubleRef) + || (b2 && eOp1 == ocPush && pCode[-1]->GetType() != svDoubleRef)) + { + rCurr->SetInForceArray( eArrayReturn); + } + } + else if (pc >= 1 && SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP) + { + // Similar for unary operators. + if (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(pCode[-1]->GetOpCode())) + { + rCurr->SetInForceArray( eArrayReturn); + } + } + return; + } + + // Inherited parameter class. + const formula::ParamClass eForceType = pCurrentFactorToken->GetInForceArray(); + if (eForceType == ParamClass::ForceArray || eForceType == ParamClass::ReferenceOrRefArray) + { + // ReferenceOrRefArray was set only if in ForceArray context already, + // it is valid for the one function only to indicate the preferred + // return type. Propagate as ForceArray if not another parameter + // handling ReferenceOrRefArray. + if (nCurrentFactorParam > 0 + && (GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1)) + == ParamClass::ReferenceOrRefArray)) + rCurr->SetInForceArray( ParamClass::ReferenceOrRefArray); + else + rCurr->SetInForceArray( ParamClass::ForceArray); + return; + } + else if (eForceType == ParamClass::ReferenceOrForceArray) + { + // Inherit further only if the return class of the nested function is + // not Reference. Else flag as suppressed. + if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference) + rCurr->SetInForceArray( eForceType); + else + rCurr->SetInForceArray( ParamClass::SuppressedReferenceOrForceArray); + return; + } + + if (nCurrentFactorParam <= 0) + return; + + // Actual current parameter's class. + const formula::ParamClass eParamType = GetForceArrayParameter( + pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1)); + if (eParamType == ParamClass::ForceArray) + rCurr->SetInForceArray( eParamType); + else if (eParamType == ParamClass::ReferenceOrForceArray) + { + if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference) + rCurr->SetInForceArray( eParamType); + else + rCurr->SetInForceArray( formula::ParamClass::SuppressedReferenceOrForceArray); + } + + // Propagate a ForceArrayReturn to caller if the called function + // returns one and the caller so far does not have a stronger array + // mode set and expects a scalar value for this parameter. + if (eParamType == ParamClass::Value && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown) + { + if (IsMatrixFunction( eOp)) + pCurrentFactorToken->SetInForceArray( eArrayReturn); + else if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) == ParamClass::ForceArrayReturn) + pCurrentFactorToken->SetInForceArray( ParamClass::ForceArrayReturn); + } +} + +void FormulaCompiler::CheckSetForceArrayParameter( FormulaTokenRef const & rCurr, sal_uInt8 nParam ) +{ + if (!pCurrentFactorToken) + return; + + nCurrentFactorParam = nParam + 1; + + ForceArrayOperator( rCurr); +} + +void FormulaCompiler::PushTokenArray( FormulaTokenArray* pa, bool bTemp ) +{ + if ( bAutoCorrect && !pStack ) + { // don't merge stacked subroutine code into entered formula + aCorrectedFormula += aCorrectedSymbol; + aCorrectedSymbol.clear(); + } + FormulaArrayStack* p = new FormulaArrayStack; + p->pNext = pStack; + p->pArr = pArr; + p->nIndex = maArrIterator.GetIndex(); + p->mpLastToken = mpLastToken; + p->bTemp = bTemp; + pStack = p; + pArr = pa; + maArrIterator = FormulaTokenArrayPlainIterator( *pArr ); +} + +} // namespace formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/core/api/FormulaOpCodeMapperObj.cxx b/formula/source/core/api/FormulaOpCodeMapperObj.cxx new file mode 100644 index 0000000000..a3ffe56cc8 --- /dev/null +++ b/formula/source/core/api/FormulaOpCodeMapperObj.cxx @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <sal/config.h> + +#include <utility> + +#include <formula/FormulaCompiler.hxx> +#include <formula/FormulaOpCodeMapperObj.hxx> +#include <formula/opcode.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +namespace formula +{ + using namespace ::com::sun::star; + +sal_Bool SAL_CALL FormulaOpCodeMapperObj::supportsService( const OUString& _rServiceName ) +{ + return cppu::supportsService(this, _rServiceName); +} + +FormulaOpCodeMapperObj::FormulaOpCodeMapperObj(::std::unique_ptr<FormulaCompiler> && _pCompiler) +: m_pCompiler(std::move(_pCompiler)) +{ +} + +FormulaOpCodeMapperObj::~FormulaOpCodeMapperObj() +{ +} + +::sal_Int32 SAL_CALL FormulaOpCodeMapperObj::getOpCodeExternal() +{ + return ocExternal; +} + + +::sal_Int32 SAL_CALL FormulaOpCodeMapperObj::getOpCodeUnknown() +{ + return FormulaCompiler::OpCodeMap::getOpCodeUnknown(); +} + + +css::uno::Sequence< css::sheet::FormulaToken > +SAL_CALL FormulaOpCodeMapperObj::getMappings( + const css::uno::Sequence< OUString >& rNames, + sal_Int32 nLanguage ) +{ + FormulaCompiler::OpCodeMapPtr xMap = m_pCompiler->GetOpCodeMap( nLanguage); + if (!xMap) + throw lang::IllegalArgumentException(); + return xMap->createSequenceOfFormulaTokens( *m_pCompiler,rNames); +} + + +css::uno::Sequence< css::sheet::FormulaOpCodeMapEntry > +SAL_CALL FormulaOpCodeMapperObj::getAvailableMappings( + sal_Int32 nLanguage, sal_Int32 nGroups ) +{ + FormulaCompiler::OpCodeMapPtr xMap = m_pCompiler->GetOpCodeMap( nLanguage); + if (!xMap) + throw lang::IllegalArgumentException(); + return xMap->createSequenceOfAvailableMappings( *m_pCompiler,nGroups); +} + +OUString SAL_CALL FormulaOpCodeMapperObj::getImplementationName( ) +{ + return "simple.formula.FormulaOpCodeMapperObj"; +} + +uno::Sequence< OUString > SAL_CALL FormulaOpCodeMapperObj::getSupportedServiceNames( ) +{ + return { "com.sun.star.sheet.FormulaOpCodeMapper" }; +} + +} // formula + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +simple_formula_FormulaOpCodeMapperObj( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire( + new formula::FormulaOpCodeMapperObj(std::make_unique<formula::FormulaCompiler>())); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/core/api/grammar.cxx b/formula/source/core/api/grammar.cxx new file mode 100644 index 0000000000..3974ee28d3 --- /dev/null +++ b/formula/source/core/api/grammar.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <formula/grammar.hxx> +#include <cassert> + +namespace formula { + +FormulaGrammar::Grammar FormulaGrammar::mapAPItoGrammar( const bool bEnglish, const bool bXML ) +{ + Grammar eGrammar; + if (bEnglish && bXML) + eGrammar = GRAM_PODF; + else if (bEnglish && !bXML) + eGrammar = GRAM_API; + else if (!bEnglish && bXML) + eGrammar = GRAM_NATIVE_ODF; + else // (!bEnglish && !bXML) + eGrammar = GRAM_NATIVE; + return eGrammar; +} + +bool FormulaGrammar::isSupported( const Grammar eGrammar ) +{ + switch (eGrammar) + { + case GRAM_ODFF : + case GRAM_PODF : + case GRAM_ENGLISH : + case GRAM_NATIVE : + case GRAM_ODFF_UI : + case GRAM_ODFF_A1 : + case GRAM_PODF_UI : + case GRAM_PODF_A1 : + case GRAM_NATIVE_UI : + case GRAM_NATIVE_ODF : + case GRAM_NATIVE_XL_A1 : + case GRAM_NATIVE_XL_R1C1 : + case GRAM_ENGLISH_XL_A1 : + case GRAM_ENGLISH_XL_R1C1: + case GRAM_ENGLISH_XL_OOX : + case GRAM_OOXML : + case GRAM_API : + return true; + default: + return extractFormulaLanguage( eGrammar) == GRAM_EXTERNAL; + } +} + +FormulaGrammar::Grammar FormulaGrammar::setEnglishBit( const Grammar eGrammar, const bool bEnglish ) +{ + if (bEnglish) + return static_cast<Grammar>( eGrammar | kEnglishBit); + else + return static_cast<Grammar>( eGrammar & ~kEnglishBit); +} + +FormulaGrammar::Grammar FormulaGrammar::mergeToGrammar( const Grammar eGrammar, const AddressConvention eConv ) +{ + bool bEnglish = isEnglish( eGrammar); + Grammar eGram = static_cast<Grammar>( + extractFormulaLanguage( eGrammar) | + ((eConv + kConventionOffset) << kConventionShift)); + eGram = setEnglishBit( eGram, bEnglish); + assert( isSupported( eGram)); + return eGram; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/core/api/token.cxx b/formula/source/core/api/token.cxx new file mode 100644 index 0000000000..74a212937b --- /dev/null +++ b/formula/source/core/api/token.cxx @@ -0,0 +1,2033 @@ +/* -*- 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 <algorithm> + +#include <string.h> +#include <limits.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <com/sun/star/sheet/FormulaToken.hpp> +#include <formula/errorcodes.hxx> +#include <formula/token.hxx> +#include <formula/tokenarray.hxx> +#include <formula/FormulaCompiler.hxx> +#include <formula/compiler.hxx> +#include <svl/sharedstringpool.hxx> +#include <memory> +#include <utility> + +namespace formula +{ + using namespace com::sun::star; + + +// --- helpers -------------------------------------------------------------- + +static bool lcl_IsReference( OpCode eOp, StackVar eType ) +{ + return + (eOp == ocPush && (eType == svSingleRef || eType == svDoubleRef)) + || (eOp == ocColRowNameAuto && eType == svDoubleRef) + || (eOp == ocColRowName && eType == svSingleRef) + || (eOp == ocMatRef && eType == svSingleRef) + ; +} + +// --- class FormulaToken -------------------------------------------------------- + +FormulaToken::FormulaToken( StackVar eTypeP, OpCode e ) : + eOp(e), eType( eTypeP ), mnRefCnt(0) +{ +} + +FormulaToken::FormulaToken( const FormulaToken& r ) : + eOp(r.eOp), eType( r.eType ), mnRefCnt(0) +{ +} + +FormulaToken::~FormulaToken() +{ +} + +bool FormulaToken::IsFunction() const +{ + return (eOp != ocPush && eOp != ocBad && eOp != ocColRowName && + eOp != ocColRowNameAuto && eOp != ocName && eOp != ocDBArea && + eOp != ocTableRef && + (GetByte() != 0 // x parameters + || (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR) // no parameter + || FormulaCompiler::IsOpCodeJumpCommand( eOp ) // @ jump commands + || (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR) // one parameter + || (SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) // x parameters (cByte==0 in + // FuncAutoPilot) + || eOp == ocMacro || eOp == ocExternal // macros, AddIns + || eOp == ocAnd || eOp == ocOr // former binary, now x parameters + || (eOp >= ocInternalBegin && eOp <= ocInternalEnd) // internal + )); +} + + +sal_uInt8 FormulaToken::GetParamCount() const +{ + if ( eOp < SC_OPCODE_STOP_DIV && eOp != ocExternal && eOp != ocMacro && + !FormulaCompiler::IsOpCodeJumpCommand( eOp ) && + eOp != ocPercentSign ) + return 0; // parameters and specials + // ocIf... jump commands not for FAP, have cByte then +//2do: bool parameter whether FAP or not? + else if (GetByte()) + return GetByte(); // all functions, also ocExternal and ocMacro + else if (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP && eOp != ocAnd && eOp != ocOr) + return 2; // binary operators, compiler checked; OR and AND legacy but are functions + else if ((SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP) || eOp == ocPercentSign) + return 1; // unary operators, compiler checked + else if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR) + return 0; // no parameter + else if (FormulaCompiler::IsOpCodeJumpCommand( eOp )) + return 1; // only the condition counts as parameter + else + return 0; // all the rest, no Parameter, or + // if so then it should be in cByte +} + +bool FormulaToken::IsExternalRef() const +{ + bool bRet = false; + switch (eType) + { + case svExternalSingleRef: + case svExternalDoubleRef: + case svExternalName: + bRet = true; + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool FormulaToken::IsRef() const +{ + switch (eType) + { + case svSingleRef: + case svDoubleRef: + case svExternalSingleRef: + case svExternalDoubleRef: + return true; + default: + if (eOp == ocTableRef) + return true; + } + + return false; +} + +bool FormulaToken::IsInForceArray() const +{ + ParamClass eParam = GetInForceArray(); + return eParam == ParamClass::ForceArray || eParam == ParamClass::ReferenceOrForceArray + || eParam == ParamClass::ReferenceOrRefArray || eParam == ParamClass::ForceArrayReturn; +} + +bool FormulaToken::operator==( const FormulaToken& rToken ) const +{ + // don't compare reference count! + return eType == rToken.eType && GetOpCode() == rToken.GetOpCode(); +} + + +// --- virtual dummy methods ------------------------------------------------- + +sal_uInt8 FormulaToken::GetByte() const +{ + // ok to be called for any derived class + return 0; +} + +void FormulaToken::SetByte( sal_uInt8 ) +{ + assert( !"virtual dummy called" ); +} + +ParamClass FormulaToken::GetInForceArray() const +{ + // ok to be called for any derived class + return (eOp == ocPush && eType == svMatrix) ? ParamClass::ForceArrayReturn : ParamClass::Unknown; +} + +void FormulaToken::SetInForceArray( ParamClass ) +{ + assert( !"virtual dummy called" ); +} + +double FormulaToken::GetDouble() const +{ + // This Get is worth an assert. + assert( !"virtual dummy called" ); + return 0.0; +} + +double & FormulaToken::GetDoubleAsReference() +{ + // This Get is worth an assert. + assert( !"virtual dummy called" ); + static double fVal = 0.0; + return fVal; +} + +sal_Int16 FormulaToken::GetDoubleType() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetDoubleType: virtual dummy called" ); + return 0; +} + +void FormulaToken::SetDoubleType( sal_Int16 ) +{ + assert( !"virtual dummy called" ); +} + +const svl::SharedString INVALID_STRING; + +const svl::SharedString & FormulaToken::GetString() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetString: virtual dummy called" ); + return INVALID_STRING; // invalid string +} + +void FormulaToken::SetString( const svl::SharedString& ) +{ + assert( !"virtual dummy called" ); +} + +sal_uInt16 FormulaToken::GetIndex() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetIndex: virtual dummy called" ); + return 0; +} + +void FormulaToken::SetIndex( sal_uInt16 ) +{ + assert( !"virtual dummy called" ); +} + +sal_Int16 FormulaToken::GetSheet() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetSheet: virtual dummy called" ); + return -1; +} + +void FormulaToken::SetSheet( sal_Int16 ) +{ + assert( !"virtual dummy called" ); +} + +sal_Unicode FormulaToken::GetChar() const +{ + // This Get is worth an assert. + assert( !"virtual dummy called" ); + return 0; +} + +short* FormulaToken::GetJump() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetJump: virtual dummy called" ); + return nullptr; +} + + +const OUString& FormulaToken::GetExternal() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetExternal: virtual dummy called" ); + static OUString aDummyString; + return aDummyString; +} + +FormulaToken* FormulaToken::GetFAPOrigToken() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetFAPOrigToken: virtual dummy called" ); + return nullptr; +} + +FormulaError FormulaToken::GetError() const +{ + SAL_WARN( "formula.core", "FormulaToken::GetError: virtual dummy called" ); + return FormulaError::NONE; +} + +void FormulaToken::SetError( FormulaError ) +{ + assert( !"virtual dummy called" ); +} + +const ScSingleRefData* FormulaToken::GetSingleRef() const +{ + OSL_FAIL( "FormulaToken::GetSingleRef: virtual dummy called" ); + return nullptr; +} + +ScSingleRefData* FormulaToken::GetSingleRef() +{ + OSL_FAIL( "FormulaToken::GetSingleRef: virtual dummy called" ); + return nullptr; +} + +const ScComplexRefData* FormulaToken::GetDoubleRef() const +{ + OSL_FAIL( "FormulaToken::GetDoubleRef: virtual dummy called" ); + return nullptr; +} + +ScComplexRefData* FormulaToken::GetDoubleRef() +{ + OSL_FAIL( "FormulaToken::GetDoubleRef: virtual dummy called" ); + return nullptr; +} + +const ScSingleRefData* FormulaToken::GetSingleRef2() const +{ + OSL_FAIL( "FormulaToken::GetSingleRef2: virtual dummy called" ); + return nullptr; +} + +ScSingleRefData* FormulaToken::GetSingleRef2() +{ + OSL_FAIL( "FormulaToken::GetSingleRef2: virtual dummy called" ); + return nullptr; +} + +const ScMatrix* FormulaToken::GetMatrix() const +{ + OSL_FAIL( "FormulaToken::GetMatrix: virtual dummy called" ); + return nullptr; +} + +ScMatrix* FormulaToken::GetMatrix() +{ + OSL_FAIL( "FormulaToken::GetMatrix: virtual dummy called" ); + return nullptr; +} + +ScJumpMatrix* FormulaToken::GetJumpMatrix() const +{ + OSL_FAIL( "FormulaToken::GetJumpMatrix: virtual dummy called" ); + return nullptr; +} +const std::vector<ScComplexRefData>* FormulaToken::GetRefList() const +{ + OSL_FAIL( "FormulaToken::GetRefList: virtual dummy called" ); + return nullptr; +} + +std::vector<ScComplexRefData>* FormulaToken::GetRefList() +{ + OSL_FAIL( "FormulaToken::GetRefList: virtual dummy called" ); + return nullptr; +} + +bool FormulaToken::TextEqual( const FormulaToken& rToken ) const +{ + return *this == rToken; +} + +// real implementations of virtual functions + + +sal_uInt8 FormulaSpaceToken::GetByte() const { return nByte; } +sal_Unicode FormulaSpaceToken::GetChar() const { return cChar; } +bool FormulaSpaceToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && nByte == r.GetByte() && + cChar == r.GetChar(); +} + + +sal_uInt8 FormulaByteToken::GetByte() const { return nByte; } +void FormulaByteToken::SetByte( sal_uInt8 n ) { nByte = n; } +ParamClass FormulaByteToken::GetInForceArray() const { return eInForceArray; } +void FormulaByteToken::SetInForceArray( ParamClass c ) { eInForceArray = c; } +bool FormulaByteToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && nByte == r.GetByte() && + eInForceArray == r.GetInForceArray(); +} + + +FormulaToken* FormulaFAPToken::GetFAPOrigToken() const { return pOrigToken.get(); } +bool FormulaFAPToken::operator==( const FormulaToken& r ) const +{ + return FormulaByteToken::operator==( r ) && pOrigToken == r.GetFAPOrigToken(); +} + + +short* FormulaJumpToken::GetJump() const { return pJump.get(); } +ParamClass FormulaJumpToken::GetInForceArray() const { return eInForceArray; } +void FormulaJumpToken::SetInForceArray( ParamClass c ) { eInForceArray = c; } +bool FormulaJumpToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && pJump[0] == r.GetJump()[0] && + memcmp( pJump.get()+1, r.GetJump()+1, pJump[0] * sizeof(short) ) == 0 && + eInForceArray == r.GetInForceArray(); +} +FormulaJumpToken::~FormulaJumpToken() +{ +} + + +bool FormulaTokenArray::AddFormulaToken( + const sheet::FormulaToken& rToken, svl::SharedStringPool& rSPool, ExternalReferenceHelper* /*pExtRef*/) +{ + bool bError = false; + const OpCode eOpCode = static_cast<OpCode>(rToken.OpCode); //! assuming equal values for the moment + + const uno::TypeClass eClass = rToken.Data.getValueTypeClass(); + switch ( eClass ) + { + case uno::TypeClass_VOID: + // empty data -> use AddOpCode (does some special cases) + AddOpCode( eOpCode ); + break; + case uno::TypeClass_DOUBLE: + // double is only used for "push" + if ( eOpCode == ocPush ) + AddDouble( rToken.Data.get<double>() ); + else + bError = true; + break; + case uno::TypeClass_LONG: + { + // long is svIndex, used for name / database area, or "byte" for spaces + sal_Int32 nValue = rToken.Data.get<sal_Int32>(); + if ( eOpCode == ocDBArea ) + Add( new formula::FormulaIndexToken( eOpCode, static_cast<sal_uInt16>(nValue) ) ); + else if ( eOpCode == ocTableRef ) + bError = true; /* TODO: implementation */ + else if ( eOpCode == ocSpaces ) + Add( new formula::FormulaByteToken( ocSpaces, static_cast<sal_uInt8>(nValue) ) ); + else + bError = true; + } + break; + case uno::TypeClass_STRING: + { + OUString aStrVal( rToken.Data.get<OUString>() ); + if ( eOpCode == ocPush ) + AddString(rSPool.intern(aStrVal)); + else if ( eOpCode == ocBad ) + AddBad( aStrVal ); + else if ( eOpCode == ocStringXML ) + AddStringXML( aStrVal ); + else if ( eOpCode == ocExternal || eOpCode == ocMacro ) + Add( new formula::FormulaExternalToken( eOpCode, aStrVal ) ); + else if ( eOpCode == ocWhitespace ) + { + // Simply ignore empty string. + // Convention is one character repeated. + if (!aStrVal.isEmpty()) + Add( new formula::FormulaSpaceToken( static_cast<sal_uInt8>(aStrVal.getLength()), aStrVal[0])); + } + else + bError = true; // unexpected string: don't know what to do with it + } + break; + default: + bError = true; + } // switch ( eClass ) + return bError; +} + +bool FormulaTokenArray::Fill( + const uno::Sequence<sheet::FormulaToken>& rSequence, + svl::SharedStringPool& rSPool, ExternalReferenceHelper* pExtRef ) +{ + bool bError = false; + const sal_Int32 nCount = rSequence.getLength(); + for (sal_Int32 nPos=0; nPos<nCount; nPos++) + { + bool bOneError = AddFormulaToken(rSequence[nPos], rSPool, pExtRef); + if (bOneError) + { + AddOpCode( ocErrName); // add something that indicates an error + bError = true; + } + } + return bError; +} + +void FormulaTokenArray::DelRPN() +{ + if( nRPN ) + { + FormulaToken** p = pRPN; + for( sal_uInt16 i = 0; i < nRPN; i++ ) + { + (*p++)->DecRef(); + } + delete [] pRPN; + } + pRPN = nullptr; + nRPN = 0; +} + +FormulaToken* FormulaTokenArray::FirstToken() const +{ + if (!pCode || nLen == 0) + return nullptr; + return pCode[0]; +} + +FormulaToken* FormulaTokenArray::PeekPrev( sal_uInt16 & nIdx ) const +{ + if (0 < nIdx && nIdx <= nLen) + return pCode[--nIdx]; + return nullptr; +} + +FormulaToken* FormulaTokenArray::FirstRPNToken() const +{ + if (!pRPN || nRPN == 0) + return nullptr; + return pRPN[0]; +} + +FormulaToken* FormulaTokenArray::LastRPNToken() const +{ + if (!pRPN || nRPN == 0) + return nullptr; + return pRPN[nRPN - 1]; +} + +bool FormulaTokenArray::HasReferences() const +{ + for (auto i: Tokens()) + { + if (i->IsRef()) + return true; + } + + for (auto i: RPNTokens()) + { + if (i->IsRef()) + return true; + } + + return false; +} + +bool FormulaTokenArray::HasExternalRef() const +{ + for (auto i: Tokens()) + { + if (i->IsExternalRef()) + return true; + } + return false; +} + +bool FormulaTokenArray::HasOpCode( OpCode eOp ) const +{ + for (auto i: Tokens()) + { + if (i->GetOpCode() == eOp) + return true; + } + return false; +} + +bool FormulaTokenArray::HasOpCodeRPN( OpCode eOp ) const +{ + for (auto i: RPNTokens()) + { + if (i->GetOpCode() == eOp) + return true; + } + return false; +} + +bool FormulaTokenArray::HasNameOrColRowName() const +{ + for (auto i: Tokens()) + { + if (i->GetType() == svIndex || i->GetOpCode() == ocColRowName ) + return true; + } + return false; +} + +bool FormulaTokenArray::HasOpCodes(const unordered_opcode_set& rOpCodes) const +{ + for (auto i: Tokens()) + { + if (rOpCodes.count(i->GetOpCode()) > 0) + return true; + } + + return false; +} + +FormulaTokenArray::FormulaTokenArray() : + pRPN(nullptr), + nLen(0), + nRPN(0), + nError(FormulaError::NONE), + nMode(ScRecalcMode::NORMAL), + bHyperLink(false), + mbFromRangeName(false), + mbShareable(true), + mbFinalized(false) +{ +} + +FormulaTokenArray::FormulaTokenArray( const FormulaTokenArray& rArr ) +{ + Assign( rArr ); +} + +FormulaTokenArray::FormulaTokenArray( FormulaTokenArray&& rArr ) +{ + Move( std::move(rArr) ); +} + +FormulaTokenArray::~FormulaTokenArray() +{ + FormulaTokenArray::Clear(); +} + +void FormulaTokenArray::Finalize() +{ + if( nLen && !mbFinalized ) + { + // Add() overallocates, so reallocate to the minimum needed size. + std::unique_ptr<FormulaToken*[]> newCode(new FormulaToken*[ nLen ]); + std::copy(&pCode[0], &pCode[nLen], newCode.get()); + pCode = std::move( newCode ); + mbFinalized = true; + } +} + +void FormulaTokenArray::Assign( const FormulaTokenArray& r ) +{ + nLen = r.nLen; + nRPN = r.nRPN; + nError = r.nError; + nMode = r.nMode; + bHyperLink = r.bHyperLink; + mbFromRangeName = r.mbFromRangeName; + mbShareable = r.mbShareable; + mbFinalized = r.mbFinalized; + pCode = nullptr; + pRPN = nullptr; + FormulaToken** pp; + if( nLen ) + { + pCode.reset(new FormulaToken*[ nLen ]); + pp = pCode.get(); + memcpy( pp, r.pCode.get(), nLen * sizeof( FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nLen; i++ ) + (*pp++)->IncRef(); + mbFinalized = true; + } + if( nRPN ) + { + pp = pRPN = new FormulaToken*[ nRPN ]; + memcpy( pp, r.pRPN, nRPN * sizeof( FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nRPN; i++ ) + (*pp++)->IncRef(); + } +} + +void FormulaTokenArray::Move( FormulaTokenArray&& r ) +{ + pCode = std::move(r.pCode); + pRPN = r.pRPN; + r.pRPN = nullptr; + nLen = r.nLen; + r.nLen = 0; + nRPN = r.nRPN; + r.nRPN = 0; + nError = r.nError; + nMode = r.nMode; + bHyperLink = r.bHyperLink; + mbFromRangeName = r.mbFromRangeName; + mbShareable = r.mbShareable; + mbFinalized = r.mbFinalized; +} + +/// Optimisation for efficiently creating StringXML placeholders +void FormulaTokenArray::Assign( sal_uInt16 nCode, FormulaToken **pTokens ) +{ + assert( nLen == 0 ); + assert( pCode == nullptr ); + + nLen = nCode; + pCode.reset(new FormulaToken*[ nLen ]); + mbFinalized = true; + + for( sal_uInt16 i = 0; i < nLen; i++ ) + { + FormulaToken *t = pTokens[ i ]; + assert( t->GetOpCode() == ocStringXML ); + pCode[ i ] = t; + t->IncRef(); + } +} + +FormulaTokenArray& FormulaTokenArray::operator=( const FormulaTokenArray& rArr ) +{ + if(this == &rArr) + return *this; + + Clear(); + Assign( rArr ); + return *this; +} + +FormulaTokenArray& FormulaTokenArray::operator=( FormulaTokenArray&& rArr ) +{ + Clear(); + Move( std::move(rArr) ); + return *this; +} + +void FormulaTokenArray::Clear() +{ + if( nRPN ) DelRPN(); + if( pCode ) + { + FormulaToken** p = pCode.get(); + for( sal_uInt16 i = 0; i < nLen; i++ ) + { + (*p++)->DecRef(); + } + pCode.reset(); + } + pRPN = nullptr; + nError = FormulaError::NONE; + nLen = nRPN = 0; + bHyperLink = false; + mbFromRangeName = false; + mbShareable = true; + mbFinalized = false; + ClearRecalcMode(); +} + +void FormulaTokenArray::CheckToken( const FormulaToken& /*r*/ ) +{ + // Do nothing. +} + +void FormulaTokenArray::CheckAllRPNTokens() +{ + if( nRPN ) + { + FormulaToken** p = pRPN; + for( sal_uInt16 i = 0; i < nRPN; i++ ) + { + CheckToken( *p[ i ] ); + } + } +} + +FormulaToken* FormulaTokenArray::AddToken( const FormulaToken& r ) +{ + return Add( r.Clone() ); +} + +FormulaToken* FormulaTokenArray::MergeArray( ) +{ + return nullptr; +} + +FormulaToken* FormulaTokenArray::ReplaceToken( sal_uInt16 nOffset, FormulaToken* t, + FormulaTokenArray::ReplaceMode eMode ) +{ + if (nOffset < nLen) + { + CheckToken(*t); + t->IncRef(); + FormulaToken* p = pCode[nOffset]; + pCode[nOffset] = t; + if (eMode == CODE_AND_RPN && p->GetRef() > 1) + { + for (sal_uInt16 i=0; i < nRPN; ++i) + { + if (pRPN[i] == p) + { + t->IncRef(); + pRPN[i] = t; + p->DecRef(); + if (p->GetRef() == 1) + break; // for + } + } + } + p->DecRef(); // may be dead now + return t; + } + else + { + t->DeleteIfZeroRef(); + return nullptr; + } +} + +sal_uInt16 FormulaTokenArray::RemoveToken( sal_uInt16 nOffset, sal_uInt16 nCount ) +{ + if (nOffset < nLen) + { + SAL_WARN_IF( nOffset + nCount > nLen, "formula.core", + "FormulaTokenArray::RemoveToken - nOffset " << nOffset << " + nCount " << nCount << " > nLen " << nLen); + const sal_uInt16 nStop = ::std::min( static_cast<sal_uInt16>(nOffset + nCount), nLen); + nCount = nStop - nOffset; + for (sal_uInt16 j = nOffset; j < nStop; ++j) + { + FormulaToken* p = pCode[j]; + if (p->GetRef() > 1) + { + for (sal_uInt16 i=0; i < nRPN; ++i) + { + if (pRPN[i] == p) + { + // Shift remaining tokens in pRPN down. + for (sal_uInt16 x=i+1; x < nRPN; ++x) + { + pRPN[x-1] = pRPN[x]; + } + --nRPN; + + p->DecRef(); + if (p->GetRef() == 1) + break; // for + } + } + } + p->DecRef(); // may be dead now + } + + // Shift remaining tokens in pCode down. + for (sal_uInt16 x = nStop; x < nLen; ++x) + { + pCode[x-nCount] = pCode[x]; + } + nLen -= nCount; + + return nCount; + } + else + { + SAL_WARN("formula.core","FormulaTokenArray::RemoveToken - nOffset " << nOffset << " >= nLen " << nLen); + return 0; + } +} + +FormulaToken* FormulaTokenArray::Add( FormulaToken* t ) +{ + assert(!mbFinalized); + if (mbFinalized) + { + t->DeleteIfZeroRef(); + return nullptr; + } + +// Allocating an array of size FORMULA_MAXTOKENS is simple, but that results in relatively large +// allocations that malloc() implementations usually do not handle as efficiently as smaller +// sizes (not only in terms of memory usage but also speed). Since most token arrays are going +// to be small, start with a small array and resize only if needed. Eventually Finalize() will +// reallocate the memory to size exactly matching the requirements. + const size_t MAX_FAST_TOKENS = 32; + if( !pCode ) + pCode.reset(new FormulaToken*[ MAX_FAST_TOKENS ]); + if( nLen == MAX_FAST_TOKENS ) + { + FormulaToken** tmp = new FormulaToken*[ FORMULA_MAXTOKENS ]; + std::copy(&pCode[0], &pCode[MAX_FAST_TOKENS], tmp); + pCode.reset(tmp); + } + if( nLen < FORMULA_MAXTOKENS - 1 ) + { + CheckToken(*t); + pCode[ nLen++ ] = t; + t->IncRef(); + if( t->GetOpCode() == ocArrayClose ) + return MergeArray(); + return t; + } + else + { + t->DeleteIfZeroRef(); + if ( nLen == FORMULA_MAXTOKENS - 1 ) + { + t = new FormulaByteToken( ocStop ); + pCode[ nLen++ ] = t; + t->IncRef(); + } + return nullptr; + } +} + +FormulaToken* FormulaTokenArray::AddString( const svl::SharedString& rStr ) +{ + return Add( new FormulaStringToken( rStr ) ); +} + +FormulaToken* FormulaTokenArray::AddDouble( double fVal ) +{ + return Add( new FormulaDoubleToken( fVal ) ); +} + +void FormulaTokenArray::AddExternal( const sal_Unicode* pStr ) +{ + AddExternal( OUString( pStr ) ); +} + +FormulaToken* FormulaTokenArray::AddExternal( const OUString& rStr, + OpCode eOp /* = ocExternal */ ) +{ + return Add( new FormulaExternalToken( eOp, rStr ) ); +} + +FormulaToken* FormulaTokenArray::AddBad( const OUString& rStr ) +{ + return Add( new FormulaStringOpToken( ocBad, svl::SharedString( rStr ) ) ); // string not interned +} + +FormulaToken* FormulaTokenArray::AddStringXML( const OUString& rStr ) +{ + return Add( new FormulaStringOpToken( ocStringXML, svl::SharedString( rStr ) ) ); // string not interned +} + + +void FormulaTokenArray::AddRecalcMode( ScRecalcMode nBits ) +{ + const unsigned nExclusive = static_cast<sal_uInt8>(nBits & ScRecalcMode::EMask); + if (nExclusive) + { + unsigned nExBit; + if (nExclusive & (nExclusive - 1)) + { + // More than one bit set, use highest priority. + for (nExBit = 1; (nExBit & static_cast<sal_uInt8>(ScRecalcMode::EMask)) != 0; nExBit <<= 1) + { + if (nExclusive & nExBit) + break; + } + } + else + { + // Only one bit is set. + nExBit = nExclusive; + } + // Set exclusive bit if priority is higher than existing. + if (nExBit < static_cast<sal_uInt8>(nMode & ScRecalcMode::EMask)) + SetMaskedRecalcMode( static_cast<ScRecalcMode>(nExBit)); + } + SetCombinedBitsRecalcMode( nBits ); +} + + +bool FormulaTokenArray::HasMatrixDoubleRefOps() const +{ + if ( !pRPN || !nRPN ) + return false; + + // RPN-Interpreter simulation. + // Simply assumes a double as return value of each function. + std::unique_ptr<FormulaToken*[]> pStack(new FormulaToken* [nRPN]); + FormulaToken* pResult = new FormulaDoubleToken( 0.0 ); + short sp = 0; + for ( auto t: RPNTokens() ) + { + OpCode eOp = t->GetOpCode(); + sal_uInt8 nParams = t->GetParamCount(); + switch ( eOp ) + { + case ocAdd : + case ocSub : + case ocMul : + case ocDiv : + case ocPow : + case ocPower : + case ocAmpersand : + case ocEqual : + case ocNotEqual : + case ocLess : + case ocGreater : + case ocLessEqual : + case ocGreaterEqual : + { + for ( sal_uInt8 k = nParams; k; k-- ) + { + if ( sp >= k && pStack[sp-k]->GetType() == svDoubleRef ) + { + pResult->Delete(); + return true; + } + } + } + break; + default: + { + // added to avoid warnings + } + } + if ( eOp == ocPush || lcl_IsReference( eOp, t->GetType() ) ) + pStack[sp++] = t; + else if (FormulaCompiler::IsOpCodeJumpCommand( eOp )) + { // ignore Jumps, pop previous Result (Condition) + if ( sp ) + --sp; + } + else + { // pop parameters, push result + sp = sal::static_int_cast<short>( sp - nParams ); + if ( sp < 0 ) + { + SAL_WARN("formula.core", "FormulaTokenArray::HasMatrixDoubleRefOps: sp < 0" ); + sp = 0; + } + pStack[sp++] = pResult; + } + } + pResult->Delete(); + + return false; +} + +// --- Formula rewrite of a token array + +inline bool MissingConventionODF::isRewriteNeeded( OpCode eOp ) const +{ + switch (eOp) + { + case ocGammaDist: + case ocPoissonDist: + case ocAddress: + case ocLogInv: + case ocLogNormDist: + case ocNormDist: + return true; + case ocMissing: + case ocLog: + return isPODF(); // rewrite only for PODF + case ocDBCount: + case ocDBCount2: + return isODFF(); // rewrite only for ODFF + default: + return false; + } +} + +/* + fdo 81596 +To be implemented yet: + ocExternal: ? + ocMacro: ? + ocIndex: INDEX() ? +*/ +inline bool MissingConventionOOXML::isRewriteNeeded( OpCode eOp ) +{ + switch (eOp) + { + case ocIf: + + case ocExternal: + case ocEuroConvert: + case ocMacro: + + case ocRound: + case ocRoundUp: + case ocRoundDown: + + case ocIndex: + + case ocCeil: + case ocFloor: + + case ocGammaDist: + case ocFDist_LT: + case ocPoissonDist: + case ocNormDist: + case ocLogInv: + case ocLogNormDist: + case ocHypGeomDist: + + case ocDBCount: + case ocDBCount2: + return true; + + default: + return false; + } +} + +namespace { + +class FormulaMissingContext +{ + public: + const FormulaToken* mpFunc; + int mnCurArg; + + void Clear() { mpFunc = nullptr; mnCurArg = 0; } + inline bool AddDefaultArg( FormulaTokenArray* pNewArr, int nArg, double f ) const; + bool AddMissingExternal( FormulaTokenArray* pNewArr ) const; + bool AddMissing( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const; + void AddMoreArgs( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const; +}; + +} + +void FormulaMissingContext::AddMoreArgs( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const +{ + if ( !mpFunc ) + return; + + switch (rConv.getConvention()) + { + case MissingConvention::FORMULA_MISSING_CONVENTION_ODFF: + case MissingConvention::FORMULA_MISSING_CONVENTION_PODF: + { + switch (mpFunc->GetOpCode()) + { + case ocGammaDist: + if (mnCurArg == 2) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true() + } + break; + case ocPoissonDist: + if (mnCurArg == 1) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 3rd, Cumulative=true() + } + break; + case ocNormDist: + if ( mnCurArg == 2 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true() + } + break; + case ocLogInv: + case ocLogNormDist: + if ( mnCurArg == 0 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 0.0 ); // 2nd, mean = 0.0 + } + if ( mnCurArg <= 1 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 3rd, standard deviation = 1.0 + } + break; + case ocLog: + if ( rConv.isPODF() && mnCurArg == 0 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 10.0 ); // 2nd, basis 10 + } + break; + default: + break; + } + } + break; + case MissingConvention::FORMULA_MISSING_CONVENTION_OOXML: + { + switch (mpFunc->GetOpCode()) + { + case ocIf: + if( mnCurArg == 0 ) + { + // Excel needs at least two parameters in IF function + pNewArr->AddOpCode( ocSep ); + pNewArr->AddOpCode( ocTrue ); // 2nd, true() as function + pNewArr->AddOpCode( ocOpen ); // so the result is of logical type + pNewArr->AddOpCode( ocClose ); // and survives roundtrip + } + break; + + case ocEuroConvert: + if ( mnCurArg == 2 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 0.0 ); // 4th, FullPrecision = false() + } + break; + + case ocPoissonDist: + if (mnCurArg == 1) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 3rd, Cumulative=true() + } + break; + + case ocGammaDist: + case ocFDist_LT: + case ocNormDist: + if (mnCurArg == 2) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true() + } + break; + + case ocLogInv: + case ocLogNormDist: + if ( mnCurArg == 0 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 0.0 ); // 2nd, mean = 0.0 + } + if ( mnCurArg <= 1 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 1.0 ); // 3rd, standard deviation = 1.0 + } + break; + + case ocHypGeomDist: + if ( mnCurArg == 3 ) + { + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 0.0 ); // 5th, Cumulative = false() + } + break; + + case ocRound: + case ocRoundUp: + case ocRoundDown: + if( mnCurArg == 0 ) + { + // ROUND, ROUNDUP, ROUNDDOWN functions are fixed to 2 parameters in Excel + pNewArr->AddOpCode( ocSep ); + pNewArr->AddDouble( 0.0 ); // 2nd, 0.0 + } + break; + + default: + break; + } + } + break; + } + +} + +inline bool FormulaMissingContext::AddDefaultArg( FormulaTokenArray* pNewArr, int nArg, double f ) const +{ + if (mnCurArg == nArg) + { + pNewArr->AddDouble( f ); + return true; + } + return false; +} + +bool FormulaMissingContext::AddMissingExternal( FormulaTokenArray *pNewArr ) const +{ + // Only called for PODF, not ODFF. No need to distinguish. + + const OUString &rName = mpFunc->GetExternal(); + + // initial (fast) checks: + sal_Int32 nLength = rName.getLength(); + if (!nLength) + return false; + + sal_Unicode nLastChar = rName[ nLength - 1]; + if ( nLastChar != 't' && nLastChar != 'm' ) + return false; + + if (rName.equalsIgnoreAsciiCase( + "com.sun.star.sheet.addin.Analysis.getAccrint" )) + { + return AddDefaultArg( pNewArr, 4, 1000.0 ); + } + if (rName.equalsIgnoreAsciiCase( + "com.sun.star.sheet.addin.Analysis.getAccrintm" )) + { + return AddDefaultArg( pNewArr, 3, 1000.0 ); + } + return false; +} + +bool FormulaMissingContext::AddMissing( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const +{ + if ( !mpFunc ) + return false; + + bool bRet = false; + const OpCode eOp = mpFunc->GetOpCode(); + + switch (rConv.getConvention()) + { + case MissingConvention::FORMULA_MISSING_CONVENTION_ODFF: + { + // Add for ODFF + switch (eOp) + { + case ocAddress: + return AddDefaultArg( pNewArr, 2, 1.0 ); // abs + default: + break; + } + } + break; + case MissingConvention::FORMULA_MISSING_CONVENTION_PODF: + { + // Add for PODF + switch (eOp) + { + case ocAddress: + return AddDefaultArg( pNewArr, 2, 1.0 ); // abs + case ocFixed: + return AddDefaultArg( pNewArr, 1, 2.0 ); + case ocBetaDist: + case ocBetaInv: + case ocPMT: + return AddDefaultArg( pNewArr, 3, 0.0 ); + case ocIpmt: + case ocPpmt: + return AddDefaultArg( pNewArr, 4, 0.0 ); + case ocPV: + case ocFV: + bRet |= AddDefaultArg( pNewArr, 2, 0.0 ); // pmt + bRet |= AddDefaultArg( pNewArr, 3, 0.0 ); // [fp]v + break; + case ocRate: + bRet |= AddDefaultArg( pNewArr, 1, 0.0 ); // pmt + bRet |= AddDefaultArg( pNewArr, 3, 0.0 ); // fv + bRet |= AddDefaultArg( pNewArr, 4, 0.0 ); // type + break; + case ocExternal: + return AddMissingExternal( pNewArr ); + + // --- more complex cases --- + + case ocOffset: + // FIXME: rather tough + // if arg 3 (height) omitted, export arg1 (rows) + break; + default: + break; + } + } + break; + case MissingConvention::FORMULA_MISSING_CONVENTION_OOXML: + { + switch (eOp) + { + case ocExternal: + return AddMissingExternal( pNewArr ); + default: + break; + } + } + break; + } + + return bRet; +} + +bool FormulaTokenArray::NeedsPodfRewrite( const MissingConventionODF & rConv ) +{ + for ( auto i: Tokens() ) + { + if ( rConv.isRewriteNeeded( i->GetOpCode())) + return true; + } + return false; +} + +bool FormulaTokenArray::NeedsOoxmlRewrite() +{ + for ( auto i: Tokens() ) + { + if ( MissingConventionOOXML::isRewriteNeeded( i->GetOpCode())) + return true; + } + return false; +} + + +FormulaTokenArray * FormulaTokenArray::RewriteMissing( const MissingConvention & rConv ) +{ + const size_t nAlloc = 256; + FormulaMissingContext aCtx[ nAlloc ]; + + /* TODO: with some effort we might be able to merge the two almost + * identical function stacks into one and generalize things, otherwise + * adding yet another "omit argument" would be copypasta. */ + + int aOpCodeAddressStack[ nAlloc ]; // use of ADDRESS() function + const int nOmitAddressArg = 3; // ADDRESS() 4th parameter A1/R1C1 + + int aOpCodeDcountStack[ nAlloc ]; // use of DCOUNT()/DCOUNTA() function + const int nOmitDcountArg = 1; // DCOUNT() and DCOUNTA() 2nd parameter DatabaseField if 0 + + sal_uInt16 nTokens = GetLen() + 1; + FormulaMissingContext* pCtx = (nAlloc < nTokens ? new FormulaMissingContext[nTokens] : &aCtx[0]); + int* pOcas = (nAlloc < nTokens ? new int[nTokens] : &aOpCodeAddressStack[0]); + int* pOcds = (nAlloc < nTokens ? new int[nTokens] : &aOpCodeDcountStack[0]); + // Never go below 0, never use 0, mpFunc always NULL. + pCtx[0].Clear(); + int nFn = 0; + int nOcas = 0; + int nOcds = 0; + + FormulaTokenArray *pNewArr = new FormulaTokenArray; + // At least ScRecalcMode::ALWAYS needs to be set. + pNewArr->AddRecalcMode( GetRecalcMode()); + + FormulaTokenArrayPlainIterator aIter(*this); + for ( FormulaToken *pCur = aIter.First(); pCur; pCur = aIter.Next() ) + { + bool bAdd = true; + // Don't write the expression of the new inserted ADDRESS() parameter. + // Do NOT omit the new second parameter of INDIRECT() though. If that + // was done for both, INDIRECT() actually could calculate different and + // valid (but wrong) results with the then changed return value of + // ADDRESS(). Better let it generate an error instead. + for (int i = nOcas; i-- > 0 && bAdd; ) + { + if (pCtx[ pOcas[ i ] ].mnCurArg == nOmitAddressArg) + { + // Omit everything except a trailing separator, the leading + // separator is omitted below. The other way around would leave + // an extraneous separator if no parameter followed. + if (pOcas[ i ] != nFn || pCur->GetOpCode() != ocSep) + bAdd = false; + } + } + // Strip the 2nd argument (leaving empty) of DCOUNT() and DCOUNTA() if + // it is 0. + for (int i = nOcds; i-- > 0 && bAdd; ) + { + if (pCtx[ pOcds[ i ] ].mnCurArg == nOmitDcountArg) + { + // Omit only a literal 0 value, nothing else. + if (pOcds[ i ] == nFn && pCur->GetOpCode() == ocPush && pCur->GetType() == svDouble && + pCur->GetDouble() == 0.0) + { + // No other expression, between separators. + FormulaToken* p = aIter.PeekPrevNoSpaces(); + if (p && p->GetOpCode() == ocSep) + { + p = aIter.PeekNextNoSpaces(); + if (p && p->GetOpCode() == ocSep) + bAdd = false; + } + } + } + } + switch ( pCur->GetOpCode() ) + { + case ocOpen: + { + ++nFn; // all following operations on _that_ function + pCtx[ nFn ].mpFunc = aIter.PeekPrevNoSpaces(); + pCtx[ nFn ].mnCurArg = 0; + if (rConv.isPODF() && pCtx[ nFn ].mpFunc && pCtx[ nFn ].mpFunc->GetOpCode() == ocAddress) + pOcas[ nOcas++ ] = nFn; // entering ADDRESS() if PODF + else if ((rConv.isODFF() || rConv.isOOXML()) && pCtx[ nFn ].mpFunc) + { + OpCode eOp = pCtx[ nFn ].mpFunc->GetOpCode(); + if (eOp == ocDBCount || eOp == ocDBCount2) + pOcds[ nOcds++ ] = nFn; // entering DCOUNT() or DCOUNTA() if ODFF or OOXML + } + } + break; + case ocClose: + pCtx[ nFn ].AddMoreArgs( pNewArr, rConv ); + SAL_WARN_IF(nFn <= 0, "formula.core", "FormulaTokenArray::RewriteMissing: underflow"); + if (nOcas > 0 && pOcas[ nOcas-1 ] == nFn) + --nOcas; // leaving ADDRESS() + else if (nOcds > 0 && pOcds[ nOcds-1 ] == nFn) + --nOcds; // leaving DCOUNT() or DCOUNTA() + if (nFn > 0) + --nFn; + break; + case ocSep: + pCtx[ nFn ].mnCurArg++; + // Omit leading separator of ADDRESS() parameter. + if (nOcas && pOcas[ nOcas-1 ] == nFn && pCtx[ nFn ].mnCurArg == nOmitAddressArg) + { + bAdd = false; + } + break; + case ocMissing: + if ( bAdd ) + bAdd = !pCtx[ nFn ].AddMissing( pNewArr, rConv ); + break; + default: + break; + } + if (bAdd) + { + OpCode eOp = pCur->GetOpCode(); + if ( ( eOp == ocCeil || eOp == ocFloor || + ( eOp == ocLogNormDist && pCur->GetByte() == 4 ) ) && + rConv.getConvention() == MissingConvention::FORMULA_MISSING_CONVENTION_OOXML ) + { + switch ( eOp ) + { + case ocCeil : + eOp = ocCeil_Math; + break; + case ocFloor : + eOp = ocFloor_Math; + break; + case ocLogNormDist : + eOp = ocLogNormDist_MS; + break; + default : + eOp = ocNone; + break; + } + FormulaToken *pToken = new FormulaToken( svByte, eOp ); + pNewArr->Add( pToken ); + } + else if ( eOp == ocHypGeomDist && + rConv.getConvention() == MissingConvention::FORMULA_MISSING_CONVENTION_OOXML ) + { + FormulaToken *pToken = new FormulaToken( svByte, ocHypGeomDist_MS ); + pNewArr->Add( pToken ); + } + else + pNewArr->AddToken( *pCur ); + } + } + + if (pOcds != &aOpCodeDcountStack[0]) + delete [] pOcds; + if (pOcas != &aOpCodeAddressStack[0]) + delete [] pOcas; + if (pCtx != &aCtx[0]) + delete [] pCtx; + + return pNewArr; +} + +namespace { +inline bool isWhitespace( OpCode eOp ) { return eOp == ocSpaces || eOp == ocWhitespace; } +} + +bool FormulaTokenArray::MayReferenceFollow() +{ + if ( !pCode || nLen <= 0 ) + return false; + + // ignore trailing spaces + sal_uInt16 i = nLen - 1; + while (i > 0 && isWhitespace( pCode[i]->GetOpCode())) + { + --i; + } + if (i > 0 || !isWhitespace( pCode[i]->GetOpCode())) + { + OpCode eOp = pCode[i]->GetOpCode(); + if ( (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP ) || + (SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP ) || + eOp == SC_OPCODE_OPEN || eOp == SC_OPCODE_SEP ) + { + return true; + } + } + return false; +} +FormulaToken* FormulaTokenArray::AddOpCode( OpCode eOp ) +{ + FormulaToken* pRet = nullptr; + switch ( eOp ) + { + case ocOpen: + case ocClose: + case ocSep: + case ocArrayOpen: + case ocArrayClose: + case ocArrayRowSep: + case ocArrayColSep: + pRet = new FormulaToken( svSep,eOp ); + break; + case ocIf: + case ocIfError: + case ocIfNA: + case ocChoose: + { + short nJump[FORMULA_MAXJUMPCOUNT + 1]; + if ( eOp == ocIf ) + nJump[ 0 ] = 3; + else if ( eOp == ocChoose ) + nJump[ 0 ] = FORMULA_MAXJUMPCOUNT + 1; + else + nJump[ 0 ] = 2; + pRet = new FormulaJumpToken( eOp, nJump ); + } + break; + default: + pRet = new FormulaByteToken( eOp, 0, ParamClass::Unknown ); + break; + } + return Add( pRet ); +} + +void FormulaTokenArray::ReinternStrings( svl::SharedStringPool& rPool ) +{ + for (auto i: Tokens()) + { + switch (i->GetType()) + { + case svString: + i->SetString( rPool.intern( i->GetString().getString())); + break; + default: + ; // nothing + } + } +} + + +/*----------------------------------------------------------------------*/ + +FormulaTokenIterator::Item::Item(const FormulaTokenArray* pArray, short pc, short stop) : + pArr(pArray), nPC(pc), nStop(stop) +{ +} + +FormulaTokenIterator::FormulaTokenIterator( const FormulaTokenArray& rArr ) + : maStack{ FormulaTokenIterator::Item(&rArr, -1, SHRT_MAX) } +{ +} + +FormulaTokenIterator::~FormulaTokenIterator() +{ +} + +void FormulaTokenIterator::Push( const FormulaTokenArray* pArr ) +{ + FormulaTokenIterator::Item item(pArr, -1, SHRT_MAX); + + maStack.push_back(item); +} + +void FormulaTokenIterator::Pop() +{ + maStack.pop_back(); +} + +void FormulaTokenIterator::Reset() +{ + while( maStack.size() > 1 ) + maStack.pop_back(); + + maStack.back().nPC = -1; +} + +FormulaToken* FormulaTokenArrayPlainIterator::GetNextName() +{ + if( mpFTA->GetArray() ) + { + while ( mnIndex < mpFTA->GetLen() ) + { + FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ]; + if( t->GetType() == svIndex ) + return t; + } + } + return nullptr; +} + +const FormulaToken* FormulaTokenIterator::Next() +{ + const FormulaToken* t = GetNonEndOfPathToken( ++maStack.back().nPC ); + if( !t && maStack.size() > 1 ) + { + Pop(); + t = Next(); + } + return t; +} + +const FormulaToken* FormulaTokenIterator::PeekNextOperator() +{ + const FormulaToken* t = nullptr; + short nIdx = maStack.back().nPC; + for (;;) + { + t = GetNonEndOfPathToken( ++nIdx); + if (t == nullptr || t->GetOpCode() != ocPush) + break; // ignore operands + } + if (!t && maStack.size() > 1) + { + FormulaTokenIterator::Item aHere = maStack.back(); + maStack.pop_back(); + t = PeekNextOperator(); + maStack.push_back(aHere); + } + return t; +} + +//! The nPC counts after a Push() are -1 + +void FormulaTokenIterator::Jump( short nStart, short nNext, short nStop ) +{ + maStack.back().nPC = nNext; + if( nStart != nNext ) + { + Push( maStack.back().pArr ); + maStack.back().nPC = nStart; + maStack.back().nStop = nStop; + } +} + +void FormulaTokenIterator::ReInit( const FormulaTokenArray& rArr ) +{ + maStack.clear(); + Push( &rArr ); +} + +const FormulaToken* FormulaTokenIterator::GetNonEndOfPathToken( short nIdx ) const +{ + FormulaTokenIterator::Item cur = maStack.back(); + + if (nIdx < cur.pArr->GetCodeLen() && nIdx < cur.nStop) + { + const FormulaToken* t = cur.pArr->GetCode()[ nIdx ]; + // such an OpCode ends an IF() or CHOOSE() path + return (t->GetOpCode() == ocSep || t->GetOpCode() == ocClose) ? nullptr : t; + } + return nullptr; +} + +bool FormulaTokenIterator::IsEndOfPath() const +{ + return GetNonEndOfPathToken( maStack.back().nPC + 1) == nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::GetNextReference() +{ + while( mnIndex < mpFTA->GetLen() ) + { + FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ]; + switch( t->GetType() ) + { + case svSingleRef: + case svDoubleRef: + case svExternalSingleRef: + case svExternalDoubleRef: + return t; + default: + { + // added to avoid warnings + } + } + } + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::GetNextColRowName() +{ + while( mnIndex < mpFTA->GetLen() ) + { + FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ]; + if ( t->GetOpCode() == ocColRowName ) + return t; + } + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::GetNextReferenceRPN() +{ + while( mnIndex < mpFTA->GetCodeLen() ) + { + FormulaToken* t = mpFTA->GetCode()[ mnIndex++ ]; + switch( t->GetType() ) + { + case svSingleRef: + case svDoubleRef: + case svExternalSingleRef: + case svExternalDoubleRef: + return t; + default: + { + // added to avoid warnings + } + } + } + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::GetNextReferenceOrName() +{ + if( !mpFTA->GetArray() ) + return nullptr; + + while ( mnIndex < mpFTA->GetLen() ) + { + FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ]; + switch( t->GetType() ) + { + case svSingleRef: + case svDoubleRef: + case svIndex: + case svExternalSingleRef: + case svExternalDoubleRef: + case svExternalName: + return t; + default: + { + // added to avoid warnings + } + } + } + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::Next() +{ + if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() ) + return mpFTA->GetArray()[ mnIndex++ ]; + else + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::NextNoSpaces() +{ + if( mpFTA->GetArray() ) + { + while ((mnIndex < mpFTA->GetLen()) && isWhitespace( mpFTA->GetArray()[ mnIndex ]->GetOpCode())) + ++mnIndex; + if( mnIndex < mpFTA->GetLen() ) + return mpFTA->GetArray()[ mnIndex++ ]; + } + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::NextRPN() +{ + if( mpFTA->GetCode() && mnIndex < mpFTA->GetCodeLen() ) + return mpFTA->GetCode()[ mnIndex++ ]; + else + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::PrevRPN() +{ + if( mpFTA->GetCode() && mnIndex ) + return mpFTA->GetCode()[ --mnIndex ]; + else + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::PeekNext() +{ + if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() ) + return mpFTA->GetArray()[ mnIndex ]; + else + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::PeekNextNoSpaces() const +{ + if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() ) + { + sal_uInt16 j = mnIndex; + while (j < mpFTA->GetLen() && isWhitespace( mpFTA->GetArray()[j]->GetOpCode())) + j++; + if ( j < mpFTA->GetLen() ) + return mpFTA->GetArray()[ j ]; + else + return nullptr; + } + else + return nullptr; +} + +FormulaToken* FormulaTokenArrayPlainIterator::PeekPrevNoSpaces() const +{ + if( mpFTA->GetArray() && mnIndex > 1 ) + { + sal_uInt16 j = mnIndex - 2; + while (isWhitespace( mpFTA->GetArray()[j]->GetOpCode()) && j > 0 ) + j--; + if (j > 0 || !isWhitespace( mpFTA->GetArray()[j]->GetOpCode())) + return mpFTA->GetArray()[ j ]; + else + return nullptr; + } + else + return nullptr; +} + +void FormulaTokenArrayPlainIterator::AfterRemoveToken( sal_uInt16 nOffset, sal_uInt16 nCount ) +{ + const sal_uInt16 nStop = std::min( static_cast<sal_uInt16>(nOffset + nCount), mpFTA->GetLen()); + + if (mnIndex >= nOffset) + { + if (mnIndex < nStop) + mnIndex = nOffset + 1; + else + mnIndex -= nStop - nOffset; + } +} + +// real implementations of virtual functions + + +double FormulaDoubleToken::GetDouble() const { return fDouble; } +double & FormulaDoubleToken::GetDoubleAsReference() { return fDouble; } + +sal_Int16 FormulaDoubleToken::GetDoubleType() const +{ + // This is a plain double value without type information, don't emit a + // warning via FormulaToken::GetDoubleType(). + return 0; +} + +bool FormulaDoubleToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && fDouble == r.GetDouble(); +} + +sal_Int16 FormulaTypedDoubleToken::GetDoubleType() const +{ + return mnType; +} + +void FormulaTypedDoubleToken::SetDoubleType( sal_Int16 nType ) +{ + mnType = nType; +} + +bool FormulaTypedDoubleToken::operator==( const FormulaToken& r ) const +{ + return FormulaDoubleToken::operator==( r ) && mnType == r.GetDoubleType(); +} + +FormulaStringToken::FormulaStringToken( svl::SharedString r ) : + FormulaToken( svString ), maString(std::move( r )) +{ +} + +FormulaStringToken::FormulaStringToken( const FormulaStringToken& r ) : + FormulaToken( r ), maString( r.maString ) {} + +FormulaToken* FormulaStringToken::Clone() const +{ + return new FormulaStringToken(*this); +} + +const svl::SharedString & FormulaStringToken::GetString() const +{ + return maString; +} + +void FormulaStringToken::SetString( const svl::SharedString& rStr ) +{ + maString = rStr; +} + +bool FormulaStringToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && maString == r.GetString(); +} + +FormulaStringOpToken::FormulaStringOpToken( OpCode e, svl::SharedString r ) : + FormulaByteToken( e, 0, svString, ParamClass::Unknown ), maString(std::move( r )) {} + +FormulaStringOpToken::FormulaStringOpToken( const FormulaStringOpToken& r ) : + FormulaByteToken( r ), maString( r.maString ) {} + +FormulaToken* FormulaStringOpToken::Clone() const +{ + return new FormulaStringOpToken(*this); +} + +const svl::SharedString & FormulaStringOpToken::GetString() const +{ + return maString; +} + +void FormulaStringOpToken::SetString( const svl::SharedString& rStr ) +{ + maString = rStr; +} + +bool FormulaStringOpToken::operator==( const FormulaToken& r ) const +{ + return FormulaByteToken::operator==( r ) && maString == r.GetString(); +} + +sal_uInt16 FormulaIndexToken::GetIndex() const { return nIndex; } +void FormulaIndexToken::SetIndex( sal_uInt16 n ) { nIndex = n; } +sal_Int16 FormulaIndexToken::GetSheet() const { return mnSheet; } +void FormulaIndexToken::SetSheet( sal_Int16 n ) { mnSheet = n; } +bool FormulaIndexToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && nIndex == r.GetIndex() && + mnSheet == r.GetSheet(); +} +const OUString& FormulaExternalToken::GetExternal() const { return aExternal; } +bool FormulaExternalToken::operator==( const FormulaToken& r ) const +{ + return FormulaByteToken::operator==( r ) && aExternal == r.GetExternal(); +} + + +FormulaError FormulaErrorToken::GetError() const { return nError; } +void FormulaErrorToken::SetError( FormulaError nErr ) { nError = nErr; } +bool FormulaErrorToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && + nError == static_cast< const FormulaErrorToken & >(r).GetError(); +} +double FormulaMissingToken::GetDouble() const { return 0.0; } + +const svl::SharedString & FormulaMissingToken::GetString() const +{ + return svl::SharedString::getEmptyString(); +} + +bool FormulaMissingToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ); +} + + +bool FormulaUnknownToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ); +} + + +} // formula + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/core/api/vectortoken.cxx b/formula/source/core/api/vectortoken.cxx new file mode 100644 index 0000000000..c1bd521577 --- /dev/null +++ b/formula/source/core/api/vectortoken.cxx @@ -0,0 +1,109 @@ +/* -*- 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/. + */ + +#include <formula/vectortoken.hxx> +#include <sal/log.hxx> + +namespace formula { + +VectorRefArray::VectorRefArray() : + mpNumericArray(nullptr), + mpStringArray(nullptr), + mbValid(true) {} + +VectorRefArray::VectorRefArray( InitInvalid ) : + mpNumericArray(nullptr), + mpStringArray(nullptr), + mbValid(false) {} + +VectorRefArray::VectorRefArray( const double* pArray ) : + mpNumericArray(pArray), + mpStringArray(nullptr), + mbValid(true) {} + +VectorRefArray::VectorRefArray( rtl_uString** pArray ) : + mpNumericArray(nullptr), + mpStringArray(pArray), + mbValid(true) {} + +VectorRefArray::VectorRefArray( const double* pNumArray, rtl_uString** pStrArray ) : + mpNumericArray(pNumArray), + mpStringArray(pStrArray), + mbValid(true) {} + +bool VectorRefArray::isValid() const +{ + return mbValid; +} + +SingleVectorRefToken::SingleVectorRefToken( const VectorRefArray& rArray, size_t nArrayLength ) : + FormulaToken(svSingleVectorRef, ocPush), maArray(rArray), mnArrayLength(nArrayLength) +{ + SAL_INFO("formula.core", "Created SingleVectorRefToken nArrayLength=" << nArrayLength); +} + +FormulaToken* SingleVectorRefToken::Clone() const +{ + return new SingleVectorRefToken(maArray, mnArrayLength); +} + +const VectorRefArray& SingleVectorRefToken::GetArray() const +{ + return maArray; +} + +size_t SingleVectorRefToken::GetArrayLength() const +{ + return mnArrayLength; +} + +DoubleVectorRefToken::DoubleVectorRefToken( + std::vector<VectorRefArray>&& rArrays, size_t nArrayLength, + size_t nRefRowSize, bool bStartFixed, bool bEndFixed ) : + FormulaToken(svDoubleVectorRef, ocPush), + maArrays(std::move(rArrays)), mnArrayLength(nArrayLength), + mnRefRowSize(nRefRowSize), mbStartFixed(bStartFixed), mbEndFixed(bEndFixed) +{ + SAL_INFO("formula.core", "Created DoubleVectorRefToken nArrayLength=" << nArrayLength); +} + +FormulaToken* DoubleVectorRefToken::Clone() const +{ + return new DoubleVectorRefToken( + std::vector(maArrays), mnArrayLength, mnRefRowSize, mbStartFixed, mbEndFixed); +} + +const std::vector<VectorRefArray>& DoubleVectorRefToken::GetArrays() const +{ + return maArrays; +} + +size_t DoubleVectorRefToken::GetArrayLength() const +{ + return mnArrayLength; +} + +size_t DoubleVectorRefToken::GetRefRowSize() const +{ + return mnRefRowSize; +} + +bool DoubleVectorRefToken::IsStartFixed() const +{ + return mbStartFixed; +} + +bool DoubleVectorRefToken::IsEndFixed() const +{ + return mbEndFixed; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/core/resource/core_resource.cxx b/formula/source/core/resource/core_resource.cxx new file mode 100644 index 0000000000..d184323579 --- /dev/null +++ b/formula/source/core/resource/core_resource.cxx @@ -0,0 +1,23 @@ +/* + * 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 <core_resource.hxx> + +OUString ForResId(TranslateId aId) { return Translate::get(aId, Translate::Create("for")); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/ControlHelper.hxx b/formula/source/ui/dlg/ControlHelper.hxx new file mode 100644 index 0000000000..2495474516 --- /dev/null +++ b/formula/source/ui/dlg/ControlHelper.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ +#pragma once + +#include <formula/funcutl.hxx> + +namespace formula +{ + +class ParaWin; + + +class ArgEdit : public RefEdit +{ +public: + ArgEdit(std::unique_ptr<weld::Entry> xControl); + + void Init(ArgEdit* pPrevEdit, ArgEdit* pNextEdit, + weld::ScrolledWindow& rArgSlider, + ParaWin& rParaWin, sal_uInt16 nArgCount); + +protected: + virtual bool KeyInput(const KeyEvent& rKEvt) override; + +private: + ArgEdit* pEdPrev; + ArgEdit* pEdNext; + weld::ScrolledWindow* pSlider; + ParaWin* pParaWin; + sal_uInt16 nArgs; +}; + + + +class ArgInput final +{ +private: + Link<ArgInput&,void> aFxClickLink; + Link<ArgInput&,void> aFxFocusLink; + Link<ArgInput&,void> aEdFocusLink; + Link<ArgInput&,void> aEdModifyLink; + + weld::Label*pFtArg; + weld::Button* pBtnFx; + ArgEdit* pEdArg; + RefButton* pRefBtn; + + DECL_LINK( FxBtnClickHdl, weld::Button&, void ); + DECL_LINK( FxBtnFocusHdl, weld::Widget&, void ); + DECL_LINK( EdFocusHdl, RefEdit&, void ); + DECL_LINK( EdModifyHdl, RefEdit&, void ); + +public: + + ArgInput(); + + void InitArgInput(weld::Label* pftArg, + weld::Button* pbtnFx, + ArgEdit* pedArg, + RefButton* prefBtn); + + void SetArgName(const OUString &aArg); + OUString GetArgName() const; + void SetArgNameFont(const vcl::Font&); + + void SetArgVal(const OUString &aVal); + OUString GetArgVal() const; + + void SelectAll(); + + ArgEdit* GetArgEdPtr() { return pEdArg; } + + + void SetFxClickHdl( const Link<ArgInput&,void>& rLink ) { aFxClickLink = rLink; } + + void SetFxFocusHdl( const Link<ArgInput&,void>& rLink ) { aFxFocusLink = rLink; } + + void SetEdFocusHdl( const Link<ArgInput&,void>& rLink ) { aEdFocusLink = rLink; } + + void SetEdModifyHdl( const Link<ArgInput&,void>& rLink ) { aEdModifyLink = rLink; } + + void Hide(); + void Show(); + + void UpdateAccessibleNames(); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/FormulaHelper.cxx b/formula/source/ui/dlg/FormulaHelper.cxx new file mode 100644 index 0000000000..225cef3c7b --- /dev/null +++ b/formula/source/ui/dlg/FormulaHelper.cxx @@ -0,0 +1,416 @@ +/* -*- 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 <algorithm> + +#include <formula/formulahelper.hxx> +#include <formula/IFunctionDescription.hxx> +#include <unotools/charclass.hxx> +#include <unotools/syslocale.hxx> + +namespace formula +{ + + namespace + { + + class OEmptyFunctionDescription : public IFunctionDescription + { + public: + OEmptyFunctionDescription(){} + virtual ~OEmptyFunctionDescription(){} + + virtual OUString getFunctionName() const override { return OUString(); } + virtual const IFunctionCategory* getCategory() const override { return nullptr; } + virtual OUString getDescription() const override { return OUString(); } + virtual sal_Int32 getSuppressedArgumentCount() const override { return 0; } + virtual OUString getFormula(const ::std::vector< OUString >& ) const override { return OUString(); } + virtual void fillVisibleArgumentMapping(::std::vector<sal_uInt16>& ) const override {} + virtual void initArgumentInfo() const override {} + virtual OUString getSignature() const override { return OUString(); } + virtual OUString getHelpId() const override { return ""; } + virtual bool isHidden() const override { return false; } + virtual sal_uInt32 getParameterCount() const override { return 0; } + virtual sal_uInt32 getVarArgsStart() const override { return 0; } + virtual sal_uInt32 getVarArgsLimit() const override { return 0; } + virtual OUString getParameterName(sal_uInt32 ) const override { return OUString(); } + virtual OUString getParameterDescription(sal_uInt32 ) const override { return OUString(); } + virtual bool isParameterOptional(sal_uInt32 ) const override { return false; } + }; + } + +// class FormulaHelper - static Method + + +#define FUNC_NOTFOUND -1 + +FormulaHelper::FormulaHelper(const IFunctionManager* _pFunctionManager) + :m_rCharClass(m_aSysLocale.GetCharClass()) + ,m_pFunctionManager(_pFunctionManager) + ,open(_pFunctionManager->getSingleToken(IFunctionManager::eOk)) + ,close(_pFunctionManager->getSingleToken(IFunctionManager::eClose)) + ,sep(_pFunctionManager->getSingleToken(IFunctionManager::eSep)) + ,arrayOpen(_pFunctionManager->getSingleToken(IFunctionManager::eArrayOpen)) + ,arrayClose(_pFunctionManager->getSingleToken(IFunctionManager::eArrayClose)) +{ +} + +sal_Int32 FormulaHelper::GetCategoryCount() const +{ + return m_pFunctionManager->getCount(); +} + +bool FormulaHelper::GetNextFunc( const OUString& rFormula, + bool bBack, + sal_Int32& rFStart, // Input and output + sal_Int32* pFEnd, // = NULL + const IFunctionDescription** ppFDesc, // = NULL + ::std::vector< OUString>* pArgs ) const // = NULL +{ + sal_Int32 nOldStart = rFStart; + OUString aFname; + + rFStart = GetFunctionStart( rFormula, rFStart, bBack, ppFDesc ? &aFname : nullptr ); + bool bFound = ( rFStart != FUNC_NOTFOUND ); + + if ( bFound ) + { + if ( pFEnd ) + *pFEnd = GetFunctionEnd( rFormula, rFStart ); + + if ( ppFDesc ) + { + *ppFDesc = nullptr; + const sal_uInt32 nCategoryCount = m_pFunctionManager->getCount(); + for(sal_uInt32 j= 0; j < nCategoryCount && !*ppFDesc; ++j) + { + const IFunctionCategory* pCategory = m_pFunctionManager->getCategory(j); + const sal_uInt32 nCount = pCategory->getCount(); + for(sal_uInt32 i = 0 ; i < nCount; ++i) + { + const IFunctionDescription* pCurrent = pCategory->getFunction(i); + if ( pCurrent->getFunctionName().equalsIgnoreAsciiCase(aFname) ) + { + *ppFDesc = pCurrent; + break; + } + }// for(sal_uInt32 i = 0 ; i < nCount; ++i) + } + if ( *ppFDesc && pArgs ) + { + GetArgStrings( *pArgs,rFormula, rFStart, static_cast<sal_uInt16>((*ppFDesc)->getParameterCount() )); + } + else + { + static OEmptyFunctionDescription s_aFunctionDescription; + *ppFDesc = &s_aFunctionDescription; + } + } + } + else + rFStart = nOldStart; + + return bFound; +} + + +void FormulaHelper::FillArgStrings( std::u16string_view rFormula, + sal_Int32 nFuncPos, + sal_uInt16 nArgs, + ::std::vector< OUString >& _rArgs ) const +{ + sal_Int32 nStart = 0; + sal_Int32 nEnd = 0; + sal_uInt16 i; + bool bLast = false; + + for ( i=0; i<nArgs && !bLast; i++ ) + { + nStart = GetArgStart( rFormula, nFuncPos, i ); + + if ( i+1<nArgs ) // last argument? + { + nEnd = GetArgStart( rFormula, nFuncPos, i+1 ); + + if ( nEnd != nStart ) + _rArgs.push_back(OUString(rFormula.substr( nStart, nEnd-1-nStart ))); + else + { + _rArgs.emplace_back(); + bLast = true; + } + } + else + { + nEnd = GetFunctionEnd( rFormula, nFuncPos )-1; + if ( nStart < nEnd ) + _rArgs.push_back( OUString(rFormula.substr( nStart, nEnd-nStart )) ); + else + _rArgs.emplace_back(); + } + } + + if ( bLast ) + for ( ; i<nArgs; i++ ) + _rArgs.emplace_back(); +} + + +void FormulaHelper::GetArgStrings( ::std::vector< OUString >& _rArgs, + std::u16string_view rFormula, + sal_Int32 nFuncPos, + sal_uInt16 nArgs ) const +{ + if (nArgs) + { + FillArgStrings( rFormula, nFuncPos, nArgs, _rArgs ); + } +} + + +static bool IsFormulaText(const CharClass& rCharClass, const OUString& rStr, sal_Int32 nPos) +{ + if( rCharClass.isLetterNumeric( rStr, nPos ) ) + return true; + else + { // In internationalized versions function names may contain a dot + // and in every version also an underscore... ;-) + sal_Unicode c = rStr[nPos]; + return c == '.' || c == '_'; + } + +} + +sal_Int32 FormulaHelper::GetFunctionStart( const OUString& rFormula, + sal_Int32 nStart, + bool bBack, + OUString* pFuncName ) const +{ + sal_Int32 nStrLen = rFormula.getLength(); + + if ( nStrLen < nStart ) + return nStart; + + sal_Int32 nFStart = FUNC_NOTFOUND; + sal_Int32 nParPos = bBack ? ::std::min( nStart, nStrLen - 1) : nStart; + + bool bRepeat; + do + { + bool bFound = false; + bRepeat = false; + + if ( bBack ) + { + while ( !bFound && (nParPos > 0) ) + { + if ( rFormula[nParPos] == '"' ) + { + nParPos--; + while ( (nParPos > 0) && rFormula[nParPos] != '"' ) + nParPos--; + if (nParPos > 0) + nParPos--; + } + else + { + bFound = rFormula[nParPos] == '('; + if ( !bFound ) + nParPos--; + } + } + } + else + { + while ( !bFound && (0 <= nParPos && nParPos < nStrLen) ) + { + if ( rFormula[nParPos] == '"' ) + { + nParPos++; + while ( (nParPos < nStrLen) && rFormula[nParPos] != '"' ) + nParPos++; + nParPos++; + } + else + { + bFound = rFormula[nParPos] == '('; + if ( !bFound ) + nParPos++; + } + } + } + + if ( bFound && (nParPos > 0) ) + { + nFStart = nParPos-1; + + while ( (nFStart > 0) && IsFormulaText(m_rCharClass, rFormula, nFStart )) + nFStart--; + } + + nFStart++; + + if ( bFound ) + { + if ( IsFormulaText( m_rCharClass, rFormula, nFStart ) ) + { + // Function found + if ( pFuncName ) + *pFuncName = rFormula.copy( nFStart, nParPos-nFStart ); + } + else // Brackets without function -> keep searching + { + bRepeat = true; + if ( !bBack ) + nParPos++; + else if (nParPos > 0) + nParPos--; + else + bRepeat = false; + } + } + else // No brackets found + { + nFStart = FUNC_NOTFOUND; + if ( pFuncName ) + pFuncName->clear(); + } + } + while(bRepeat); + + return nFStart; +} + + +sal_Int32 FormulaHelper::GetFunctionEnd( std::u16string_view rStr, sal_Int32 nStart ) const +{ + sal_Int32 nStrLen = rStr.size(); + + if ( nStrLen < nStart ) + return nStart; + + short nParCount = 0; + bool bInArray = false; + bool bFound = false; + + while ( !bFound && (nStart < nStrLen) ) + { + sal_Unicode c = rStr[nStart]; + + if ( c == '"' ) + { + nStart++; + while ( (nStart < nStrLen) && rStr[nStart] != '"' ) + nStart++; + } + else if ( c == open ) + nParCount++; + else if ( c == close ) + { + nParCount--; + if ( nParCount == 0 ) + bFound = true; + else if ( nParCount < 0 ) + { + bFound = true; + nStart--; // read one too far + } + } + else if ( c == arrayOpen ) + { + bInArray = true; + } + else if ( c == arrayClose ) + { + bInArray = false; + } + else if ( c == sep ) + { + if ( !bInArray && nParCount == 0 ) + { + bFound = true; + nStart--; // read one too far + } + } + nStart++; // Set behind found position + } + + // nStart > nStrLen can happen if there was an unclosed quote; instead of + // checking that in every loop iteration check it once here. + return std::min(nStart, nStrLen); +} + + +sal_Int32 FormulaHelper::GetArgStart( std::u16string_view rStr, sal_Int32 nStart, sal_uInt16 nArg ) const +{ + sal_Int32 nStrLen = rStr.size(); + + if ( nStrLen < nStart ) + return nStart; + + short nParCount = 0; + bool bInArray = false; + bool bFound = false; + + while ( !bFound && (nStart < nStrLen) ) + { + sal_Unicode c = rStr[nStart]; + + if ( c == '"' ) + { + nStart++; + while ( (nStart < nStrLen) && rStr[nStart] != '"' ) + nStart++; + } + else if ( c == open ) + { + bFound = ( nArg == 0 ); + nParCount++; + } + else if ( c == close ) + { + nParCount--; + bFound = ( nParCount == 0 ); + } + else if ( c == arrayOpen ) + { + bInArray = true; + } + else if ( c == arrayClose ) + { + bInArray = false; + } + else if ( c == sep ) + { + if ( !bInArray && nParCount == 1 ) + { + nArg--; + bFound = ( nArg == 0 ); + } + } + nStart++; + } + + return nStart; +} + +} // formula + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/formula.cxx b/formula/source/ui/dlg/formula.cxx new file mode 100644 index 0000000000..123642c46c --- /dev/null +++ b/formula/source/ui/dlg/formula.cxx @@ -0,0 +1,1961 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <sfx2/viewfrm.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <sal/log.hxx> + +#include <unotools/charclass.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include "funcpage.hxx" +#include <formula/formula.hxx> +#include <formula/IFunctionDescription.hxx> +#include <formula/FormulaCompiler.hxx> +#include <formula/token.hxx> +#include <formula/tokenarray.hxx> +#include <formula/formdata.hxx> +#include <formula/formulahelper.hxx> +#include "structpg.hxx" +#include "parawin.hxx" +#include <strings.hrc> +#include <core_resource.hxx> +#include <com/sun/star/sheet/FormulaToken.hpp> +#include <com/sun/star/sheet/FormulaLanguage.hpp> +#include <com/sun/star/sheet/FormulaMapGroup.hpp> +#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp> +#include <com/sun/star/sheet/XFormulaOpCodeMapper.hpp> +#include <com/sun/star/sheet/XFormulaParser.hpp> +#include <map> + +// For tab page +#define TOKEN_OPEN 0 +#define TOKEN_CLOSE 1 +namespace formula +{ + +using namespace ::com::sun::star; + +class FormulaDlg_Impl +{ +public: + ::std::pair<RefButton*, RefEdit*> + RefInputStartBefore( RefEdit* pEdit, RefButton* pButton ); + void RefInputStartAfter(); + void RefInputDoneAfter( bool bForced ); + bool CalcValue( const OUString& rStrExp, OUString& rStrResult, bool bForceMatrixFormula = false ); + void CalcStruct( const OUString& rStrExp, bool bForceRecalcStruct = false ); + void UpdateValues( bool bForceRecalcStruct = false ); + void DeleteArgs(); + sal_Int32 GetFunctionPos(sal_Int32 nPos); + void ClearAllParas(); + + void MakeTree(StructPage* _pTree, weld::TreeIter* pParent, const FormulaToken* pFuncToken, + const FormulaToken* _pToken, tools::Long Count); + void fillTree(StructPage* _pTree); + void UpdateTokenArray( const OUString& rStrExp); + OUString RepairFormula(const OUString& aFormula); + void FillDialog(bool bFlag = true); + bool EditNextFunc( bool bForward, sal_Int32 nFStart = NOT_FOUND ); + void EditThisFunc(sal_Int32 nFStart); + + OUString GetPrevFuncExpression( bool bStartFromEnd ); + + void StoreFormEditData(FormEditData* pEditData); + + void Update(); + void Update(const OUString& _sExp); + + void SaveArg( sal_uInt16 nEd ); + void UpdateSelection(); + void DoEnter( bool bOk ); + void FillListboxes(); + void FillControls( bool &rbNext, bool &rbPrev); + + FormulaDlgMode SetMeText( const OUString& _sText, sal_Int32 PrivStart, sal_Int32 PrivEnd, bool bMatrix, bool _bSelect, bool _bUpdate); + void SetMeText(const OUString& _sText); + bool CheckMatrix(OUString& aFormula /*IN/OUT*/); + + void SetEdSelection(); + + bool UpdateParaWin(Selection& _rSelection); + void UpdateParaWin( const Selection& _rSelection, const OUString& _sRefStr); + + void SetData( sal_Int32 nFStart, sal_Int32 nNextFStart, sal_Int32 nNextFEnd, sal_Int32& PrivStart, sal_Int32& PrivEnd); + + RefEdit* GetCurrRefEdit(); + + const FormulaHelper& GetFormulaHelper() const { return m_aFormulaHelper;} + void InitFormulaOpCodeMapper(); + + void UpdateOldSel(); + void FormulaCursor(); + + DECL_LINK( ModifyHdl, ParaWin&, void ); + DECL_LINK( FxHdl, ParaWin&, void ); + + DECL_LINK( MatrixHdl, weld::Toggleable&, void ); + DECL_LINK( FormulaHdl, weld::TextView&, void); + DECL_LINK( FormulaCursorHdl, weld::TextView&, void ); + DECL_LINK( BtnHdl, weld::Button&, void ); + DECL_LINK( DblClkHdl, FuncPage&, void ); + DECL_LINK( FuncSelHdl, FuncPage&, void ); + DECL_LINK( StructSelHdl, StructPage&, void ); +public: + mutable uno::Reference< sheet::XFormulaOpCodeMapper> m_xOpCodeMapper; + uno::Sequence< sheet::FormulaToken > m_aTokenList; + ::std::unique_ptr<FormulaTokenArray> m_pTokenArray; + ::std::optional<FormulaTokenArrayPlainIterator> m_oTokenArrayIterator; + mutable uno::Sequence< sheet::FormulaOpCodeMapEntry > m_aSpecialOpCodes; + mutable uno::Sequence< sheet::FormulaToken > m_aSeparatorsOpCodes; + mutable uno::Sequence< sheet::FormulaOpCodeMapEntry > m_aFunctionOpCodes; + mutable const sheet::FormulaOpCodeMapEntry* m_pFunctionOpCodesEnd; + ::std::map<const FormulaToken*, sheet::FormulaToken> m_aTokenMap; + IFormulaEditorHelper* m_pHelper; + weld::Dialog& m_rDialog; + + OUString m_aOldFormula; + bool m_bStructUpdate; + bool m_bUserMatrixFlag; + + const OUString m_aTitle1; + const OUString m_aTitle2; + FormulaHelper m_aFormulaHelper; + + OUString m_aEditHelpId; + + OUString m_aOldHelp; + bool m_bMakingTree; // in method of constructing tree + + bool m_bEditFlag; + const IFunctionDescription* m_pFuncDesc; + sal_Int32 m_nArgs; + ::std::vector< OUString > m_aArguments; + Selection m_aFuncSel; + + sal_Int32 m_nFuncExpStart; ///< current formula position for treeview results + + int m_nSelectionStart; + int m_nSelectionEnd; + + RefEdit* m_pTheRefEdit; + RefButton* m_pTheRefButton; + + std::unique_ptr<weld::Notebook> m_xTabCtrl; + std::unique_ptr<weld::Container> m_xParaWinBox; + std::unique_ptr<ParaWin> m_xParaWin; + std::unique_ptr<weld::Label> m_xFtHeadLine; + std::unique_ptr<weld::Label> m_xFtFuncName; + std::unique_ptr<weld::Label> m_xFtFuncDesc; + + std::unique_ptr<weld::Label> m_xFtEditName; + + std::unique_ptr<weld::Label> m_xFtResult; + std::unique_ptr<weld::Entry> m_xWndResult; + + std::unique_ptr<weld::Label> m_xFtFormula; + std::unique_ptr<weld::TextView> m_xMEdit; + + std::unique_ptr<weld::CheckButton> m_xBtnMatrix; + std::unique_ptr<weld::Button> m_xBtnCancel; + + std::unique_ptr<weld::Button> m_xBtnBackward; + std::unique_ptr<weld::Button> m_xBtnForward; + std::unique_ptr<weld::Button> m_xBtnEnd; + + std::unique_ptr<weld::Label> m_xFtFormResult; + std::unique_ptr<weld::Entry> m_xWndFormResult; + + std::unique_ptr<RefEdit> m_xEdRef; + std::unique_ptr<RefButton> m_xRefBtn; + + std::unique_ptr<FuncPage> m_xFuncPage; + std::unique_ptr<StructPage> m_xStructPage; + + FormulaDlg_Impl(weld::Dialog& rDialog, + weld::Builder& rBuilder, + bool _bSupportFunctionResult, + bool _bSupportResult, + bool _bSupportMatrix, + IFormulaEditorHelper* _pHelper, + const IFunctionManager* _pFunctionMgr, + IControlReferenceHandler* _pDlg); + ~FormulaDlg_Impl(); +}; + +FormulaDlg_Impl::FormulaDlg_Impl(weld::Dialog& rDialog, + weld::Builder& rBuilder, + bool _bSupportFunctionResult, + bool _bSupportResult, + bool _bSupportMatrix, + IFormulaEditorHelper* _pHelper, + const IFunctionManager* _pFunctionMgr, + IControlReferenceHandler* _pDlg) + : m_pFunctionOpCodesEnd(nullptr) + , m_pHelper(_pHelper) + , m_rDialog(rDialog) + , m_bUserMatrixFlag(false) + , m_aTitle1( ForResId( STR_TITLE1 ) ) + , m_aTitle2( ForResId( STR_TITLE2 ) ) + , m_aFormulaHelper(_pFunctionMgr) + , m_bMakingTree(false) + , m_pFuncDesc(nullptr) + , m_nArgs(0) + , m_nFuncExpStart(0) + , m_nSelectionStart(-1) + , m_nSelectionEnd(-1) + , m_pTheRefEdit(nullptr) + , m_pTheRefButton(nullptr) + , m_xTabCtrl(rBuilder.weld_notebook("tabcontrol")) + , m_xParaWinBox(rBuilder.weld_container("BOX")) + , m_xFtHeadLine(rBuilder.weld_label("headline")) + , m_xFtFuncName(rBuilder.weld_label("funcname")) + , m_xFtFuncDesc(rBuilder.weld_label("funcdesc")) + , m_xFtEditName(rBuilder.weld_label("editname")) + , m_xFtResult(rBuilder.weld_label("label2")) + , m_xWndResult(rBuilder.weld_entry("result")) + , m_xFtFormula(rBuilder.weld_label("formula")) + , m_xMEdit(rBuilder.weld_text_view("ed_formula")) + , m_xBtnMatrix(rBuilder.weld_check_button("array")) + , m_xBtnCancel(rBuilder.weld_button("cancel")) + , m_xBtnBackward(rBuilder.weld_button("back")) + , m_xBtnForward(rBuilder.weld_button("next")) + , m_xBtnEnd(rBuilder.weld_button("ok")) + , m_xFtFormResult(rBuilder.weld_label("label1")) + , m_xWndFormResult(rBuilder.weld_entry("formula_result")) + , m_xEdRef(new RefEdit(rBuilder.weld_entry("ED_REF"))) + , m_xRefBtn(new RefButton(rBuilder.weld_button("RB_REF"))) +{ + auto nWidth = m_xMEdit->get_approximate_digit_width() * 62; + + //Space for two lines of text + m_xFtHeadLine->set_label("X\nX\n"); + auto nHeight = m_xFtHeadLine->get_preferred_size().Height(); + m_xFtHeadLine->set_size_request(nWidth, nHeight); + m_xFtHeadLine->set_label(""); + + m_xFtFuncName->set_label("X\nX\n"); + nHeight = m_xFtFuncName->get_preferred_size().Height(); + m_xFtFuncName->set_size_request(nWidth, nHeight); + m_xFtFuncDesc->set_size_request(nWidth, nHeight); + m_xFtFuncName->set_label(""); + + m_xMEdit->set_size_request(nWidth, + m_xMEdit->get_height_rows(5)); + + m_xEdRef->SetReferences(_pDlg, m_xFtEditName.get()); + m_xRefBtn->SetReferences(_pDlg, m_xEdRef.get()); + + m_xParaWin.reset(new ParaWin(m_xParaWinBox.get(), _pDlg)); + m_xParaWin->Show(); + m_xParaWinBox->hide(); + m_xFtEditName->hide(); + m_xEdRef->GetWidget()->hide(); + m_xRefBtn->GetWidget()->hide(); + + m_xMEdit->set_accessible_name(m_xFtFormula->get_label()); + + m_aEditHelpId = m_xMEdit->get_help_id(); + + m_bEditFlag =false; + m_bStructUpdate =true; + m_xParaWin->SetArgModifiedHdl( LINK( this, FormulaDlg_Impl, ModifyHdl ) ); + m_xParaWin->SetFxHdl( LINK( this, FormulaDlg_Impl, FxHdl ) ); + + m_xFuncPage.reset(new FuncPage(m_xTabCtrl->get_page("functiontab"), _pFunctionMgr)); + m_xStructPage.reset(new StructPage(m_xTabCtrl->get_page("structtab"))); + m_xTabCtrl->set_current_page("functiontab"); + + m_aOldHelp = m_rDialog.get_help_id(); // HelpId from resource always for "Page 1" + + m_xFtResult->set_visible( _bSupportResult ); + m_xWndResult->set_visible( _bSupportResult ); + + m_xFtFormResult->set_visible( _bSupportFunctionResult ); + m_xWndFormResult->set_visible( _bSupportFunctionResult ); + + if ( _bSupportMatrix ) + m_xBtnMatrix->connect_toggled( LINK( this, FormulaDlg_Impl, MatrixHdl ) ); + else + m_xBtnMatrix->hide(); + + m_xBtnCancel->connect_clicked( LINK( this, FormulaDlg_Impl, BtnHdl ) ); + m_xBtnEnd->connect_clicked( LINK( this, FormulaDlg_Impl, BtnHdl ) ); + m_xBtnForward->connect_clicked( LINK( this, FormulaDlg_Impl, BtnHdl ) ); + m_xBtnBackward->connect_clicked( LINK( this, FormulaDlg_Impl, BtnHdl ) ); + + m_xFuncPage->SetDoubleClickHdl( LINK( this, FormulaDlg_Impl, DblClkHdl ) ); + m_xFuncPage->SetSelectHdl( LINK( this, FormulaDlg_Impl, FuncSelHdl) ); + m_xStructPage->SetSelectionHdl( LINK( this, FormulaDlg_Impl, StructSelHdl ) ); + m_xMEdit->connect_changed( LINK( this, FormulaDlg_Impl, FormulaHdl ) ); + m_xMEdit->connect_cursor_position( LINK( this, FormulaDlg_Impl, FormulaCursorHdl ) ); + + vcl::Font aFntLight = m_xFtFormula->get_font(); + vcl::Font aFntBold = aFntLight; + aFntBold.SetWeight( WEIGHT_BOLD ); + + m_xParaWin->SetArgumentFonts( aFntBold, aFntLight); +} + +FormulaDlg_Impl::~FormulaDlg_Impl() +{ + m_xTabCtrl->remove_page("functiontab"); + m_xTabCtrl->remove_page("structtab"); + + DeleteArgs(); +} + +void FormulaDlg_Impl::StoreFormEditData(FormEditData* pData) +{ + if (!pData) // it won't be destroyed via Close + return; + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + pData->SetFStart(nStartPos); + pData->SetSelection(Selection(nStartPos, nEndPos)); + + if (m_xTabCtrl->get_current_page_ident() == "functiontab") + pData->SetMode( FormulaDlgMode::Formula ); + else + pData->SetMode( FormulaDlgMode::Edit ); + pData->SetUndoStr(m_xMEdit->get_text()); + pData->SetMatrixFlag(m_xBtnMatrix->get_active()); +} + +void FormulaDlg_Impl::InitFormulaOpCodeMapper() +{ + if ( m_xOpCodeMapper.is() ) + return; + + m_xOpCodeMapper = m_pHelper->getFormulaOpCodeMapper(); + m_aFunctionOpCodes = m_xOpCodeMapper->getAvailableMappings( sheet::FormulaLanguage::ODFF, sheet::FormulaMapGroup::FUNCTIONS); + m_pFunctionOpCodesEnd = m_aFunctionOpCodes.getConstArray() + m_aFunctionOpCodes.getLength(); + + // 0:TOKEN_OPEN, 1:TOKEN_CLOSE, 2:TOKEN_SEP + uno::Sequence< OUString > aArgs { "(", ")", ";" }; + m_aSeparatorsOpCodes = m_xOpCodeMapper->getMappings( aArgs, sheet::FormulaLanguage::ODFF); + + m_aSpecialOpCodes = m_xOpCodeMapper->getAvailableMappings( sheet::FormulaLanguage::ODFF, sheet::FormulaMapGroup::SPECIAL); +} + +void FormulaDlg_Impl::DeleteArgs() +{ + ::std::vector< OUString>().swap(m_aArguments); + m_nArgs = 0; +} + +sal_Int32 FormulaDlg_Impl::GetFunctionPos(sal_Int32 nPos) +{ + if ( !m_aTokenList.hasElements() ) + return SAL_MAX_INT32; + + const sal_Unicode sep = m_pHelper->getFunctionManager()->getSingleToken(IFunctionManager::eSep); + + sal_Int32 nFuncPos = SAL_MAX_INT32; + OUString aFormString = m_aFormulaHelper.GetCharClass().uppercase(m_xMEdit->get_text()); + + const uno::Reference< sheet::XFormulaParser > xParser(m_pHelper->getFormulaParser()); + const table::CellAddress aRefPos(m_pHelper->getReferencePosition()); + + const sheet::FormulaToken* pIter = m_aTokenList.getConstArray(); + const sheet::FormulaToken* pEnd = pIter + m_aTokenList.getLength(); + try + { + bool bFlag = false; + sal_Int32 nTokPos = 1; + sal_Int32 nOldTokPos = 1; + sal_Int32 nPrevFuncPos = 1; + short nBracketCount = 0; + const sal_Int32 nOpPush = m_aSpecialOpCodes[sheet::FormulaMapGroupSpecialOffset::PUSH].Token.OpCode; + const sal_Int32 nOpSpaces = m_aSpecialOpCodes[sheet::FormulaMapGroupSpecialOffset::SPACES].Token.OpCode; + const sal_Int32 nOpWhitespace = m_aSpecialOpCodes[sheet::FormulaMapGroupSpecialOffset::WHITESPACE].Token.OpCode; + while ( pIter != pEnd ) + { + const sal_Int32 eOp = pIter->OpCode; + uno::Sequence<sheet::FormulaToken> aArgs { *pIter }; + const OUString aString = xParser->printFormula( aArgs, aRefPos); + const sheet::FormulaToken* pNextToken = pIter + 1; + + if ( !m_bUserMatrixFlag && FormulaCompiler::IsMatrixFunction(static_cast<OpCode>(eOp)) ) + { + m_xBtnMatrix->set_active(true); + } + + if (eOp == nOpPush || eOp == nOpSpaces || eOp == nOpWhitespace) + { + const sal_Int32 n1 = nTokPos < 0 ? -1 : aFormString.indexOf( sep, nTokPos); + const sal_Int32 n2 = nTokPos < 0 ? -1 : aFormString.indexOf( ')', nTokPos); + sal_Int32 nXXX = nTokPos; + if ( n1 < n2 && n1 != -1 ) + { + nTokPos = n1; + } + else + { + nTokPos = n2; + } + if ( pNextToken != pEnd ) + { + aArgs.getArray()[0] = *pNextToken; + const OUString a2String = xParser->printFormula( aArgs, aRefPos); + const sal_Int32 n3 = nXXX < 0 ? -1 : aFormString.indexOf( a2String, nXXX); + if (n3 < nTokPos && n3 != -1) + nTokPos = n3; + } + } + else + { + nTokPos = nTokPos + aString.getLength(); + } + + if ( eOp == m_aSeparatorsOpCodes[TOKEN_OPEN].OpCode ) + { + nBracketCount++; + bFlag = true; + } + else if ( eOp == m_aSeparatorsOpCodes[TOKEN_CLOSE].OpCode ) + { + nBracketCount--; + bFlag = false; + nFuncPos = nPrevFuncPos; + } + bool bIsFunction = std::any_of( m_aFunctionOpCodes.getConstArray(), + m_pFunctionOpCodesEnd, + [&eOp](const sheet::FormulaOpCodeMapEntry& aEntry) { return aEntry.Token.OpCode == eOp; }); + + if ( bIsFunction && nOpSpaces != eOp && nOpWhitespace != eOp ) + { + nPrevFuncPos = nFuncPos; + nFuncPos = nOldTokPos; + } + + if ( nOldTokPos <= nPos && nPos < nTokPos ) + { + if ( !bIsFunction ) + { + if ( nBracketCount < 1 ) + { + nFuncPos = m_xMEdit->get_text().getLength(); + } + else if ( !bFlag ) + { + nFuncPos = nPrevFuncPos; + } + } + break; + } + + pIter = pNextToken; + nOldTokPos = nTokPos; + } // while ( pIter != pEnd ) + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("formula.ui", "FormulaDlg_Impl::GetFunctionPos"); + } + + return nFuncPos; +} + +bool FormulaDlg_Impl::CalcValue( const OUString& rStrExp, OUString& rStrResult, bool bForceMatrixFormula ) +{ + bool bResult = true; + + if ( !rStrExp.isEmpty() ) + { + // Only calculate the value when there isn't any more keyboard input: + + // Make this debuggable by assigning to a variable that can be changed + // from within the debugger. + bool bInput = Application::AnyInput( VclInputFlags::KEYBOARD ); + if ( !bInput ) + { + bResult = m_pHelper->calculateValue( rStrExp, rStrResult, bForceMatrixFormula || m_xBtnMatrix->get_active()); + } + else + bResult = false; + } + + return bResult; +} + +void FormulaDlg_Impl::UpdateValues( bool bForceRecalcStruct ) +{ + // Take a force-array context into account. RPN creation propagated those + // to tokens that are ref-counted so also available in the token array. + bool bForceArray = false; + // Only necessary if it's not a matrix formula anyway and matrix evaluation + // is supported, i.e. the button is visible. + if (m_xBtnMatrix->get_visible() && !m_xBtnMatrix->get_active()) + { + std::unique_ptr<FormulaCompiler> pCompiler(m_pHelper->createCompiler(*m_pTokenArray)); + // In the case of the reportdesign dialog there is no currently active + // OpCode symbol mapping that could be used to create strings from + // tokens, it's all dreaded API mapping. However, in that case there's + // no array/matrix support anyway, but ensure checking. + if (pCompiler->GetCurrentOpCodeMap()) + { + const sal_Int32 nPos = m_aFuncSel.Min(); + assert( 0 <= nPos && nPos < m_pHelper->getCurrentFormula().getLength()); + OUStringBuffer aBuf; + const FormulaToken* pToken = nullptr; + for (pToken = m_oTokenArrayIterator->First(); pToken; pToken = m_oTokenArrayIterator->Next()) + { + pCompiler->CreateStringFromToken( aBuf, pToken); + if (nPos < aBuf.getLength()) + break; + } + if (pToken && nPos < aBuf.getLength()) + bForceArray = pToken->IsInForceArray(); + } + } + + OUString aStrResult; + if (m_pFuncDesc && CalcValue( m_pFuncDesc->getFormula( m_aArguments), aStrResult, bForceArray)) + m_xWndResult->set_text( aStrResult ); + + if (m_bMakingTree) + return; + + aStrResult.clear(); + if ( CalcValue( m_pHelper->getCurrentFormula(), aStrResult ) ) + m_xWndFormResult->set_text( aStrResult ); + else + { + aStrResult.clear(); + m_xWndFormResult->set_text( aStrResult ); + } + CalcStruct( m_xMEdit->get_text(), bForceRecalcStruct); +} + +void FormulaDlg_Impl::CalcStruct( const OUString& rStrExp, bool bForceRecalcStruct ) +{ + sal_Int32 nLength = rStrExp.getLength(); + + if ( !(!rStrExp.isEmpty() && (bForceRecalcStruct || m_aOldFormula != rStrExp) && m_bStructUpdate)) + return; + + m_xStructPage->ClearStruct(); + + OUString aString = rStrExp; + if (rStrExp[nLength-1] == '(') + { + aString = aString.copy( 0, nLength-1); + } + + aString = aString.replaceAll( "\n", ""); + OUString aStrResult; + + if ( CalcValue( aString, aStrResult ) ) + m_xWndFormResult->set_text(aStrResult); + + UpdateTokenArray(aString); + fillTree(m_xStructPage.get()); + + m_aOldFormula = rStrExp; + if (rStrExp[nLength-1] == '(') + UpdateTokenArray(rStrExp); +} + +void FormulaDlg_Impl::MakeTree(StructPage* _pTree, weld::TreeIter* pParent, const FormulaToken* pFuncToken, + const FormulaToken* _pToken, tools::Long Count) +{ + if ( _pToken == nullptr || Count <= 0 ) + return; + + tools::Long nParas = _pToken->GetParamCount(); + OpCode eOp = _pToken->GetOpCode(); + + // #i101512# for output, the original token is needed + const FormulaToken* pOrigToken = (_pToken->GetType() == svFAP) ? _pToken->GetFAPOrigToken() : _pToken; + ::std::map<const FormulaToken*, sheet::FormulaToken>::const_iterator itr = m_aTokenMap.find(pOrigToken); + if (itr == m_aTokenMap.end()) + return; + + uno::Sequence<sheet::FormulaToken> aArgs { itr->second }; + try + { + const table::CellAddress aRefPos(m_pHelper->getReferencePosition()); + const OUString aResult = m_pHelper->getFormulaParser()->printFormula( aArgs, aRefPos); + + if ( nParas > 0 || (nParas == 0 && _pToken->IsFunction()) ) + { + std::unique_ptr<weld::TreeIter> xEntry; + weld::TreeIter* pEntry; + + bool bCalcSubformula = false; + OUString aTest = _pTree->GetEntryText(pParent); + + if (aTest == aResult && (eOp == ocAdd || eOp == ocMul || eOp == ocAmpersand)) + { + pEntry = pParent; + } + else + { + xEntry = m_xStructPage->GetTlbStruct().make_iterator(); + + if (eOp == ocBad) + { + _pTree->InsertEntry(aResult, pParent, STRUCT_ERROR, 0, _pToken, *xEntry); + } + else if (!((SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP) || + (SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP))) + { + // Not a binary or unary operator. + bCalcSubformula = true; + _pTree->InsertEntry(aResult, pParent, STRUCT_FOLDER, 0, _pToken, *xEntry); + } + else + { + /* TODO: question remains, why not sub calculate operators? */ + _pTree->InsertEntry(aResult, pParent, STRUCT_FOLDER, 0, _pToken, *xEntry); + } + + pEntry = xEntry.get(); + } + + MakeTree(_pTree, pEntry, _pToken, m_oTokenArrayIterator->PrevRPN(), nParas); + + if (bCalcSubformula) + { + OUString aFormula; + + if (!m_bMakingTree) + { + // gets the last subformula result + m_bMakingTree = true; + aFormula = GetPrevFuncExpression( true); + } + else + { + // gets subsequent subformula results (from the back) + aFormula = GetPrevFuncExpression( false); + } + + OUString aStr; + if (CalcValue( aFormula, aStr, _pToken->IsInForceArray())) + m_xWndResult->set_text( aStr ); + aStr = m_xWndResult->get_text(); + m_xStructPage->GetTlbStruct().set_text(*pEntry, aResult + " = " + aStr); + } + + --Count; + m_oTokenArrayIterator->NextRPN(); /* TODO: what's this to be? ThisRPN()? */ + MakeTree( _pTree, pParent, _pToken, m_oTokenArrayIterator->PrevRPN(), Count); + } + else + { + std::unique_ptr<weld::TreeIter> xEntry(m_xStructPage->GetTlbStruct().make_iterator()); + if (eOp == ocBad) + { + _pTree->InsertEntry( aResult, pParent, STRUCT_ERROR, 0, _pToken, *xEntry); + } + else if (eOp == ocPush) + { + // Interpret range reference in matrix context to resolve + // as array elements. Depending on parameter classification + // a scalar value (non-array context) is calculated first. + OUString aUnforcedResult; + bool bForceMatrix = (!m_xBtnMatrix->get_active() && + (_pToken->GetType() == svDoubleRef || _pToken->GetType() == svExternalDoubleRef)); + if (bForceMatrix && pFuncToken) + { + formula::ParamClass eParamClass = ParamClass::Reference; + if (pFuncToken->IsInForceArray()) + eParamClass = ParamClass::ForceArray; + else + { + std::shared_ptr<FormulaCompiler> pCompiler = m_pHelper->getCompiler(); + if (pCompiler) + eParamClass = pCompiler->GetForceArrayParameter( pFuncToken, Count - 1); + } + switch (eParamClass) + { + case ParamClass::Unknown: + case ParamClass::Bounds: + case ParamClass::Value: + if (CalcValue( "=" + aResult, aUnforcedResult, false) && aUnforcedResult != aResult) + aUnforcedResult += " "; + else + aUnforcedResult.clear(); + break; + case ParamClass::Reference: + case ParamClass::ReferenceOrRefArray: + case ParamClass::Array: + case ParamClass::ForceArray: + case ParamClass::ReferenceOrForceArray: + case ParamClass::SuppressedReferenceOrForceArray: + case ParamClass::ForceArrayReturn: + ; // nothing, only as array/matrix + // no default to get compiler warning + } + } + OUString aCellResult; + if (CalcValue( "=" + aResult, aCellResult, bForceMatrix) && aCellResult != aResult) + { + // Cell is a formula, print subformula. + // With scalar values prints "A1:A3 = 2 {1;2;3}" + _pTree->InsertEntry( aResult + " = " + aUnforcedResult + aCellResult, + pParent, STRUCT_END, 0, _pToken, *xEntry); + } + else + _pTree->InsertEntry(aResult, pParent, STRUCT_END, 0, _pToken, *xEntry); + } + else + { + _pTree->InsertEntry(aResult, pParent, STRUCT_END, 0, _pToken, *xEntry); + } + --Count; + MakeTree( _pTree, pParent, _pToken, m_oTokenArrayIterator->PrevRPN(), Count); + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("formula.ui"); + } +} + +void FormulaDlg_Impl::fillTree(StructPage* _pTree) +{ + InitFormulaOpCodeMapper(); + FormulaToken* pToken = m_oTokenArrayIterator->LastRPN(); + + if ( pToken != nullptr) + { + MakeTree( _pTree, nullptr, nullptr, pToken, 1); + m_bMakingTree = false; + } +} + +void FormulaDlg_Impl::UpdateTokenArray( const OUString& rStrExp) +{ + m_aTokenMap.clear(); + m_aTokenList.realloc(0); + try + { + const table::CellAddress aRefPos(m_pHelper->getReferencePosition()); + m_aTokenList = m_pHelper->getFormulaParser()->parseFormula( rStrExp, aRefPos); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("formula.ui"); + } + InitFormulaOpCodeMapper(); + m_pTokenArray = m_pHelper->convertToTokenArray(m_aTokenList); + m_oTokenArrayIterator.emplace(*m_pTokenArray); + const sal_Int32 nLen = static_cast<sal_Int32>(m_pTokenArray->GetLen()); + FormulaToken** pTokens = m_pTokenArray->GetArray(); + if ( pTokens && nLen == m_aTokenList.getLength() ) + { + for (sal_Int32 nPos = 0; nPos < nLen; nPos++) + { + m_aTokenMap.emplace( pTokens[nPos], m_aTokenList[nPos] ); + } + } // if ( pTokens && nLen == m_aTokenList.getLength() ) + + std::unique_ptr<FormulaCompiler> pCompiler(m_pHelper->createCompiler(*m_pTokenArray)); + // #i101512# Disable special handling of jump commands. + pCompiler->EnableJumpCommandReorder(false); + pCompiler->EnableStopOnError(false); + pCompiler->SetComputeIIFlag(true); + pCompiler->SetMatrixFlag(m_bUserMatrixFlag); + pCompiler->CompileTokenArray(); +} + +void FormulaDlg_Impl::FillDialog(bool bFlag) +{ + bool bNext = true, bPrev = true; + if (bFlag) + FillControls( bNext, bPrev); + FillListboxes(); + if (bFlag) + { + m_xBtnBackward->set_sensitive(bPrev); + m_xBtnForward->set_sensitive(bNext); + } + + OUString aStrResult; + + if ( CalcValue( m_pHelper->getCurrentFormula(), aStrResult ) ) + m_xWndFormResult->set_text( aStrResult ); + else + { + aStrResult.clear(); + m_xWndFormResult->set_text( aStrResult ); + } +} + +void FormulaDlg_Impl::FillListboxes() +{ + // Switch between the "Pages" + FormEditData* pData = m_pHelper->getFormEditData(); + // 1. Page: select function + if ( m_pFuncDesc && m_pFuncDesc->getCategory() ) + { + // We'll never have more than int32 max categories so this is safe ... + // Category listbox holds additional entries for Last Used and All, so + // the offset should be two but hard coded numbers are ugly... + const sal_Int32 nCategoryOffset = m_xFuncPage->GetCategoryEntryCount() - m_aFormulaHelper.GetCategoryCount(); + if ( m_xFuncPage->GetCategory() != static_cast<sal_Int32>(m_pFuncDesc->getCategory()->getNumber() + nCategoryOffset) ) + m_xFuncPage->SetCategory(m_pFuncDesc->getCategory()->getNumber() + nCategoryOffset); + + sal_Int32 nPos = m_xFuncPage->GetFuncPos(m_pFuncDesc); + + m_xFuncPage->SetFunction(nPos); + } + else if ( pData ) + { + // tdf#104487 - remember last used function category + m_xFuncPage->SetCategory(FuncPage::GetRememeberdFunctionCategory()); + m_xFuncPage->SetFunction( -1 ); + } + FuncSelHdl(*m_xFuncPage); + + m_pHelper->setDispatcherLock( true ); // Activate Modal-Mode + + // HelpId for 1. page is the one from the resource + m_rDialog.set_help_id( m_aOldHelp ); +} + +void FormulaDlg_Impl::FillControls( bool &rbNext, bool &rbPrev) +{ + // Switch between the "Pages" + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData ) + return; + + // 2. Page or Edit: show selected function + + sal_Int32 nFStart = pData->GetFStart(); + OUString aFormula = m_pHelper->getCurrentFormula() + " )"; + sal_Int32 nNextFStart = nFStart; + sal_Int32 nNextFEnd = 0; + + DeleteArgs(); + const IFunctionDescription* pOldFuncDesc = m_pFuncDesc; + + if ( m_aFormulaHelper.GetNextFunc( aFormula, false, + nNextFStart, &nNextFEnd, &m_pFuncDesc, &m_aArguments ) ) + { + const bool bTestFlag = (pOldFuncDesc != m_pFuncDesc); + if (bTestFlag) + { + m_xFtHeadLine->hide(); + m_xFtFuncName->hide(); + m_xFtFuncDesc->hide(); + m_xParaWin->SetFunctionDesc(m_pFuncDesc); + m_xFtEditName->set_label( m_pFuncDesc->getFunctionName() ); + m_xFtEditName->show(); + m_xParaWinBox->show(); + const OUString aHelpId = m_pFuncDesc->getHelpId(); + if ( !aHelpId.isEmpty() ) + m_xMEdit->set_help_id(aHelpId); + } + + sal_Int32 nOldStart, nOldEnd; + m_pHelper->getSelection( nOldStart, nOldEnd ); + if ( nOldStart != nNextFStart || nOldEnd != nNextFEnd ) + { + m_pHelper->setSelection( nNextFStart, nNextFEnd ); + } + m_aFuncSel.Min() = nNextFStart; + m_aFuncSel.Max() = nNextFEnd; + + if (!m_bEditFlag) + m_xMEdit->set_text(m_pHelper->getCurrentFormula()); + sal_Int32 PrivStart, PrivEnd; + m_pHelper->getSelection( PrivStart, PrivEnd); + if (!m_bEditFlag) + m_xMEdit->select_region(PrivStart, PrivEnd); + + m_nArgs = m_pFuncDesc->getSuppressedArgumentCount(); + sal_uInt16 nOffset = pData->GetOffset(); + + // Concatenate the Edit's for Focus-Control + + if (bTestFlag) + m_xParaWin->SetArgumentOffset(nOffset); + sal_uInt16 nActiv = 0; + sal_Int32 nArgPos = m_aFormulaHelper.GetArgStart( aFormula, nFStart, 0 ); + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + sal_Int32 nEditPos = nStartPos; + bool bFlag = false; + + for (sal_Int32 i = 0; i < m_nArgs; i++) + { + sal_Int32 nLength = m_aArguments[i].getLength()+1; + m_xParaWin->SetArgument( i, m_aArguments[i]); + if (nArgPos <= nEditPos && nEditPos < nArgPos+nLength) + { + nActiv = i; + bFlag = true; + } + nArgPos = nArgPos + nLength; + } + m_xParaWin->UpdateParas(); + + if (bFlag) + { + m_xParaWin->SetActiveLine(nActiv); + } + + UpdateValues(); + } + else + { + m_xFtEditName->set_label(""); + m_xMEdit->set_help_id(m_aEditHelpId); + } + // test if before/after are anymore functions + + sal_Int32 nTempStart = m_aFormulaHelper.GetArgStart( aFormula, nFStart, 0 ); + rbNext = m_aFormulaHelper.GetNextFunc( aFormula, false, nTempStart ); + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + nTempStart = nStartPos; + pData->SetFStart(nTempStart); + rbPrev = m_aFormulaHelper.GetNextFunc( aFormula, true, nTempStart ); +} + + +void FormulaDlg_Impl::ClearAllParas() +{ + DeleteArgs(); + m_pFuncDesc = nullptr; + m_xParaWin->ClearAll(); + m_xWndResult->set_text(OUString()); + m_xFtFuncName->set_label(OUString()); + FuncSelHdl(*m_xFuncPage); + + if (m_xFuncPage->IsVisible()) + { + m_xFtEditName->hide(); + m_xParaWinBox->hide(); + + m_xBtnForward->set_sensitive(true); //@new + m_xFtHeadLine->show(); + m_xFtFuncName->show(); + m_xFtFuncDesc->show(); + } +} + +OUString FormulaDlg_Impl::RepairFormula(const OUString& aFormula) +{ + OUString aResult('='); + try + { + UpdateTokenArray(aFormula); + + if ( m_aTokenList.hasElements() ) + { + const table::CellAddress aRefPos(m_pHelper->getReferencePosition()); + const OUString sFormula( m_pHelper->getFormulaParser()->printFormula( m_aTokenList, aRefPos)); + if ( sFormula.isEmpty() || sFormula[0] != '=' ) + aResult += sFormula; + else + aResult = sFormula; + + } + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("formula.ui", "FormulaDlg_Impl::RepairFormula"); + } + return aResult; +} + +void FormulaDlg_Impl::DoEnter(bool bOk) +{ + // Accept input to the document or cancel + if ( bOk) + { + // remove dummy arguments + OUString aInputFormula = m_pHelper->getCurrentFormula(); + OUString aString = RepairFormula(m_xMEdit->get_text()); + m_pHelper->setSelection( 0, aInputFormula.getLength()); + m_pHelper->setCurrentFormula(aString); + } + + m_pHelper->switchBack(); + + m_pHelper->dispatch( bOk, m_xBtnMatrix->get_active()); + // Clear data + m_pHelper->deleteFormData(); + + // Close dialog + m_pHelper->doClose(bOk); +} + + +IMPL_LINK(FormulaDlg_Impl, BtnHdl, weld::Button&, rBtn, void) +{ + if (&rBtn == m_xBtnCancel.get()) + { + DoEnter(false); // closes the Dialog + } + else if (&rBtn == m_xBtnEnd.get()) + { + DoEnter(true); // closes the Dialog + } + else if (&rBtn == m_xBtnForward.get()) + { + const IFunctionDescription* pDesc; + sal_Int32 nSelFunc = m_xFuncPage->GetFunction(); + if (nSelFunc != -1) + pDesc = m_xFuncPage->GetFuncDesc( nSelFunc ); + else + { + // Do not overwrite the selected formula expression, just edit the + // unlisted function. + m_pFuncDesc = pDesc = nullptr; + } + + if (pDesc == m_pFuncDesc || !m_xFuncPage->IsVisible()) + EditNextFunc( true ); + else + { + DblClkHdl(*m_xFuncPage); //new + m_xBtnForward->set_sensitive(false); //new + } + } + else if (&rBtn == m_xBtnBackward.get()) + { + m_bEditFlag = false; + m_xBtnForward->set_sensitive(true); + EditNextFunc( false ); + } +} + +// Functions for 1. Page + +// Handler for Listboxes + +IMPL_LINK_NOARG( FormulaDlg_Impl, DblClkHdl, FuncPage&, void) +{ + sal_Int32 nFunc = m_xFuncPage->GetFunction(); + + // ex-UpdateLRUList + const IFunctionDescription* pDesc = m_xFuncPage->GetFuncDesc(nFunc); + m_pHelper->insertEntryToLRUList(pDesc); + + OUString aFuncName = m_xFuncPage->GetSelFunctionName() + "()"; + m_pHelper->setCurrentFormula(aFuncName); + m_xMEdit->replace_selection(aFuncName); + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + nEndPos = nEndPos - 1; + m_xMEdit->select_region(nStartPos, nEndPos); + + FormulaHdl(*m_xMEdit); + + nStartPos = nEndPos; + m_xMEdit->select_region(nStartPos, nEndPos); + + if (m_nArgs == 0) + { + BtnHdl(*m_xBtnBackward); + } + + m_xParaWin->SetEdFocus(); + m_xBtnForward->set_sensitive(false); //@New +} + +// Functions for right Page + +void FormulaDlg_Impl::SetData( sal_Int32 nFStart, sal_Int32 nNextFStart, sal_Int32 nNextFEnd, sal_Int32& PrivStart, sal_Int32& PrivEnd) +{ + sal_Int32 nFEnd; + + // Notice and set new selection + m_pHelper->getSelection( nFStart, nFEnd ); + m_pHelper->setSelection( nNextFStart, nNextFEnd ); + if (!m_bEditFlag) + m_xMEdit->set_text(m_pHelper->getCurrentFormula()); + + + m_pHelper->getSelection( PrivStart, PrivEnd); + if (!m_bEditFlag) + { + m_xMEdit->select_region(PrivStart, PrivEnd); + UpdateOldSel(); + } + + FormEditData* pData = m_pHelper->getFormEditData(); + pData->SetFStart( nNextFStart ); + pData->SetOffset( 0 ); + + FillDialog(); +} + +void FormulaDlg_Impl::EditThisFunc(sal_Int32 nFStart) +{ + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData) + return; + + OUString aFormula = m_pHelper->getCurrentFormula(); + + if (nFStart == NOT_FOUND) + { + nFStart = pData->GetFStart(); + } + else + { + pData->SetFStart(nFStart); + } + + sal_Int32 nNextFStart = nFStart; + sal_Int32 nNextFEnd = 0; + + bool bFound; + + bFound = m_aFormulaHelper.GetNextFunc( aFormula, false, nNextFStart, &nNextFEnd); + if ( bFound ) + { + sal_Int32 PrivStart, PrivEnd; + SetData( nFStart, nNextFStart, nNextFEnd, PrivStart, PrivEnd); + m_pHelper->showReference( aFormula.copy( PrivStart, PrivEnd-PrivStart)); + } + else + { + ClearAllParas(); + } +} + +bool FormulaDlg_Impl::EditNextFunc( bool bForward, sal_Int32 nFStart ) +{ + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData) + return false; + + OUString aFormula = m_pHelper->getCurrentFormula(); + + if (nFStart == NOT_FOUND) + { + nFStart = pData->GetFStart(); + } + else + { + pData->SetFStart(nFStart); + } + + sal_Int32 nNextFStart = 0; + sal_Int32 nNextFEnd = 0; + + bool bFound; + if ( bForward ) + { + nNextFStart = m_aFormulaHelper.GetArgStart( aFormula, nFStart, 0 ); + bFound = m_aFormulaHelper.GetNextFunc( aFormula, false, nNextFStart, &nNextFEnd); + } + else + { + nNextFStart = nFStart; + bFound = m_aFormulaHelper.GetNextFunc( aFormula, true, nNextFStart, &nNextFEnd); + } + + if ( bFound ) + { + sal_Int32 PrivStart, PrivEnd; + SetData( nFStart, nNextFStart, nNextFEnd, PrivStart, PrivEnd); + } + + return bFound; +} + +OUString FormulaDlg_Impl::GetPrevFuncExpression( bool bStartFromEnd ) +{ + OUString aExpression; + + OUString aFormula( m_pHelper->getCurrentFormula()); + if (aFormula.isEmpty()) + return aExpression; + + if (bStartFromEnd || m_nFuncExpStart >= aFormula.getLength()) + m_nFuncExpStart = aFormula.getLength() - 1; + + sal_Int32 nFStart = m_nFuncExpStart; + sal_Int32 nFEnd = 0; + if (m_aFormulaHelper.GetNextFunc( aFormula, true, nFStart, &nFEnd)) + { + aExpression = aFormula.copy( nFStart, nFEnd - nFStart); // nFEnd is exclusive + m_nFuncExpStart = nFStart; + } + + return aExpression; +} + +void FormulaDlg_Impl::SaveArg( sal_uInt16 nEd ) +{ + if (nEd >= m_nArgs) + return; + + for (sal_uInt16 i = 0; i <= nEd; i++) + { + if ( m_aArguments[i].isEmpty() ) + m_aArguments[i] = " "; + } + if (!m_xParaWin->GetArgument(nEd).isEmpty()) + m_aArguments[nEd] = m_xParaWin->GetArgument(nEd); + + sal_uInt16 nClearPos = nEd+1; + for (sal_Int32 i = nEd+1; i < m_nArgs; i++) + { + if ( !m_xParaWin->GetArgument(i).isEmpty() ) + { + nClearPos = i+1; + } + } + + for (sal_Int32 i = nClearPos; i < m_nArgs; i++) + { + m_aArguments[i].clear(); + } +} + +IMPL_LINK( FormulaDlg_Impl, FxHdl, ParaWin&, rPtr, void ) +{ + if (&rPtr != m_xParaWin.get()) + return; + + m_xBtnForward->set_sensitive(true); //@ In order to be able to input another function. + m_xTabCtrl->set_current_page("functiontab"); + + OUString aUndoStr = m_pHelper->getCurrentFormula(); // it will be added before a ";" + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData) + return; + + sal_uInt16 nArgNo = m_xParaWin->GetActiveLine(); + sal_uInt16 nEdFocus = nArgNo; + + SaveArg(nArgNo); + UpdateSelection(); + + sal_Int32 nFormulaStrPos = pData->GetFStart(); + + OUString aFormula = m_pHelper->getCurrentFormula(); + sal_Int32 n1 = m_aFormulaHelper.GetArgStart( aFormula, nFormulaStrPos, nEdFocus + pData->GetOffset() ); + + pData->SaveValues(); + pData->SetMode( FormulaDlgMode::Formula ); + pData->SetFStart( n1 ); + pData->SetUndoStr( aUndoStr ); + ClearAllParas(); + + FillDialog(false); + m_xFuncPage->SetFocus(); //There Parawin is not visible anymore +} + +IMPL_LINK( FormulaDlg_Impl, ModifyHdl, ParaWin&, rPtr, void ) +{ + if (&rPtr == m_xParaWin.get()) + { + SaveArg(m_xParaWin->GetActiveLine()); + UpdateValues(); + + UpdateSelection(); + CalcStruct(m_xMEdit->get_text()); + } +} + +IMPL_LINK_NOARG( FormulaDlg_Impl, FormulaHdl, weld::TextView&, void) +{ + + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData) + return; + + m_bEditFlag = true; + OUString aInputFormula = m_pHelper->getCurrentFormula(); + OUString aString = m_xMEdit->get_text(); + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + if (aString.isEmpty()) // in case everything was cleared + { + aString += "="; + m_xMEdit->set_text(aString); + nStartPos = 1; + nEndPos = 1; + m_xMEdit->select_region(nStartPos, nEndPos); + } + else if (aString[0]!='=') // in case it's replaced + { + aString = "=" + aString; + m_xMEdit->set_text(aString); + nStartPos += 1; + nEndPos += 1; + m_xMEdit->select_region(nStartPos, nEndPos); + } + + m_pHelper->setSelection( 0, aInputFormula.getLength()); + m_pHelper->setCurrentFormula(aString); + m_pHelper->setSelection(nStartPos, nEndPos); + + sal_Int32 nPos = nStartPos - 1; + + OUString aStrResult; + + if ( CalcValue( m_pHelper->getCurrentFormula(), aStrResult ) ) + m_xWndFormResult->set_text( aStrResult ); + else + { + aStrResult.clear(); + m_xWndFormResult->set_text( aStrResult ); + } + CalcStruct(aString); + + nPos = GetFunctionPos(nPos); + + if (nPos < nStartPos - 1) + { + sal_Int32 nPos1 = aString.indexOf( '(', nPos); + EditNextFunc( false, nPos1); + } + else + { + ClearAllParas(); + } + + m_pHelper->setSelection(nStartPos, nEndPos); + m_bEditFlag = false; +} + +void FormulaDlg_Impl::FormulaCursor() +{ + FormEditData* pData = m_pHelper->getFormEditData(); + if (!pData) + return; + + m_bEditFlag = true; + + OUString aString = m_xMEdit->get_text(); + + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + m_pHelper->setSelection(nStartPos, nEndPos); + + if (nStartPos == 0) + { + nStartPos = 1; + m_xMEdit->select_region(nStartPos, nEndPos); + } + if (nStartPos != aString.getLength()) + { + sal_Int32 nPos = nStartPos; + + sal_Int32 nFStart = GetFunctionPos(nPos - 1); + + if (nFStart < nPos) + { + sal_Int32 nPos1 = m_aFormulaHelper.GetFunctionEnd( aString, nFStart); + + if (nPos1 > nPos) + { + EditThisFunc(nFStart); + } + else + { + sal_Int32 n = nPos; + short nCount = 1; + while(n > 0) + { + if (aString[n]==')') + nCount++; + else if (aString[n]=='(') + nCount--; + if (nCount == 0) + break; + n--; + } + if (nCount == 0) + { + nFStart = m_aFormulaHelper.GetFunctionStart( aString, n, true); + EditThisFunc(nFStart); + } + else + { + ClearAllParas(); + } + } + } + else + { + ClearAllParas(); + } + } + m_pHelper->setSelection(nStartPos, nEndPos); + + m_bEditFlag = false; +} + +void FormulaDlg_Impl::UpdateOldSel() +{ + m_xMEdit->get_selection_bounds(m_nSelectionStart, m_nSelectionEnd); + if (m_nSelectionStart > m_nSelectionEnd) + std::swap(m_nSelectionStart, m_nSelectionEnd); +} + +IMPL_LINK_NOARG( FormulaDlg_Impl, FormulaCursorHdl, weld::TextView&, void) +{ + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + + if (nStartPos != m_nSelectionStart || nEndPos != m_nSelectionEnd) + { + m_nSelectionStart = nStartPos; + m_nSelectionEnd = nEndPos; + FormulaCursor(); + } +} + +void FormulaDlg_Impl::UpdateSelection() +{ + m_pHelper->setSelection( m_aFuncSel.Min(), m_aFuncSel.Max()); + if (m_pFuncDesc) + { + m_pHelper->setCurrentFormula( m_pFuncDesc->getFormula( m_aArguments ) ); + m_nArgs = m_pFuncDesc->getSuppressedArgumentCount(); + } + else + { + m_pHelper->setCurrentFormula(""); + m_nArgs = 0; + } + + m_xMEdit->set_text(m_pHelper->getCurrentFormula()); + sal_Int32 PrivStart, PrivEnd; + m_pHelper->getSelection( PrivStart, PrivEnd); + m_aFuncSel.Min() = PrivStart; + m_aFuncSel.Max() = PrivEnd; + + OUString aFormula = m_xMEdit->get_text(); + sal_Int32 nArgPos = m_aFormulaHelper.GetArgStart( aFormula, PrivStart, 0); + + sal_uInt16 nPos = m_xParaWin->GetActiveLine(); + if (nPos >= m_aArguments.size()) + { + SAL_WARN("formula.ui","FormulaDlg_Impl::UpdateSelection - shot in foot: nPos " << + nPos << " >= m_aArguments.size() " << m_aArguments.size() << + " for aFormula '" << aFormula << "'"); + nPos = m_aArguments.size(); + if (nPos) + --nPos; + } + + for (sal_uInt16 i = 0; i < nPos; i++) + { + nArgPos += (m_aArguments[i].getLength() + 1); + } + sal_Int32 nLength = (nPos < m_aArguments.size()) ? m_aArguments[nPos].getLength() : 0; + + m_pHelper->setSelection(nArgPos, nArgPos + nLength); + m_xMEdit->select_region(nArgPos, nArgPos + nLength); + UpdateOldSel(); +} + +::std::pair<RefButton*, RefEdit*> FormulaDlg_Impl::RefInputStartBefore(RefEdit* pEdit, RefButton* pButton) +{ + m_pTheRefEdit = pEdit; + m_pTheRefButton = pButton; + + Selection aOrigSelection; + if (m_pTheRefEdit) + { + // grab selection before showing next widget in case the selection is blown away + // by it appearing + aOrigSelection = m_pTheRefEdit->GetSelection(); + } + + // because its initially hidden, give it its optimal size so clicking the + // refbutton has an initial size to work when retro-fitting this to .ui + m_xEdRef->GetWidget()->set_size_request(m_xEdRef->GetWidget()->get_preferred_size().Width(), -1); + m_xEdRef->GetWidget()->show(); + + if ( m_pTheRefEdit ) + { + m_xEdRef->SetRefString(m_pTheRefEdit->GetText()); + m_xEdRef->SetSelection(aOrigSelection); + m_xEdRef->GetWidget()->set_help_id(m_pTheRefEdit->GetWidget()->get_help_id()); + } + + m_xRefBtn->GetWidget()->set_visible(pButton != nullptr); + + ::std::pair<RefButton*, RefEdit*> aPair; + aPair.first = pButton ? m_xRefBtn.get() : nullptr; + aPair.second = m_xEdRef.get(); + return aPair; +} + +void FormulaDlg_Impl::RefInputStartAfter() +{ + m_xRefBtn->SetEndImage(); + + if (!m_pTheRefEdit) + return; + + OUString aStr = m_aTitle2 + " " + m_xFtEditName->get_label() + "( "; + + if ( m_xParaWin->GetActiveLine() > 0 ) + aStr += "...; "; + aStr += m_xParaWin->GetActiveArgName(); + if ( m_xParaWin->GetActiveLine() + 1 < m_nArgs ) + aStr += "; ..."; + aStr += " )"; + + m_rDialog.set_title(m_rDialog.strip_mnemonic(aStr)); +} + +void FormulaDlg_Impl::RefInputDoneAfter( bool bForced ) +{ + m_xRefBtn->SetStartImage(); + if (!bForced && m_xRefBtn->GetWidget()->get_visible()) + return; + + m_xEdRef->GetWidget()->hide(); + m_xRefBtn->GetWidget()->hide(); + if ( m_pTheRefEdit ) + { + m_pTheRefEdit->SetRefString( m_xEdRef->GetText() ); + m_pTheRefEdit->GrabFocus(); + + if ( m_pTheRefButton ) + m_pTheRefButton->SetStartImage(); + + sal_uInt16 nPrivActiv = m_xParaWin->GetActiveLine(); + m_xParaWin->SetArgument( nPrivActiv, m_xEdRef->GetText() ); + ModifyHdl( *m_xParaWin ); + m_pTheRefEdit = nullptr; + } + m_rDialog.set_title(m_aTitle1); +} + +RefEdit* FormulaDlg_Impl::GetCurrRefEdit() +{ + return m_xEdRef->GetWidget()->get_visible() ? m_xEdRef.get() : m_xParaWin->GetActiveEdit(); +} + +void FormulaDlg_Impl::Update() +{ + FormEditData* pData = m_pHelper->getFormEditData(); + const OUString sExpression = m_xMEdit->get_text(); + m_aOldFormula.clear(); + UpdateTokenArray(sExpression); + FormulaCursor(); + CalcStruct(sExpression); + if (pData->GetMode() == FormulaDlgMode::Formula) + m_xTabCtrl->set_current_page("functiontab"); + else + m_xTabCtrl->set_current_page("structtab"); + m_xBtnMatrix->set_active(pData->GetMatrixFlag()); +} + +void FormulaDlg_Impl::Update(const OUString& _sExp) +{ + CalcStruct(_sExp); + FillDialog(); + FuncSelHdl(*m_xFuncPage); +} + +void FormulaDlg_Impl::SetMeText(const OUString& _sText) +{ + FormEditData* pData = m_pHelper->getFormEditData(); + m_xMEdit->set_text(_sText); + auto aSelection = pData->GetSelection(); + m_xMEdit->select_region(aSelection.Min(), aSelection.Max()); + UpdateOldSel(); +} + +FormulaDlgMode FormulaDlg_Impl::SetMeText( const OUString& _sText, sal_Int32 PrivStart, sal_Int32 PrivEnd, bool bMatrix, bool _bSelect, bool _bUpdate) +{ + FormulaDlgMode eMode = FormulaDlgMode::Formula; + if (!m_bEditFlag) + m_xMEdit->set_text(_sText); + + if ( _bSelect || !m_bEditFlag ) + m_xMEdit->select_region(PrivStart, PrivEnd); + if ( _bUpdate ) + { + UpdateOldSel(); + int nStartPos, nEndPos; + m_xMEdit->get_selection_bounds(nStartPos, nEndPos); + if (nStartPos > nEndPos) + std::swap(nStartPos, nEndPos); + m_pHelper->showReference(m_xMEdit->get_text().copy(nStartPos, nEndPos - nStartPos)); + eMode = FormulaDlgMode::Edit; + + m_xBtnMatrix->set_active( bMatrix ); + } // if ( _bUpdate ) + return eMode; +} + +bool FormulaDlg_Impl::CheckMatrix(OUString& aFormula) +{ + m_xMEdit->grab_focus(); + sal_Int32 nLen = aFormula.getLength(); + bool bMatrix = nLen > 3 // Matrix-Formula + && aFormula[0] == '{' + && aFormula[1] == '=' + && aFormula[nLen-1] == '}'; + if ( bMatrix ) + { + aFormula = aFormula.copy( 1, aFormula.getLength()-2 ); + m_xBtnMatrix->set_active( bMatrix ); + m_xBtnMatrix->set_sensitive(false); + } // if ( bMatrix ) + + m_xTabCtrl->set_current_page("structtab"); + return bMatrix; +} + +IMPL_LINK_NOARG( FormulaDlg_Impl, StructSelHdl, StructPage&, void) +{ + m_bStructUpdate = false; + if (m_xStructPage->IsVisible()) + m_xBtnForward->set_sensitive(false); //@New + m_bStructUpdate = true; +} + +IMPL_LINK_NOARG( FormulaDlg_Impl, MatrixHdl, weld::Toggleable&, void) +{ + m_bUserMatrixFlag = true; + UpdateValues(true); +} + +IMPL_LINK_NOARG( FormulaDlg_Impl, FuncSelHdl, FuncPage&, void) +{ + if ( (m_xFuncPage->GetFunctionEntryCount() > 0) + && (m_xFuncPage->GetFunction() != -1) ) + { + const IFunctionDescription* pDesc = m_xFuncPage->GetFuncDesc( m_xFuncPage->GetFunction() ); + + if (pDesc != m_pFuncDesc) + m_xBtnForward->set_sensitive(true); //new + + if (pDesc) + { + pDesc->initArgumentInfo(); // full argument info is needed + + OUString aSig = pDesc->getSignature(); + m_xFtHeadLine->set_label( pDesc->getFunctionName() ); + m_xFtFuncName->set_label( aSig ); + m_xFtFuncDesc->set_label( pDesc->getDescription() ); + } + } + else + { + m_xFtHeadLine->set_label( OUString() ); + m_xFtFuncName->set_label( OUString() ); + m_xFtFuncDesc->set_label( OUString() ); + } +} + +void FormulaDlg_Impl::UpdateParaWin( const Selection& _rSelection, const OUString& _sRefStr) +{ + Selection theSel = _rSelection; + m_xEdRef->GetWidget()->replace_selection(_sRefStr); + theSel.Max() = theSel.Min() + _sRefStr.getLength(); + m_xEdRef->SetSelection( theSel ); + + + // Manual Update of the results' fields: + + sal_uInt16 nPrivActiv = m_xParaWin->GetActiveLine(); + m_xParaWin->SetArgument( nPrivActiv, m_xEdRef->GetText()); + m_xParaWin->UpdateParas(); + + RefEdit* pEd = GetCurrRefEdit(); + if (pEd) + pEd->SetSelection( theSel ); +} + +bool FormulaDlg_Impl::UpdateParaWin(Selection& _rSelection) +{ + OUString aStrEd; + RefEdit* pEd = GetCurrRefEdit(); + if (pEd && !m_pTheRefEdit) + { + _rSelection = pEd->GetSelection(); + _rSelection.Normalize(); + aStrEd = pEd->GetText(); + m_xEdRef->SetRefString(aStrEd); + m_xEdRef->SetSelection( _rSelection ); + } + else + { + _rSelection = m_xEdRef->GetSelection(); + _rSelection.Normalize(); + aStrEd = m_xEdRef->GetText(); + } + return m_pTheRefEdit == nullptr; +} + +void FormulaDlg_Impl::SetEdSelection() +{ + RefEdit* pEd = GetCurrRefEdit()/*aScParaWin.GetActiveEdit()*/; + if (pEd) + { + Selection theSel = m_xEdRef->GetSelection(); + // Edit may have the focus -> call ModifyHdl in addition + // to what's happening in GetFocus + pEd->GetModifyHdl().Call(*pEd); + pEd->GrabFocus(); + pEd->SetSelection(theSel); + } // if ( pEd ) +} + +FormulaModalDialog::FormulaModalDialog(weld::Window* pParent, + IFunctionManager const * _pFunctionMgr, + IControlReferenceHandler* _pDlg) + : GenericDialogController(pParent, "formula/ui/formuladialog.ui", "FormulaDialog") + , m_pImpl(new FormulaDlg_Impl(*m_xDialog, *m_xBuilder, false/*_bSupportFunctionResult*/, + false/*_bSupportResult*/, false/*_bSupportMatrix*/, + this, _pFunctionMgr, _pDlg)) +{ + m_xDialog->set_title(m_pImpl->m_aTitle1); +} + +FormulaModalDialog::~FormulaModalDialog() { } + +void FormulaModalDialog::Update(const OUString& _sExp) +{ + m_pImpl->Update(_sExp); +} + +void FormulaModalDialog::SetMeText(const OUString& _sText) +{ + m_pImpl->SetMeText(_sText); +} + +void FormulaModalDialog::CheckMatrix(OUString& aFormula) +{ + m_pImpl->CheckMatrix(aFormula); +} + +void FormulaModalDialog::Update() +{ + m_pImpl->Update(); +} + +::std::pair<RefButton*, RefEdit*> FormulaModalDialog::RefInputStartBefore( RefEdit* pEdit, RefButton* pButton ) +{ + return m_pImpl->RefInputStartBefore( pEdit, pButton ); +} + +void FormulaModalDialog::RefInputStartAfter() +{ + m_pImpl->RefInputStartAfter(); +} + +void FormulaModalDialog::RefInputDoneAfter() +{ + m_pImpl->RefInputDoneAfter( true/*bForced*/ ); +} + +void FormulaModalDialog::StoreFormEditData(FormEditData* pData) +{ + m_pImpl->StoreFormEditData(pData); +} + +// Initialisation / General functions for Dialog +FormulaDlg::FormulaDlg(SfxBindings* pB, SfxChildWindow* pCW, + weld::Window* pParent, + IFunctionManager const * _pFunctionMgr, IControlReferenceHandler* _pDlg) + : SfxModelessDialogController( pB, pCW, pParent, "formula/ui/formuladialog.ui", "FormulaDialog") + , m_pImpl(new FormulaDlg_Impl(*m_xDialog, *m_xBuilder, true/*_bSupportFunctionResult*/ + , true/*_bSupportResult*/ + , true/*_bSupportMatrix*/ + , this, _pFunctionMgr, _pDlg)) +{ + m_xDialog->set_title(m_pImpl->m_aTitle1); +} + +FormulaDlg::~FormulaDlg() +{ +} + +void FormulaDlg::Update(const OUString& _sExp) +{ + m_pImpl->Update(_sExp); +} + +void FormulaDlg::SetMeText(const OUString& _sText) +{ + m_pImpl->SetMeText(_sText); +} + +FormulaDlgMode FormulaDlg::SetMeText( const OUString& _sText, sal_Int32 PrivStart, sal_Int32 PrivEnd, bool bMatrix, bool _bSelect, bool _bUpdate) +{ + return m_pImpl->SetMeText( _sText, PrivStart, PrivEnd, bMatrix, _bSelect, _bUpdate); +} + +bool FormulaDlg::CheckMatrix(OUString& aFormula) +{ + return m_pImpl->CheckMatrix(aFormula); +} + +OUString FormulaDlg::GetMeText() const +{ + return m_pImpl->m_xMEdit->get_text(); +} + +void FormulaDlg::Update() +{ + m_pImpl->Update(); +} + +void FormulaDlg::DoEnter() +{ + m_pImpl->DoEnter(false); +} + +::std::pair<RefButton*, RefEdit*> FormulaDlg::RefInputStartBefore( RefEdit* pEdit, RefButton* pButton ) +{ + return m_pImpl->RefInputStartBefore( pEdit, pButton ); +} + +void FormulaDlg::RefInputStartAfter() +{ + m_pImpl->RefInputStartAfter(); +} + +void FormulaDlg::RefInputDoneAfter( bool bForced ) +{ + m_pImpl->RefInputDoneAfter( bForced ); +} + +void FormulaDlg::disableOk() +{ + m_pImpl->m_xBtnEnd->set_sensitive(false); +} + +void FormulaDlg::StoreFormEditData(FormEditData* pData) +{ + m_pImpl->StoreFormEditData(pData); +} + +const IFunctionDescription* FormulaDlg::getCurrentFunctionDescription() const +{ + SAL_WARN_IF( (m_pImpl->m_pFuncDesc && m_pImpl->m_pFuncDesc->getSuppressedArgumentCount() != m_pImpl->m_nArgs), + "formula.ui", "FormulaDlg::getCurrentFunctionDescription: getSuppressedArgumentCount " << + m_pImpl->m_pFuncDesc->getSuppressedArgumentCount() << " != m_nArgs " << m_pImpl->m_nArgs << " for " << + m_pImpl->m_pFuncDesc->getFunctionName()); + return m_pImpl->m_pFuncDesc; +} + +void FormulaDlg::UpdateParaWin( const Selection& _rSelection, const OUString& _sRefStr) +{ + m_pImpl->UpdateParaWin( _rSelection, _sRefStr); +} + +bool FormulaDlg::UpdateParaWin(Selection& _rSelection) +{ + return m_pImpl->UpdateParaWin(_rSelection); +} + +RefEdit* FormulaDlg::GetActiveEdit() +{ + return m_pImpl->m_xParaWin->GetActiveEdit(); +} + +const FormulaHelper& FormulaDlg::GetFormulaHelper() const +{ + return m_pImpl->GetFormulaHelper(); +} + +void FormulaDlg::SetEdSelection() +{ + m_pImpl->SetEdSelection(); +} + +void FormEditData::SaveValues() +{ + Reset(); +} + +void FormEditData::Reset() +{ + nMode = FormulaDlgMode::Formula; + nFStart = 0; + nOffset = 0; + bMatrix = false; + aSelection.Min() = 0; + aSelection.Max() = 0; + aUndoStr.clear(); +} + +FormEditData& FormEditData::operator=( const FormEditData& r ) +{ + nMode = r.nMode; + nFStart = r.nFStart; + nOffset = r.nOffset; + aUndoStr = r.aUndoStr; + bMatrix = r.bMatrix ; + aSelection = r.aSelection; + return *this; +} + +FormEditData::FormEditData() +{ + Reset(); +} + +FormEditData::~FormEditData() +{ +} + +FormEditData::FormEditData( const FormEditData& r ) +{ + *this = r; +} + + +} // formula + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/funcpage.cxx b/formula/source/ui/dlg/funcpage.cxx new file mode 100644 index 0000000000..dbdb49464a --- /dev/null +++ b/formula/source/ui/dlg/funcpage.cxx @@ -0,0 +1,267 @@ +/* -*- 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 <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <formula/IFunctionDescription.hxx> + +#include "funcpage.hxx" +#include <unotools/syslocale.hxx> +#include <unotools/charclass.hxx> + +namespace formula +{ +IMPL_LINK(FuncPage, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + if (rKEvt.GetCharCode() == ' ') + { + aDoubleClickLink.Call(*this); + return true; + } + return false; +} + +// tdf#104487 - remember last used function category - set default to All category +sal_Int32 FuncPage::m_nRememberedFunctionCategory = 1; + +FuncPage::FuncPage(weld::Container* pParent, const IFunctionManager* _pFunctionManager) + : m_xBuilder(Application::CreateBuilder(pParent, "formula/ui/functionpage.ui")) + , m_xContainer(m_xBuilder->weld_container("FunctionPage")) + , m_xLbCategory(m_xBuilder->weld_combo_box("category")) + , m_xLbFunction(m_xBuilder->weld_tree_view("function")) + , m_xLbFunctionSearchString(m_xBuilder->weld_entry("search")) + , m_pFunctionManager(_pFunctionManager) +{ + m_xLbFunction->make_sorted(); + m_aHelpId = m_xLbFunction->get_help_id(); + + m_pFunctionManager->fillLastRecentlyUsedFunctions(aLRUList); + + const sal_uInt32 nCategoryCount = m_pFunctionManager->getCount(); + for (sal_uInt32 j = 0; j < nCategoryCount; ++j) + { + const IFunctionCategory* pCategory = m_pFunctionManager->getCategory(j); + OUString sId(weld::toId(pCategory)); + m_xLbCategory->append(sId, pCategory->getName()); + } + + // tdf#104487 - remember last used function category + m_xLbCategory->set_active(m_nRememberedFunctionCategory); + OUString searchStr = m_xLbFunctionSearchString->get_text(); + UpdateFunctionList(searchStr); + // lock to its initial size + m_xLbFunction->set_size_request(m_xLbFunction->get_preferred_size().Width(), + m_xLbFunction->get_height_rows(15)); + m_xLbCategory->connect_changed(LINK(this, FuncPage, SelComboBoxHdl)); + m_xLbFunction->connect_changed(LINK(this, FuncPage, SelTreeViewHdl)); + m_xLbFunction->connect_row_activated(LINK(this, FuncPage, DblClkHdl)); + m_xLbFunction->connect_key_press(LINK(this, FuncPage, KeyInputHdl)); + m_xLbFunctionSearchString->connect_changed(LINK(this, FuncPage, ModifyHdl)); + + m_xLbFunctionSearchString->grab_focus(); +} + +FuncPage::~FuncPage() {} + +void FuncPage::impl_addFunctions(const IFunctionCategory* _pCategory) +{ + const sal_uInt32 nCount = _pCategory->getCount(); + for (sal_uInt32 i = 0; i < nCount; ++i) + { + TFunctionDesc pDesc(_pCategory->getFunction(i)); + if (!pDesc->isHidden()) + { + OUString sId(weld::toId(pDesc)); + m_xLbFunction->append(sId, pDesc->getFunctionName()); + } + } +} + +//aStr is non-empty when user types in the search box to search some function +void FuncPage::UpdateFunctionList(const OUString& aStr) +{ + m_xLbFunction->clear(); + m_xLbFunction->freeze(); + + const sal_Int32 nSelPos = m_xLbCategory->get_active(); + // tdf#104487 - remember last used function category + m_nRememberedFunctionCategory = nSelPos; + + if (aStr.isEmpty() || nSelPos == 0) + { + const IFunctionCategory* pCategory + = weld::fromId<const IFunctionCategory*>(m_xLbCategory->get_id(nSelPos)); + + if (nSelPos > 0) + { + if (pCategory == nullptr) + { + const sal_uInt32 nCount = m_pFunctionManager->getCount(); + for (sal_uInt32 i = 0; i < nCount; ++i) + { + impl_addFunctions(m_pFunctionManager->getCategory(i)); + } + } + else + { + impl_addFunctions(pCategory); + } + } + else // LRU-List + { + for (auto const& elem : aLRUList) + { + if (elem) // may be null if a function is no longer available + { + OUString sId(weld::toId(elem)); + m_xLbFunction->append(sId, elem->getFunctionName()); + } + } + } + } + else + { + SvtSysLocale aSysLocale; + const CharClass& rCharClass = aSysLocale.GetCharClass(); + const OUString aSearchStr(rCharClass.uppercase(aStr)); + + const sal_uInt32 nCategoryCount = m_pFunctionManager->getCount(); + // Category listbox holds additional entries for Last Used and All, so + // the offset should be two but hard coded numbers are ugly... + const sal_Int32 nCategoryOffset = m_xLbCategory->get_count() - nCategoryCount; + // If a real category (not Last Used or All) is selected, list only + // functions of that category. Else list all, LRU is handled above. + sal_Int32 nCatBeg = (nSelPos == -1 ? -1 : nSelPos - nCategoryOffset); + sal_uInt32 nCatEnd; + if (nCatBeg < 0) + { + nCatBeg = 0; + nCatEnd = nCategoryCount; + } + else + { + nCatEnd = nCatBeg + 1; + } + for (sal_uInt32 i = nCatBeg; i < nCatEnd; ++i) + { + const IFunctionCategory* pCategory = m_pFunctionManager->getCategory(i); + const sal_uInt32 nFunctionCount = pCategory->getCount(); + for (sal_uInt32 j = 0; j < nFunctionCount; ++j) + { + TFunctionDesc pDesc(pCategory->getFunction(j)); + // tdf#146781 - search for the desired function also in the description + if (rCharClass.uppercase(pDesc->getFunctionName()).indexOf(aSearchStr) >= 0 + || rCharClass.uppercase(pDesc->getDescription()).indexOf(aSearchStr) >= 0) + { + if (!pDesc->isHidden()) + { + OUString sId(weld::toId(pDesc)); + m_xLbFunction->append(sId, pDesc->getFunctionName()); + } + } + } + } + } + + m_xLbFunction->thaw(); + // Ensure no function is selected so the Next button doesn't overwrite a + // function that is not in the list with an arbitrary selected one. + m_xLbFunction->unselect_all(); + + if (IsVisible()) + SelTreeViewHdl(*m_xLbFunction); +} + +IMPL_LINK_NOARG(FuncPage, SelComboBoxHdl, weld::ComboBox&, void) +{ + OUString searchStr = m_xLbFunctionSearchString->get_text(); + m_xLbFunction->set_help_id(m_aHelpId); + UpdateFunctionList(searchStr); +} + +IMPL_LINK_NOARG(FuncPage, SelTreeViewHdl, weld::TreeView&, void) +{ + const IFunctionDescription* pDesc = GetFuncDesc(GetFunction()); + if (pDesc) + { + const OUString sHelpId = pDesc->getHelpId(); + if (!sHelpId.isEmpty()) + m_xLbFunction->set_help_id(sHelpId); + } + aSelectionLink.Call(*this); +} + +IMPL_LINK_NOARG(FuncPage, DblClkHdl, weld::TreeView&, bool) +{ + aDoubleClickLink.Call(*this); + return true; +} + +IMPL_LINK_NOARG(FuncPage, ModifyHdl, weld::Entry&, void) +{ + // While typing select All category. + m_xLbCategory->set_active(1); + OUString searchStr = m_xLbFunctionSearchString->get_text(); + UpdateFunctionList(searchStr); +} + +void FuncPage::SetCategory(sal_Int32 nCat) +{ + // tdf#104487 - remember last used function category + m_nRememberedFunctionCategory = nCat; + m_xLbCategory->set_active(nCat); + UpdateFunctionList(OUString()); +} + +sal_Int32 FuncPage::GetFuncPos(const IFunctionDescription* _pDesc) +{ + return m_xLbFunction->find_id(weld::toId(_pDesc)); +} + +void FuncPage::SetFunction(sal_Int32 nFunc) +{ + if (nFunc == -1) + m_xLbFunction->unselect_all(); + else + m_xLbFunction->select(nFunc); +} + +void FuncPage::SetFocus() { m_xLbFunction->grab_focus(); } + +sal_Int32 FuncPage::GetCategory() const { return m_xLbCategory->get_active(); } + +sal_Int32 FuncPage::GetCategoryEntryCount() const { return m_xLbCategory->get_count(); } + +sal_Int32 FuncPage::GetFunction() const { return m_xLbFunction->get_selected_index(); } + +sal_Int32 FuncPage::GetFunctionEntryCount() const { return m_xLbFunction->n_children(); } + +OUString FuncPage::GetSelFunctionName() const { return m_xLbFunction->get_selected_text(); } + +const IFunctionDescription* FuncPage::GetFuncDesc(sal_Int32 nPos) const +{ + if (nPos == -1) + return nullptr; + // not pretty, but hopefully rare + return weld::fromId<const IFunctionDescription*>(m_xLbFunction->get_id(nPos)); +} + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/funcpage.hxx b/formula/source/ui/dlg/funcpage.hxx new file mode 100644 index 0000000000..e7ca248d86 --- /dev/null +++ b/formula/source/ui/dlg/funcpage.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> +#include <vector> + +namespace formula +{ + +class IFunctionDescription; +class IFunctionManager; +class IFunctionCategory; + +typedef const IFunctionDescription* TFunctionDesc; + +class FuncPage final +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + + std::unique_ptr<weld::ComboBox> m_xLbCategory; + std::unique_ptr<weld::TreeView> m_xLbFunction; + std::unique_ptr<weld::Entry> m_xLbFunctionSearchString; + + Link<FuncPage&,void> aDoubleClickLink; + Link<FuncPage&,void> aSelectionLink; + const IFunctionManager* m_pFunctionManager; + + ::std::vector< TFunctionDesc > aLRUList; + OUString m_aHelpId; + + // tdf#104487 - remember last used function category + static sal_Int32 m_nRememberedFunctionCategory; + + void impl_addFunctions(const IFunctionCategory* _pCategory); + + DECL_LINK(SelComboBoxHdl, weld::ComboBox&, void); + DECL_LINK(SelTreeViewHdl, weld::TreeView&, void); + DECL_LINK(DblClkHdl, weld::TreeView&, bool); + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + DECL_LINK(ModifyHdl, weld::Entry&, void); + + void UpdateFunctionList(const OUString&); + +public: + + FuncPage(weld::Container* pContainer, const IFunctionManager* _pFunctionManager); + ~FuncPage(); + + void SetCategory(sal_Int32 nCat); + void SetFunction(sal_Int32 nFunc); + void SetFocus(); + sal_Int32 GetCategory() const; + sal_Int32 GetCategoryEntryCount() const; + sal_Int32 GetFunction() const; + sal_Int32 GetFunctionEntryCount() const; + + // tdf#104487 - remember last used function category + static sal_Int32 GetRememeberdFunctionCategory() { return m_nRememberedFunctionCategory; }; + + sal_Int32 GetFuncPos(const IFunctionDescription* _pDesc); + const IFunctionDescription* GetFuncDesc( sal_Int32 nPos ) const; + OUString GetSelFunctionName() const; + + void SetDoubleClickHdl( const Link<FuncPage&,void>& rLink ) { aDoubleClickLink = rLink; } + + void SetSelectHdl( const Link<FuncPage&,void>& rLink ) { aSelectionLink = rLink; } + + bool IsVisible() const { return m_xContainer->get_visible(); } +}; + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/funcutl.cxx b/formula/source/ui/dlg/funcutl.cxx new file mode 100644 index 0000000000..58c2492c55 --- /dev/null +++ b/formula/source/ui/dlg/funcutl.cxx @@ -0,0 +1,486 @@ +/* -*- 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 <vcl/event.hxx> + +#include <formula/funcutl.hxx> +#include <formula/IControlReferenceHandler.hxx> +#include <vcl/svapp.hxx> +#include "ControlHelper.hxx" +#include "parawin.hxx" +#include <strings.hrc> +#include <bitmaps.hlst> +#include <core_resource.hxx> + +namespace formula +{ + +ArgEdit::ArgEdit(std::unique_ptr<weld::Entry> xControl) + : RefEdit(std::move(xControl)) + , pEdPrev(nullptr) + , pEdNext(nullptr) + , pSlider(nullptr) + , pParaWin(nullptr) + , nArgs(0) +{ +} + +void ArgEdit::Init(ArgEdit* pPrevEdit, ArgEdit* pNextEdit, + weld::ScrolledWindow& rArgSlider, + ParaWin& rParaWin, sal_uInt16 nArgCount) +{ + pEdPrev = pPrevEdit; + pEdNext = pNextEdit; + pSlider = &rArgSlider; + pParaWin = &rParaWin; + nArgs = nArgCount; +} + +// Cursor control for Edit Fields in Argument Dialog +bool ArgEdit::KeyInput(const KeyEvent& rKEvt) +{ + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + bool bUp = (aCode.GetCode() == KEY_UP); + bool bDown = (aCode.GetCode() == KEY_DOWN); + + if ( pSlider + && ( !aCode.IsShift() && !aCode.IsMod1() && !aCode.IsMod2() ) + && ( bUp || bDown ) ) + { + if ( nArgs > 1 ) + { + ArgEdit* pEd = nullptr; + int nThumb = pSlider->vadjustment_get_value(); + bool bDoScroll = false; + bool bChangeFocus = false; + + if ( bDown ) + { + if ( nArgs > 4 ) + { + if ( !pEdNext ) + { + nThumb++; + bDoScroll = ( nThumb+3 < static_cast<tools::Long>(nArgs) ); + } + else + { + pEd = pEdNext; + bChangeFocus = true; + } + } + else if ( pEdNext ) + { + pEd = pEdNext; + bChangeFocus = true; + } + } + else // if ( bUp ) + { + if ( nArgs > 4 ) + { + if ( !pEdPrev ) + { + nThumb--; + bDoScroll = ( nThumb >= 0 ); + } + else + { + pEd = pEdPrev; + bChangeFocus = true; + } + } + else if ( pEdPrev ) + { + pEd = pEdPrev; + bChangeFocus = true; + } + } + + if ( bDoScroll ) + { + pSlider->vadjustment_set_value( nThumb ); + pParaWin->SliderMoved(); + } + else if ( bChangeFocus ) + { + pEd->GrabFocus(); + } + } + return true; + } + return RefEdit::KeyInput(rKEvt); +} + +ArgInput::ArgInput() +{ + pFtArg=nullptr; + pBtnFx=nullptr; + pEdArg=nullptr; + pRefBtn=nullptr; +} + +void ArgInput::InitArgInput(weld::Label* pftArg, weld::Button* pbtnFx, + ArgEdit* pedArg, RefButton* prefBtn) +{ + pFtArg =pftArg; + pBtnFx =pbtnFx; + pEdArg =pedArg; + pRefBtn=prefBtn; + + if(pBtnFx!=nullptr) + { + pBtnFx->connect_clicked( LINK( this, ArgInput, FxBtnClickHdl ) ); + pBtnFx->connect_focus_in( LINK( this, ArgInput, FxBtnFocusHdl ) ); + } + if(pEdArg!=nullptr) + { + pEdArg->SetGetFocusHdl ( LINK( this, ArgInput, EdFocusHdl ) ); + pEdArg->SetModifyHdl ( LINK( this, ArgInput, EdModifyHdl ) ); + } +} + +// Sets the Name for the Argument +void ArgInput::SetArgName(const OUString &aArg) +{ + if (pFtArg) + pFtArg->set_label(aArg ); +} + +// Returns the Name for the Argument +OUString ArgInput::GetArgName() const +{ + OUString aPrivArgName; + if (pFtArg) + aPrivArgName = pFtArg->get_label(); + return aPrivArgName; +} + +//Sets the Name for the Argument +void ArgInput::SetArgNameFont(const vcl::Font &aFont) +{ + if (pFtArg) + pFtArg->set_font(aFont); +} + +//Sets up the Selection for the EditBox. +void ArgInput::SelectAll() +{ + if (pEdArg) + pEdArg->SelectAll(); +} + +//Sets the Value for the Argument +void ArgInput::SetArgVal(const OUString &rVal) +{ + if (pEdArg) + pEdArg->SetRefString(rVal); +} + +//Returns the Value for the Argument +OUString ArgInput::GetArgVal() const +{ + OUString aResult; + if (pEdArg) + aResult=pEdArg->GetText(); + return aResult; +} + +//Hides the Controls +void ArgInput::Hide() +{ + if (pFtArg && pBtnFx && pEdArg && pRefBtn) + { + pFtArg->hide(); + pBtnFx->hide(); + pEdArg->GetWidget()->hide(); + pRefBtn->GetWidget()->hide(); + } +} + +//Casts the Controls again. +void ArgInput::Show() +{ + if (pFtArg && pBtnFx && pEdArg && pRefBtn) + { + pFtArg->show(); + pBtnFx->show(); + pEdArg->GetWidget()->show(); + pRefBtn->GetWidget()->show(); + } +} + +void ArgInput::UpdateAccessibleNames() +{ + OUString aArgName = ":" + pFtArg->get_label(); + + OUString aName = pBtnFx->get_tooltip_text() + aArgName; + pBtnFx->set_accessible_name(aName); + + aName = pRefBtn->GetWidget()->get_tooltip_text() + aArgName; + pRefBtn->GetWidget()->set_accessible_name(aName); +} + +IMPL_LINK(ArgInput, FxBtnClickHdl, weld::Button&, rBtn, void) +{ + if (&rBtn == pBtnFx) + aFxClickLink.Call(*this); +} + +IMPL_LINK( ArgInput, FxBtnFocusHdl, weld::Widget&, rControl, void ) +{ + if (&rControl == pBtnFx) + aFxFocusLink.Call(*this); +} + +IMPL_LINK( ArgInput, EdFocusHdl, RefEdit&, rControl, void ) +{ + if (&rControl == pEdArg) + aEdFocusLink.Call(*this); +} + +IMPL_LINK( ArgInput, EdModifyHdl, RefEdit&, rEdit, void ) +{ + if (&rEdit == pEdArg) + aEdModifyLink.Call(*this); +} + +RefEdit::RefEdit(std::unique_ptr<weld::Entry> xControl) + : xEntry(std::move(xControl)) + , aIdle("formula RefEdit Idle") + , pAnyRefDlg(nullptr) + , pLabelWidget(nullptr) + , mpFocusInEvent(nullptr) + , mpFocusOutEvent(nullptr) +{ + xEntry->connect_focus_in(LINK(this, RefEdit, GetFocusHdl)); + xEntry->connect_focus_out(LINK(this, RefEdit, LoseFocusHdl)); + xEntry->connect_key_press(LINK(this, RefEdit, KeyInputHdl)); + xEntry->connect_changed(LINK(this, RefEdit, Modify)); + aIdle.SetInvokeHandler( LINK( this, RefEdit, UpdateHdl ) ); +} + +RefEdit::~RefEdit() +{ + if (mpFocusInEvent) + Application::RemoveUserEvent(mpFocusInEvent); + if (mpFocusOutEvent) + Application::RemoveUserEvent(mpFocusOutEvent); + aIdle.ClearInvokeHandler(); + aIdle.Stop(); +} + +void RefEdit::SetRefString( const OUString& rStr ) +{ + // Prevent unwanted side effects by setting only a differing string. + // See commit message for reasons. + if (xEntry->get_text() != rStr) + xEntry->set_text(rStr); +} + +void RefEdit::SetRefValid(bool bValid) +{ + xEntry->set_message_type(bValid ? weld::EntryMessageType::Normal : weld::EntryMessageType::Error); +} + +void RefEdit::SetText(const OUString& rStr) +{ + xEntry->set_text(rStr); + UpdateHdl( &aIdle ); +} + +void RefEdit::StartUpdateData() +{ + aIdle.Start(); +} + +void RefEdit::SetReferences(IControlReferenceHandler* pDlg, weld::Label* pLabel) +{ + pAnyRefDlg = pDlg; + pLabelWidget = pLabel; + + if( pDlg ) + { + aIdle.SetInvokeHandler(LINK(this, RefEdit, UpdateHdl)); + } + else + { + aIdle.ClearInvokeHandler(); + aIdle.Stop(); + } +} + +IMPL_LINK_NOARG(RefEdit, Modify, weld::Entry&, void) +{ + maModifyHdl.Call(*this); + if (pAnyRefDlg) + pAnyRefDlg->HideReference(); +} + +IMPL_LINK(RefEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return KeyInput(rKEvt); +} + +bool RefEdit::KeyInput(const KeyEvent& rKEvt) +{ + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + if (pAnyRefDlg && !rKeyCode.GetModifier() && rKeyCode.GetCode() == KEY_F2) + { + pAnyRefDlg->ReleaseFocus( this ); + return true; + } + + switch (rKeyCode.GetCode()) + { + case KEY_RETURN: + case KEY_ESCAPE: + return maActivateHdl.Call(*GetWidget()); + } + + return false; +} + +void RefEdit::GetFocus() +{ + maGetFocusHdl.Call(*this); + StartUpdateData(); +} + +void RefEdit::LoseFocus() +{ + maLoseFocusHdl.Call(*this); + if( pAnyRefDlg ) + pAnyRefDlg->HideReference(); +} + +IMPL_LINK_NOARG(RefEdit, GetFocusHdl, weld::Widget&, void) +{ + // in e.g. function wizard RefEdits we want to select all when we get focus + // but in the gtk case there are pending gtk handlers which change selection + // after our handler, so post our focus in event to happen after those complete + if (mpFocusInEvent) + Application::RemoveUserEvent(mpFocusInEvent); + mpFocusInEvent = Application::PostUserEvent(LINK(this, RefEdit, AsyncFocusInHdl)); +} + +IMPL_LINK_NOARG(RefEdit, LoseFocusHdl, weld::Widget&, void) +{ + // tdf#127262 because focus in is async, focus out must not appear out + // of sequence to focus in + if (mpFocusOutEvent) + Application::RemoveUserEvent(mpFocusOutEvent); + mpFocusOutEvent = Application::PostUserEvent(LINK(this, RefEdit, AsyncFocusOutHdl)); +} + +IMPL_LINK_NOARG(RefEdit, AsyncFocusInHdl, void*, void) +{ + mpFocusInEvent = nullptr; + GetFocus(); +} + +IMPL_LINK_NOARG(RefEdit, AsyncFocusOutHdl, void*, void) +{ + mpFocusOutEvent = nullptr; + LoseFocus(); +} + +IMPL_LINK_NOARG(RefEdit, UpdateHdl, Timer *, void) +{ + if( pAnyRefDlg ) + pAnyRefDlg->ShowReference(xEntry->get_text()); +} + +RefButton::RefButton(std::unique_ptr<weld::Button> xControl) + : xButton(std::move(xControl)) + , pAnyRefDlg( nullptr ) + , pRefEdit( nullptr ) +{ + xButton->connect_focus_in(LINK(this, RefButton, GetFocus)); + xButton->connect_focus_out(LINK(this, RefButton, LoseFocus)); + xButton->connect_key_press(LINK(this, RefButton, KeyInput)); + xButton->connect_clicked(LINK(this, RefButton, Click)); + SetStartImage(); +} + +RefButton::~RefButton() +{ +} + +void RefButton::SetStartImage() +{ + xButton->set_from_icon_name(RID_BMP_REFBTN1); + xButton->set_tooltip_text(ForResId(RID_STR_SHRINK)); +} + +void RefButton::SetEndImage() +{ + xButton->set_from_icon_name(RID_BMP_REFBTN2); + xButton->set_tooltip_text(ForResId(RID_STR_EXPAND)); +} + +void RefButton::SetReferences( IControlReferenceHandler* pDlg, RefEdit* pEdit ) +{ + pAnyRefDlg = pDlg; + pRefEdit = pEdit; +} + +IMPL_LINK_NOARG(RefButton, Click, weld::Button&, void) +{ + maClickHdl.Call(*this); + if( pAnyRefDlg ) + pAnyRefDlg->ToggleCollapsed( pRefEdit, this ); +} + +IMPL_LINK(RefButton, KeyInput, const KeyEvent&, rKEvt, bool) +{ + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + if (pAnyRefDlg && !rKeyCode.GetModifier() && rKeyCode.GetCode() == KEY_F2) + { + pAnyRefDlg->ReleaseFocus( pRefEdit ); + return true; + } + + switch (rKeyCode.GetCode()) + { + case KEY_RETURN: + case KEY_ESCAPE: + return maActivateHdl.Call(*GetWidget()); + } + + return false; +} + +IMPL_LINK_NOARG(RefButton, GetFocus, weld::Widget&, void) +{ + maGetFocusHdl.Call(*this); + if (pRefEdit) + pRefEdit->StartUpdateData(); +} + +IMPL_LINK_NOARG(RefButton, LoseFocus, weld::Widget&, void) +{ + maLoseFocusHdl.Call(*this); + if (pRefEdit) + pRefEdit->DoModify(); +} + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/parawin.cxx b/formula/source/ui/dlg/parawin.cxx new file mode 100644 index 0000000000..bcc1c2d1a9 --- /dev/null +++ b/formula/source/ui/dlg/parawin.cxx @@ -0,0 +1,600 @@ +/* -*- 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 <comphelper/string.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +#include "parawin.hxx" +#include <formula/IFunctionDescription.hxx> +#include <formula/funcvarargs.h> +#include <strings.hrc> +#include <bitmaps.hlst> +#include <core_resource.hxx> + +namespace formula +{ + +// Formula token argument count is sal_uInt8, max 255, edit offset 254. +constexpr sal_uInt16 kMaxArgCount = 255; +constexpr sal_uInt16 kMaxArgOffset = kMaxArgCount - 1; + +ParaWin::ParaWin(weld::Container* pParent,IControlReferenceHandler* _pDlg) + : pFuncDesc(nullptr) + , pMyParent(_pDlg) + , m_sOptional(ForResId(STR_OPTIONAL)) + , m_sRequired(ForResId(STR_REQUIRED)) + , m_xBuilder(Application::CreateBuilder(pParent, "formula/ui/parameter.ui")) + , m_xContainer(m_xBuilder->weld_container("ParameterPage")) + , m_xSlider(m_xBuilder->weld_scrolled_window("scrollbar", true)) + , m_xParamGrid(m_xBuilder->weld_widget("paramgrid")) + , m_xGrid(m_xBuilder->weld_widget("grid")) + , m_xFtEditDesc(m_xBuilder->weld_label("editdesc")) + , m_xFtArgName(m_xBuilder->weld_label("parname")) + , m_xFtArgDesc(m_xBuilder->weld_label("pardesc")) + , m_xBtnFx1(m_xBuilder->weld_button("FX1")) + , m_xBtnFx2(m_xBuilder->weld_button("FX2")) + , m_xBtnFx3(m_xBuilder->weld_button("FX3")) + , m_xBtnFx4(m_xBuilder->weld_button("FX4")) + , m_xFtArg1(m_xBuilder->weld_label("FT_ARG1")) + , m_xFtArg2(m_xBuilder->weld_label("FT_ARG2")) + , m_xFtArg3(m_xBuilder->weld_label("FT_ARG3")) + , m_xFtArg4(m_xBuilder->weld_label("FT_ARG4")) + , m_xEdArg1(new ArgEdit(m_xBuilder->weld_entry("ED_ARG1"))) + , m_xEdArg2(new ArgEdit(m_xBuilder->weld_entry("ED_ARG2"))) + , m_xEdArg3(new ArgEdit(m_xBuilder->weld_entry("ED_ARG3"))) + , m_xEdArg4(new ArgEdit(m_xBuilder->weld_entry("ED_ARG4"))) + , m_xRefBtn1(new RefButton(m_xBuilder->weld_button("RB_ARG1"))) + , m_xRefBtn2(new RefButton(m_xBuilder->weld_button("RB_ARG2"))) + , m_xRefBtn3(new RefButton(m_xBuilder->weld_button("RB_ARG3"))) + , m_xRefBtn4(new RefButton(m_xBuilder->weld_button("RB_ARG4"))) +{ + // Space for three lines of text in function description. + m_xFtEditDesc->set_label("X\nX\nX\n"); + auto nEditHeight = m_xFtEditDesc->get_preferred_size().Height(); + m_xFtEditDesc->set_size_request(-1, nEditHeight); + m_xFtEditDesc->set_label(""); + // Space for two lines of text in parameter description. + m_xFtArgDesc->set_label("X\nX\n"); + auto nArgHeight = m_xFtArgDesc->get_preferred_size().Height(); + m_xFtArgDesc->set_size_request(-1, nArgHeight); + m_xFtArgDesc->set_label(""); + + m_xBtnFx1->set_from_icon_name(BMP_FX); + m_xBtnFx2->set_from_icon_name(BMP_FX); + m_xBtnFx3->set_from_icon_name(BMP_FX); + m_xBtnFx4->set_from_icon_name(BMP_FX); + + //lock down initial preferences + m_xParamGrid->set_size_request(-1, m_xParamGrid->get_preferred_size().Height()); + Size aSize(m_xContainer->get_preferred_size()); + m_xContainer->set_size_request(aSize.Width(), aSize.Height()); + + aDefaultString = m_xFtEditDesc->get_label(); + nEdFocus = NOT_FOUND; + nActiveLine = 0; + + m_xSlider->connect_vadjustment_changed(LINK(this, ParaWin, ScrollHdl)); + + InitArgInput( 0, *m_xFtArg1, *m_xBtnFx1, *m_xEdArg1, *m_xRefBtn1); + InitArgInput( 1, *m_xFtArg2, *m_xBtnFx2, *m_xEdArg2, *m_xRefBtn2); + InitArgInput( 2, *m_xFtArg3, *m_xBtnFx3, *m_xEdArg3, *m_xRefBtn3); + InitArgInput( 3, *m_xFtArg4, *m_xBtnFx4, *m_xEdArg4, *m_xRefBtn4); + ClearAll(); +} + +void ParaWin::UpdateArgDesc( sal_uInt16 nArg ) +{ + if (nArg == NOT_FOUND) + return; + + if (nMaxArgs > 4) + nArg = sal::static_int_cast<sal_uInt16>( nArg + GetSliderPos() ); + + if ((nMaxArgs <= 0) || (nArg >= nMaxArgs)) + return; + + OUString aArgDesc; + OUString aArgName; + + SetArgumentDesc( OUString() ); + SetArgumentText( OUString() ); + + if ( nArgs < VAR_ARGS ) + { + sal_uInt16 nRealArg = (nArg < aVisibleArgMapping.size()) ? aVisibleArgMapping[nArg] : nArg; + aArgDesc = pFuncDesc->getParameterDescription(nRealArg); + aArgName = pFuncDesc->getParameterName(nRealArg) + " " + + ((pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired); + } + else if ( nArgs < PAIRED_VAR_ARGS ) + { + sal_uInt16 nFix = nArgs - VAR_ARGS; + sal_uInt16 nPos = std::min( nArg, nFix ); + sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ? + aVisibleArgMapping[nPos] : aVisibleArgMapping.back()); + aArgDesc = pFuncDesc->getParameterDescription(nRealArg); + aArgName = pFuncDesc->getParameterName(nRealArg); + sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart(); + if ( nArg >= nVarArgsStart ) + aArgName += OUString::number( nArg-nVarArgsStart+1 ); + aArgName += " " + ((nArg > nFix || pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired) ; + } + else + { + sal_uInt16 nFix = nArgs - PAIRED_VAR_ARGS; + sal_uInt16 nPos; + if ( nArg < nFix ) + nPos = nArg; + else + nPos = nFix + ( (nArg-nFix) % 2); + sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ? + aVisibleArgMapping[nPos] : aVisibleArgMapping.back()); + aArgDesc = pFuncDesc->getParameterDescription(nRealArg); + aArgName = pFuncDesc->getParameterName(nRealArg); + sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart(); + if ( nArg >= nVarArgsStart ) + aArgName += OUString::number( (nArg-nVarArgsStart)/2 + 1 ); + aArgName += " " + ((nArg > (nFix+1) || pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired) ; + } + + SetArgumentDesc(aArgDesc); + SetArgumentText(aArgName); +} + +void ParaWin::UpdateArgInput( sal_uInt16 nOffset, sal_uInt16 i ) +{ + sal_uInt16 nArg = nOffset + i; + if (nArg > kMaxArgOffset) + return; + + if ( nArgs < VAR_ARGS) + { + if (nArg < nMaxArgs) + { + sal_uInt16 nRealArg = aVisibleArgMapping[nArg]; + SetArgNameFont (i,(pFuncDesc->isParameterOptional(nRealArg)) + ? aFntLight : aFntBold ); + SetArgName (i,pFuncDesc->getParameterName(nRealArg)); + } + } + else if ( nArgs < PAIRED_VAR_ARGS) + { + sal_uInt16 nFix = nArgs - VAR_ARGS; + sal_uInt16 nPos = std::min( nArg, nFix ); + sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ? + aVisibleArgMapping[nPos] : aVisibleArgMapping.back()); + SetArgNameFont( i, + (nArg > nFix || pFuncDesc->isParameterOptional(nRealArg)) ? + aFntLight : aFntBold ); + sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart(); + if ( nArg >= nVarArgsStart ) + { + OUString aArgName = pFuncDesc->getParameterName(nRealArg) + + OUString::number(nArg-nVarArgsStart+1); + SetArgName( i, aArgName ); + } + else + SetArgName( i, pFuncDesc->getParameterName(nRealArg) ); + } + else + { + sal_uInt16 nFix = nArgs - PAIRED_VAR_ARGS; + sal_uInt16 nPos; + if ( nArg < nFix ) + nPos = nArg; + else + nPos = nFix + ( (nArg-nFix) % 2); + sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ? + aVisibleArgMapping[nPos] : aVisibleArgMapping.back()); + SetArgNameFont( i, + (nArg > (nFix+1) || pFuncDesc->isParameterOptional(nRealArg)) ? + aFntLight : aFntBold ); + sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart(); + if ( nArg >= nVarArgsStart ) + { + OUString aArgName = pFuncDesc->getParameterName(nRealArg) + + OUString::number( (nArg-nVarArgsStart)/2 + 1 ); + SetArgName( i, aArgName ); + } + else + SetArgName( i, pFuncDesc->getParameterName(nRealArg) ); + } + if (nArg < nMaxArgs) + aArgInput[i].SetArgVal(aParaArray[nArg]); +} + +ParaWin::~ParaWin() +{ + // #i66422# if the focus changes during destruction of the controls, + // don't call the focus handlers + Link<weld::Widget&,void> aEmptyLink; + m_xBtnFx1->connect_focus_in(aEmptyLink); + m_xBtnFx2->connect_focus_in(aEmptyLink); + m_xBtnFx3->connect_focus_in(aEmptyLink); + m_xBtnFx4->connect_focus_in(aEmptyLink); +} + +void ParaWin::SetActiveLine(sal_uInt16 no) +{ + if (no >= nMaxArgs) + return; + + tools::Long nOffset = GetSliderPos(); + nActiveLine=no; + tools::Long nNewEdPos=static_cast<tools::Long>(nActiveLine)-nOffset; + if(nNewEdPos<0 || nNewEdPos>3) + { + nOffset+=nNewEdPos; + SetSliderPos(static_cast<sal_uInt16>(nOffset)); + nOffset=GetSliderPos(); + } + nEdFocus=no-static_cast<sal_uInt16>(nOffset); + UpdateArgDesc( nEdFocus ); +} + +RefEdit* ParaWin::GetActiveEdit() +{ + if (nMaxArgs > 0 && nEdFocus != NOT_FOUND) + { + return aArgInput[nEdFocus].GetArgEdPtr(); + } + else + { + return nullptr; + } +} + + +OUString ParaWin::GetArgument(sal_uInt16 no) +{ + OUString aStr; + if(no<aParaArray.size()) + { + aStr=aParaArray[no]; + if(no==nActiveLine && aStr.isEmpty()) + aStr += " "; + } + return aStr; +} + +OUString ParaWin::GetActiveArgName() const +{ + OUString aStr; + if (nMaxArgs > 0 && nEdFocus != NOT_FOUND) + { + aStr=aArgInput[nEdFocus].GetArgName(); + } + return aStr; +} + + +void ParaWin::SetArgument(sal_uInt16 no, std::u16string_view aString) +{ + if (no < aParaArray.size()) + aParaArray[no] = comphelper::string::stripStart(aString, ' '); +} + +void ParaWin::SetArgumentFonts(const vcl::Font&aBoldFont,const vcl::Font&aLightFont) +{ + aFntBold=aBoldFont; + aFntLight=aLightFont; +} + +void ParaWin::SetFunctionDesc(const IFunctionDescription* pFDesc) +{ + pFuncDesc=pFDesc; + + SetArgumentDesc( OUString() ); + SetArgumentText( OUString() ); + SetEditDesc( OUString() ); + nMaxArgs = nArgs = 0; + if ( pFuncDesc!=nullptr) + { + if ( !pFuncDesc->getDescription().isEmpty() ) + { + SetEditDesc(pFuncDesc->getDescription()); + } + else + { + SetEditDesc(aDefaultString); + } + nArgs = pFuncDesc->getSuppressedArgumentCount(); + nMaxArgs = std::min( nArgs, kMaxArgCount); + if (sal_uInt16 nVarArgsLimit = pFuncDesc->getVarArgsLimit()) + nMaxArgs = std::min( nVarArgsLimit, nMaxArgs); + pFuncDesc->fillVisibleArgumentMapping(aVisibleArgMapping); + m_xSlider->set_vpolicy(VclPolicyType::NEVER); + m_xSlider->set_size_request(-1, -1); + OUString sHelpId = pFuncDesc->getHelpId(); + m_xContainer->set_help_id(sHelpId); + m_xEdArg1->GetWidget()->set_help_id(sHelpId); + m_xEdArg2->GetWidget()->set_help_id(sHelpId); + m_xEdArg3->GetWidget()->set_help_id(sHelpId); + m_xEdArg4->GetWidget()->set_help_id(sHelpId); + + SetActiveLine(0); + } + else + { + nActiveLine=0; + } + +} + +void ParaWin::SetArgumentText(const OUString& aText) +{ + m_xFtArgName->set_label(aText); +} + +void ParaWin::SetArgumentDesc(const OUString& aText) +{ + m_xFtArgDesc->set_label(aText); +} + +void ParaWin::SetEditDesc(const OUString& aText) +{ + m_xFtEditDesc->set_label(aText); +} + +void ParaWin::SetArgName(sal_uInt16 no,const OUString& aText) +{ + aArgInput[no].SetArgName(aText); + aArgInput[no].UpdateAccessibleNames(); +} + +void ParaWin::SetArgNameFont(sal_uInt16 no,const vcl::Font& aFont) +{ + aArgInput[no].SetArgNameFont(aFont); +} + +void ParaWin::SetEdFocus() +{ + UpdateArgDesc(0); + if(!aParaArray.empty()) + aArgInput[0].GetArgEdPtr()->GrabFocus(); +} + +void ParaWin::InitArgInput(sal_uInt16 nPos, weld::Label& rFtArg, weld::Button& rBtnFx, + ArgEdit& rEdArg, RefButton& rRefBtn) +{ + + rRefBtn.SetReferences(pMyParent, &rEdArg); + rEdArg.SetReferences(pMyParent, &rFtArg); + + aArgInput[nPos].InitArgInput (&rFtArg,&rBtnFx,&rEdArg,&rRefBtn); + + aArgInput[nPos].Hide(); + + aArgInput[nPos].SetFxClickHdl ( LINK( this, ParaWin, GetFxHdl ) ); + aArgInput[nPos].SetFxFocusHdl ( LINK( this, ParaWin, GetFxFocusHdl ) ); + aArgInput[nPos].SetEdFocusHdl ( LINK( this, ParaWin, GetEdFocusHdl ) ); + aArgInput[nPos].SetEdModifyHdl ( LINK( this, ParaWin, ModifyHdl ) ); + aArgInput[nPos].UpdateAccessibleNames(); +} + +void ParaWin::ClearAll() +{ + SetFunctionDesc(nullptr); + SetArgumentOffset(0); +} + +void ParaWin::SetArgumentOffset(sal_uInt16 nOffset) +{ + aParaArray.clear(); + m_xSlider->vadjustment_set_value(0); + + aParaArray.resize(nMaxArgs); + + if (nMaxArgs > 0) + { + for ( int i=0; i<4 && i<nMaxArgs; i++ ) + { + aArgInput[i].SetArgVal(OUString()); + aArgInput[i].GetArgEdPtr()->Init( + (i==0) ? nullptr : aArgInput[i-1].GetArgEdPtr(), + (i==3 || i==nMaxArgs-1) ? nullptr : aArgInput[i+1].GetArgEdPtr(), + *m_xSlider, *this, nMaxArgs ); + } + } + + UpdateParas(); + + if (nMaxArgs < 5) + { + m_xSlider->set_vpolicy(VclPolicyType::NEVER); + m_xSlider->set_size_request(-1, -1); + } + else + { + m_xSlider->vadjustment_configure(nOffset, 0, nMaxArgs, 1, 4, 4); + m_xSlider->set_vpolicy(VclPolicyType::ALWAYS); + Size aPrefSize(m_xGrid->get_preferred_size()); + m_xSlider->set_size_request(aPrefSize.Width(), aPrefSize.Height()); + } +} + +void ParaWin::UpdateParas() +{ + sal_uInt16 i; + sal_uInt16 nOffset = GetSliderPos(); + + if ( nMaxArgs > 0 ) + { + for ( i=0; (i<nMaxArgs) && (i<4); i++ ) + { + UpdateArgInput( nOffset, i ); + aArgInput[i].Show(); + } + } + + for ( i=nMaxArgs; i<4; i++ ) + aArgInput[i].Hide(); +} + + +sal_uInt16 ParaWin::GetSliderPos() const +{ + return static_cast<sal_uInt16>(m_xSlider->vadjustment_get_value()); +} + +void ParaWin::SetSliderPos(sal_uInt16 nSliderPos) +{ + sal_uInt16 nOffset = GetSliderPos(); + + if(m_xSlider->get_visible() && nOffset!=nSliderPos) + { + m_xSlider->vadjustment_set_value(nSliderPos); + for ( sal_uInt16 i=0; i<4; i++ ) + { + UpdateArgInput( nSliderPos, i ); + } + } +} + +void ParaWin::SliderMoved() +{ + sal_uInt16 nOffset = GetSliderPos(); + + for ( sal_uInt16 i=0; i<4; i++ ) + { + UpdateArgInput( nOffset, i ); + } + if(nEdFocus!=NOT_FOUND) + { + UpdateArgDesc( nEdFocus ); + aArgInput[nEdFocus].SelectAll(); + nActiveLine=nEdFocus+nOffset; + ArgumentModified(); + aArgInput[nEdFocus].SelectAll(); // ensure all is still selected + aArgInput[nEdFocus].UpdateAccessibleNames(); + } +} + +void ParaWin::ArgumentModified() +{ + aArgModifiedLink.Call(*this); +} + +IMPL_LINK( ParaWin, GetFxHdl, ArgInput&, rPtr, void ) +{ + sal_uInt16 nOffset = GetSliderPos(); + nEdFocus=NOT_FOUND; + for (size_t nPos=0; nPos < std::size(aArgInput); ++nPos) + { + if(&rPtr == &aArgInput[nPos]) + { + nEdFocus=nPos; + break; + } + } + + if(nEdFocus!=NOT_FOUND) + { + aArgInput[nEdFocus].SelectAll(); + nActiveLine=nEdFocus+nOffset; + aFxLink.Call(*this); + } +} + +IMPL_LINK( ParaWin, GetFxFocusHdl, ArgInput&, rPtr, void ) +{ + sal_uInt16 nOffset = GetSliderPos(); + nEdFocus=NOT_FOUND; + for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos) + { + if(&rPtr == &aArgInput[nPos]) + { + nEdFocus=nPos; + break; + } + } + + if(nEdFocus!=NOT_FOUND) + { + aArgInput[nEdFocus].SelectAll(); + UpdateArgDesc( nEdFocus ); + nActiveLine=nEdFocus+nOffset; + } +} + +IMPL_LINK( ParaWin, GetEdFocusHdl, ArgInput&, rPtr, void ) +{ + sal_uInt16 nOffset = GetSliderPos(); + nEdFocus=NOT_FOUND; + for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos) + { + if(&rPtr == &aArgInput[nPos]) + { + nEdFocus=nPos; + break; + } + } + + if(nEdFocus!=NOT_FOUND) + { + aArgInput[nEdFocus].SelectAll(); + UpdateArgDesc( nEdFocus ); + nActiveLine=nEdFocus+nOffset; + ArgumentModified(); + aArgInput[nEdFocus].SelectAll(); // ensure all is still selected + aArgInput[nEdFocus].UpdateAccessibleNames(); + } +} + +IMPL_LINK_NOARG(ParaWin, ScrollHdl, weld::ScrolledWindow&, void) +{ + SliderMoved(); +} + +IMPL_LINK( ParaWin, ModifyHdl, ArgInput&, rPtr, void ) +{ + sal_uInt16 nOffset = GetSliderPos(); + nEdFocus=NOT_FOUND; + for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos) + { + if(&rPtr == &aArgInput[nPos]) + { + nEdFocus=nPos; + break; + } + } + if(nEdFocus!=NOT_FOUND) + { + size_t nPara = nEdFocus + nOffset; + if (nPara < aParaArray.size()) + aParaArray[nPara] = aArgInput[nEdFocus].GetArgVal(); + else + { + SAL_WARN("formula.ui","ParaWin::ModifyHdl - shot in foot: nPara " << + nPara << " >= aParaArray.size() " << aParaArray.size() << + " with nEdFocus " << nEdFocus << + " and aArgInput[nEdFocus].GetArgVal() '" << aArgInput[nEdFocus].GetArgVal() << "'"); + } + UpdateArgDesc( nEdFocus); + nActiveLine = static_cast<sal_uInt16>(nPara); + } + + ArgumentModified(); +} + + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/parawin.hxx b/formula/source/ui/dlg/parawin.hxx new file mode 100644 index 0000000000..b2fe7ece62 --- /dev/null +++ b/formula/source/ui/dlg/parawin.hxx @@ -0,0 +1,145 @@ +/* -*- 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 . + */ + +#pragma once + +#include <formula/funcutl.hxx> +#include <vcl/weld.hxx> +#include "ControlHelper.hxx" +#include <vector> + +namespace formula +{ + +#define NOT_FOUND 0xffff + +class IFunctionDescription; +class IControlReferenceHandler; + +class ParaWin +{ +private: + Link<ParaWin&,void> aFxLink; + Link<ParaWin&,void> aArgModifiedLink; + + ::std::vector<sal_uInt16> aVisibleArgMapping; + const IFunctionDescription* pFuncDesc; + IControlReferenceHandler* pMyParent; + sal_uInt16 nArgs; // unsuppressed arguments, may be >= VAR_ARGS to indicate repeating parameters + sal_uInt16 nMaxArgs; // max arguments, limited to supported number of arguments + vcl::Font aFntBold; + vcl::Font aFntLight; + + OUString m_sOptional; + OUString m_sRequired; + + sal_uInt16 nEdFocus; + sal_uInt16 nActiveLine; + + ArgInput aArgInput[4]; + OUString aDefaultString; + ::std::vector<OUString> aParaArray; + + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + + std::unique_ptr<weld::ScrolledWindow> m_xSlider; + std::unique_ptr<weld::Widget> m_xParamGrid; + std::unique_ptr<weld::Widget> m_xGrid; + + std::unique_ptr<weld::Label> m_xFtEditDesc; + std::unique_ptr<weld::Label> m_xFtArgName; + std::unique_ptr<weld::Label> m_xFtArgDesc; + + std::unique_ptr<weld::Button> m_xBtnFx1; + std::unique_ptr<weld::Button> m_xBtnFx2; + std::unique_ptr<weld::Button> m_xBtnFx3; + std::unique_ptr<weld::Button> m_xBtnFx4; + + std::unique_ptr<weld::Label> m_xFtArg1; + std::unique_ptr<weld::Label> m_xFtArg2; + std::unique_ptr<weld::Label> m_xFtArg3; + std::unique_ptr<weld::Label> m_xFtArg4; + + std::unique_ptr<ArgEdit> m_xEdArg1; + std::unique_ptr<ArgEdit> m_xEdArg2; + std::unique_ptr<ArgEdit> m_xEdArg3; + std::unique_ptr<ArgEdit> m_xEdArg4; + + std::unique_ptr<RefButton> m_xRefBtn1; + std::unique_ptr<RefButton> m_xRefBtn2; + std::unique_ptr<RefButton> m_xRefBtn3; + std::unique_ptr<RefButton> m_xRefBtn4; + + DECL_LINK( ScrollHdl, weld::ScrolledWindow&, void); + DECL_LINK( ModifyHdl, ArgInput&, void ); + DECL_LINK( GetEdFocusHdl, ArgInput&, void ); + DECL_LINK( GetFxFocusHdl, ArgInput&, void ); + DECL_LINK( GetFxHdl, ArgInput&, void ); + + void ArgumentModified(); + + void InitArgInput(sal_uInt16 nPos, weld::Label& rFtArg, weld::Button& rBtnFx, + ArgEdit& rEdArg, RefButton& rRefBtn); + + void SetArgumentDesc(const OUString& aText); + void SetArgumentText(const OUString& aText); + + + void SetArgName (sal_uInt16 no,const OUString &aArg); + void SetArgNameFont (sal_uInt16 no,const vcl::Font&); + + void UpdateArgDesc( sal_uInt16 nArg ); + void UpdateArgInput( sal_uInt16 nOffset, sal_uInt16 i ); + +public: + ParaWin(weld::Container* pParent, IControlReferenceHandler* _pDlg); + ~ParaWin(); + + void SetFunctionDesc(const IFunctionDescription* pFDesc); + void SetArgumentOffset(sal_uInt16 nOffset); + void SetEditDesc(const OUString& aText); + void UpdateParas(); + void ClearAll(); + + sal_uInt16 GetActiveLine() const { return nActiveLine;} + void SetActiveLine(sal_uInt16 no); + RefEdit* GetActiveEdit(); + OUString GetActiveArgName() const; + + OUString GetArgument(sal_uInt16 no); + void SetArgument(sal_uInt16 no, std::u16string_view aString); + void SetArgumentFonts(const vcl::Font& aBoldFont,const vcl::Font& aLightFont); + + void SetEdFocus(); // visible edit lines + sal_uInt16 GetSliderPos() const; + void SetSliderPos(sal_uInt16 nSliderPos); + + void SetArgModifiedHdl( const Link<ParaWin&,void>& rLink ) { aArgModifiedLink = rLink; } + void SetFxHdl( const Link<ParaWin&,void>& rLink ) { aFxLink = rLink; } + + void SliderMoved(); + + void Show() { m_xContainer->show(); } +}; + + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/structpg.cxx b/formula/source/ui/dlg/structpg.cxx new file mode 100644 index 0000000000..6cfc34a96c --- /dev/null +++ b/formula/source/ui/dlg/structpg.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <vcl/svapp.hxx> + +#include "structpg.hxx" +#include <formula/formula.hxx> +#include <formula/token.hxx> +#include <bitmaps.hlst> + +namespace formula +{ + +void StructPage::SetActiveFlag(bool bFlag) +{ + bActiveFlag = bFlag; +} + +StructPage::StructPage(weld::Container* pParent) + : m_xBuilder(Application::CreateBuilder(pParent, "formula/ui/structpage.ui")) + , m_xContainer(m_xBuilder->weld_container("StructPage")) + , m_xTlbStruct(m_xBuilder->weld_tree_view("struct")) + , maImgEnd(BMP_STR_END) + , maImgError(BMP_STR_ERROR) + , pSelectedToken(nullptr) + , bActiveFlag(false) +{ + m_xTlbStruct->set_size_request(m_xTlbStruct->get_approximate_digit_width() * 20, + m_xTlbStruct->get_height_rows(17)); + + m_xTlbStruct->connect_changed(LINK( this, StructPage, SelectHdl ) ); +} + +StructPage::~StructPage() +{ +} + +void StructPage::ClearStruct() +{ + SetActiveFlag(false); + m_xTlbStruct->clear(); +} + +bool StructPage::InsertEntry(const OUString& rText, const weld::TreeIter* pParent, + sal_uInt16 nFlag, int nPos, + const FormulaToken* pIFormulaToken, + weld::TreeIter& rRet) +{ + SetActiveFlag(false); + + OUString sId(weld::toId(pIFormulaToken)); + + bool bEntry = false; + switch (nFlag) + { + case STRUCT_FOLDER: + m_xTlbStruct->insert(pParent, nPos, &rText, &sId, nullptr, nullptr, + false, &rRet); + m_xTlbStruct->set_image(rRet, BMP_STR_OPEN); + bEntry = true; + break; + case STRUCT_END: + m_xTlbStruct->insert(pParent, nPos, &rText, &sId, nullptr, nullptr, + false, &rRet); + m_xTlbStruct->set_image(rRet, maImgEnd); + bEntry = true; + break; + case STRUCT_ERROR: + m_xTlbStruct->insert(pParent, nPos, &rText, &sId, nullptr, nullptr, + false, &rRet); + m_xTlbStruct->set_image(rRet, maImgError); + bEntry = true; + break; + } + + if (bEntry && pParent) + m_xTlbStruct->expand_row(*pParent); + return bEntry; +} + +OUString StructPage::GetEntryText(const weld::TreeIter* pEntry) const +{ + OUString aString; + if (pEntry) + aString = m_xTlbStruct->get_text(*pEntry); + return aString; +} + +const FormulaToken* StructPage::GetFunctionEntry(const weld::TreeIter* pEntry) +{ + if (!pEntry) + return nullptr; + + const FormulaToken * pToken = weld::fromId<const FormulaToken*>(m_xTlbStruct->get_id(*pEntry)); + if (pToken) + { + if ( !(pToken->IsFunction() || pToken->GetParamCount() > 1 ) ) + { + std::unique_ptr<weld::TreeIter> xParent(m_xTlbStruct->make_iterator(pEntry)); + if (!m_xTlbStruct->iter_parent(*xParent)) + return nullptr; + return GetFunctionEntry(xParent.get()); + } + else + { + return pToken; + } + } + return nullptr; +} + +IMPL_LINK(StructPage, SelectHdl, weld::TreeView&, rTlb, void) +{ + if (!GetActiveFlag()) + return; + + if (&rTlb == m_xTlbStruct.get()) + { + std::unique_ptr<weld::TreeIter> xCurEntry(m_xTlbStruct->make_iterator()); + if (m_xTlbStruct->get_cursor(xCurEntry.get())) + { + pSelectedToken = weld::fromId<const FormulaToken*>(m_xTlbStruct->get_id(*xCurEntry)); + if (pSelectedToken) + { + if ( !(pSelectedToken->IsFunction() || pSelectedToken->GetParamCount() > 1) ) + { + pSelectedToken = GetFunctionEntry(xCurEntry.get()); + } + } + } + } + + aSelLink.Call(*this); +} + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/formula/source/ui/dlg/structpg.hxx b/formula/source/ui/dlg/structpg.hxx new file mode 100644 index 0000000000..1164031899 --- /dev/null +++ b/formula/source/ui/dlg/structpg.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> + +namespace formula +{ + +class FormulaToken; + + +class StructPage final +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::TreeView> m_xTlbStruct; + + Link<StructPage&,void> aSelLink; + + OUString maImgEnd; + OUString maImgError; + + const FormulaToken* pSelectedToken; + bool bActiveFlag; + + DECL_LINK(SelectHdl, weld::TreeView&, void); + + const FormulaToken* GetFunctionEntry(const weld::TreeIter* pEntry); + + void SetActiveFlag(bool bFlag); + bool GetActiveFlag() const { return bActiveFlag;} + +public: + + explicit StructPage(weld::Container* pParent); + ~StructPage(); + + void ClearStruct(); + bool InsertEntry(const OUString& rText, const weld::TreeIter* pParent, + sal_uInt16 nFlag, int nPos, + const FormulaToken* pIFormulaToken, + weld::TreeIter& rRet); + + OUString GetEntryText(const weld::TreeIter* pEntry) const; + + void SetSelectionHdl( const Link<StructPage&,void>& rLink ) { aSelLink = rLink; } + + weld::TreeView& GetTlbStruct() const { return *m_xTlbStruct; } + + bool IsVisible() const { return m_xContainer->get_visible(); } +}; + +} // formula + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |