diff --git a/sc/CppunitTest_sc_ucalc_solver.mk b/sc/CppunitTest_sc_ucalc_solver.mk new file mode 100644 index 000000000000..6b1f66f9ccc7 --- /dev/null +++ b/sc/CppunitTest_sc_ucalc_solver.mk @@ -0,0 +1,75 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_use_common_precompiled_header,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_add_exception_objects,sc_ucalc_solver, \ + sc/qa/unit/ucalc_solver \ +)) + +$(eval $(call gb_CppunitTest_use_externals,sc_ucalc_solver, \ + boost_headers \ + mdds_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,sc_ucalc_solver, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + i18nlangtag \ + sal \ + sax \ + sc \ + scqahelper \ + sfx \ + subsequenttest \ + svl \ + svx \ + svxcore \ + test \ + tl \ + unotest \ + utl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_set_include,sc_ucalc_solver,\ + -I$(SRCDIR)/sc/source/ui/inc \ + -I$(SRCDIR)/sc/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_api,sc_ucalc_solver,\ + offapi \ + udkapi \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_use_ure,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_use_vcl,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_use_rdb,sc_ucalc_solver,services)) + +$(eval $(call gb_CppunitTest_use_components,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_use_configuration,sc_ucalc_solver)) + +$(eval $(call gb_CppunitTest_add_arguments,sc_ucalc_solver, \ + -env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}" \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk index 49c36cf27d3c..82bab43389c1 100644 --- a/sc/Library_sc.mk +++ b/sc/Library_sc.mk @@ -186,6 +186,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\ sc/source/core/data/segmenttree \ sc/source/core/data/sheetevents \ sc/source/core/data/simpleformulacalc \ + sc/source/core/data/SolverSettings \ sc/source/core/data/sortparam \ sc/source/core/data/stlpool \ sc/source/core/data/stlsheet \ diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk index e9710baf10ee..7531130809e8 100644 --- a/sc/Module_sc.mk +++ b/sc/Module_sc.mk @@ -54,6 +54,7 @@ $(eval $(call gb_Module_add_check_targets,sc,\ CppunitTest_sc_ucalc_range \ CppunitTest_sc_ucalc_sharedformula \ CppunitTest_sc_ucalc_sparkline \ + CppunitTest_sc_ucalc_solver \ CppunitTest_sc_ucalc_sort \ CppunitTest_sc_filters_test \ CppunitTest_sc_mark_test \ diff --git a/sc/inc/SolverSettings.hxx b/sc/inc/SolverSettings.hxx new file mode 100644 index 000000000000..ec1ef994a7b8 --- /dev/null +++ b/sc/inc/SolverSettings.hxx @@ -0,0 +1,209 @@ +/* -*- 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/. + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +class ScTable; +class ScDocShell; + +namespace sc +{ +// These values are MS compatible +enum ObjectiveType +{ + OT_MAXIMIZE = 1, + OT_MINIMIZE = 2, + OT_VALUE = 3 +}; + +enum SolverParameter +{ + SP_OBJ_CELL, // Objective cell + SP_OBJ_TYPE, // Objective type (max, min, value) + SP_OBJ_VAL, // Value (used when objective is of type "value") + SP_VAR_CELLS, // Variable cells + SP_CONSTR_COUNT, // Number of constraints (MSO only) + SP_LO_ENGINE, // Engine name used in LO + SP_MS_ENGINE, // Engine ID used in MSO + SP_INTEGER, // Assume all variables are integer (0: no, 1: yes) + SP_NON_NEGATIVE, // Assume non negativity (1: yes, 2: no) + SP_EPSILON_LEVEL, // Epsilon level + SP_LIMIT_BBDEPTH, // Branch and bound depth + SP_TIMEOUT, // Time limit to return a solution + SP_ALGORITHM // Algorithm used by the SwarmSolver (1, 2 or 3) +}; + +// Starts at 1 to maintain MS compatibility +enum ConstraintOperator +{ + CO_LESS_EQUAL = 1, + CO_EQUAL = 2, + CO_GREATER_EQUAL = 3, + CO_INTEGER = 4, + CO_BINARY = 5 +}; + +// Parts of a constraint +enum ConstraintPart +{ + CP_LEFT_HAND_SIDE, + CP_OPERATOR, + CP_RIGHT_HAND_SIDE +}; + +// Stores the information of a single constraint (condition) +struct ModelConstraint +{ + OUString aLeftStr; + ConstraintOperator nOperator; + OUString aRightStr; + + ModelConstraint() + : nOperator(CO_LESS_EQUAL) + { + } + bool IsDefault() const + { + return aLeftStr.isEmpty() && aRightStr.isEmpty() && nOperator == CO_LESS_EQUAL; + } +}; + +/* Class SolverSettings + * + * This class is used to load/save and manipulate solver settings in a Calc tab. + * + * During initialization, (see Initialize() method) all settings stored in the tab are loaded onto + * the object. Settings that are not defined use default values. + * + * Read/Write methods are private and are used internally to load/write solver settings from + * named ranges associated with the sheet. + * + * Get/Set methods are public methods used to change object properties (they do not save data + * to the file). + * + * The method SaveSolverSettings() is used to create the named ranges containing the current + * property values into the file. + * + */ + +class SolverSettings +{ +private: + ScTable& m_rTable; + ScDocument& m_rDoc; + ScDocShell* m_pDocShell; + + // Used to read/write the named ranges in the tab + ScRangeName* m_pRangeName; + + OUString m_sObjCell; + ObjectiveType m_eObjType; + OUString m_sObjVal; + OUString m_sVariableCells; + OUString m_sLOEngineName; + OUString m_sMSEngineId; + + // Solver engine options + OUString m_sInteger; + OUString m_sNonNegative; + OUString m_sEpsilonLevel; + OUString m_sLimitBBDepth; + OUString m_sTimeout; + OUString m_sAlgorithm; + css::uno::Sequence m_aEngineOptions; + + std::vector m_aConstraints; + + void Initialize(); + + // Used to create or read a single solver parameter based on its named range + bool ReadParamValue(SolverParameter eParam, OUString& rValue, bool bRemoveQuotes = false); + void WriteParamValue(SolverParameter eParam, OUString sValue, bool bQuoted = false); + + // Creates or reads all constraints stored in named ranges + void ReadConstraints(); + void WriteConstraints(); + + // Used to create or get a single constraint part + bool ReadConstraintPart(ConstraintPart ePart, tools::Long nIndex, OUString& rValue); + void WriteConstraintPart(ConstraintPart ePart, tools::Long nIndex, OUString sValue); + + // Creates or reads all named ranges associated with solver engine options + void ReadEngine(); + void WriteEngine(); + + void DeleteAllNamedRanges(); + + // Maps solver parameters to named ranges + std::map m_mNamedRanges + = { { SP_OBJ_CELL, "solver_opt" }, { SP_OBJ_TYPE, "solver_typ" }, + { SP_OBJ_VAL, "solver_val" }, { SP_VAR_CELLS, "solver_adj" }, + { SP_CONSTR_COUNT, "solver_num" }, { SP_LO_ENGINE, "solver_lo_eng" }, + { SP_MS_ENGINE, "solver_eng" }, { SP_INTEGER, "solver_int" }, + { SP_NON_NEGATIVE, "solver_neg" }, { SP_EPSILON_LEVEL, "solver_eps" }, + { SP_LIMIT_BBDEPTH, "solver_bbd" }, { SP_TIMEOUT, "solver_tim" }, + { SP_ALGORITHM, "solver_alg" } }; + + // Maps LO solver implementation names to MS engine codes + std::map SolverNamesToExcelEngines = { + { "com.sun.star.comp.Calc.CoinMPSolver", "2" }, // Simplex LP + { "com.sun.star.comp.Calc.LpsolveSolver", "2" }, // Simplex LP + { "com.sun.star.comp.Calc.SwarmSolver", "1" } // GRG Nonlinear + }; + + // Maps MS solver engine codes to LO solver implementation names + std::map SolverCodesToLOEngines = { + { "1", "com.sun.star.comp.Calc.SwarmSolver" }, // GRG Nonlinear + { "2", "com.sun.star.comp.Calc.CoinMPSolver" }, // Simplex LP + { "3", "com.sun.star.comp.Calc.SwarmSolver" } // Evolutionary + }; + + // Maps LO solver parameters to named ranges to be used + // NonNegative: for MS compatibility, use 1 for selected and 2 for not selected + typedef std::vector> TParamInfo; + std::map SolverParamNames + = { { "Integer", { SP_INTEGER, "solver_int", "bool" } }, + { "NonNegative", { SP_NON_NEGATIVE, "solver_neg", "bool" } }, + { "EpsilonLevel", { SP_EPSILON_LEVEL, "solver_eps", "int" } }, + { "LimitBBDepth", { SP_LIMIT_BBDEPTH, "solver_bbd", "bool" } }, + { "Timeout", { SP_TIMEOUT, "solver_tim", "int" } }, + { "Algorithm", { SP_ALGORITHM, "solver_alg", "int" } } }; + + // Stores the roots used for named ranges of constraint parts + // Items here must be in the same order as in ConstraintPart enum + std::vector m_aConstraintParts{ "solver_lhs", "solver_rel", "solver_rhs" }; + +public: + /* A SolverSettings object is linked to the ScTable where solver parameters + * are located and saved to */ + SolverSettings(ScTable& pTable); + + SC_DLLPUBLIC OUString GetParameter(SolverParameter eParam); + SC_DLLPUBLIC void SetParameter(SolverParameter eParam, OUString sValue); + SC_DLLPUBLIC ObjectiveType GetObjectiveType() { return m_eObjType; } + SC_DLLPUBLIC void SetObjectiveType(ObjectiveType eType); + SC_DLLPUBLIC void GetEngineOptions(css::uno::Sequence& aOptions); + SC_DLLPUBLIC void SetEngineOptions(css::uno::Sequence& aOptions); + SC_DLLPUBLIC std::vector GetConstraints() { return m_aConstraints; } + SC_DLLPUBLIC void SetConstraints(std::vector aConstraints); + + SC_DLLPUBLIC void SaveSolverSettings(); + SC_DLLPUBLIC void ResetToDefaults(); +}; + +} // namespace sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index 9c6e376acca5..f044eaf77f78 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -2691,7 +2691,7 @@ private: bool HasPartOfMerged( const ScRange& rRange ); public: - ScTable* FetchTable( SCTAB nTab ); + SC_DLLPUBLIC ScTable* FetchTable( SCTAB nTab ); const ScTable* FetchTable( SCTAB nTab ) const; ScRefCellValue GetRefCellValue( const ScAddress& rPos ); diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx index b5fa4e9ec334..5eefb5f95c38 100644 --- a/sc/inc/table.hxx +++ b/sc/inc/table.hxx @@ -33,6 +33,7 @@ #include "document.hxx" #include "drwlayer.hxx" #include "SparklineList.hxx" +#include "SolverSettings.hxx" #include #include @@ -257,6 +258,9 @@ private: /** this is touched from formula group threading context */ std::atomic bStreamValid; + // Solver settings in current tab + std::shared_ptr m_pSolverSettings; + // Default attributes for the unallocated columns. ScColumnData aDefaultColData; @@ -442,6 +446,8 @@ public: void SetFormula( SCCOL nCol, SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram ); + SC_DLLPUBLIC std::shared_ptr GetSolverSettings(); + /** * Takes ownership of pCell * diff --git a/sc/qa/unit/ucalc_solver.cxx b/sc/qa/unit/ucalc_solver.cxx new file mode 100644 index 000000000000..7a8d76cc7534 --- /dev/null +++ b/sc/qa/unit/ucalc_solver.cxx @@ -0,0 +1,133 @@ +/* -*- 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 +#include "helper/qahelper.hxx" +#include +#include +#include + +using namespace sc; + +class SolverTest : public ScModelTestBase +{ +public: + SolverTest() + : ScModelTestBase("sc/qa/unit/data") + { + } + + std::vector CreateConstraintsModelA(); + void TestConstraintsModelA(SolverSettings* pSettings); +}; + +// Creates a simple set of constraints for testing +std::vector SolverTest::CreateConstraintsModelA() +{ + std::vector aConstraints; + + ModelConstraint aConstr1; + aConstr1.aLeftStr = "C1:C10"; + aConstr1.nOperator = CO_LESS_EQUAL; + aConstr1.aRightStr = "100"; + aConstraints.push_back(aConstr1); + + ModelConstraint aConstr2; + aConstr2.aLeftStr = "F5"; + aConstr2.nOperator = CO_EQUAL; + aConstr2.aRightStr = "500"; + aConstraints.push_back(aConstr2); + + ModelConstraint aConstr3; + aConstr3.aLeftStr = "D1:D5"; + aConstr3.nOperator = CO_BINARY; + aConstr3.aRightStr = ""; + aConstraints.push_back(aConstr3); + + return aConstraints; +} + +// Tests the contents of the three constraints +void SolverTest::TestConstraintsModelA(SolverSettings* pSettings) +{ + std::vector aConstraints = pSettings->GetConstraints(); + + CPPUNIT_ASSERT_EQUAL(OUString("C1:C10"), aConstraints[0].aLeftStr); + CPPUNIT_ASSERT_EQUAL(CO_LESS_EQUAL, aConstraints[0].nOperator); + CPPUNIT_ASSERT_EQUAL(OUString("100"), aConstraints[0].aRightStr); + + CPPUNIT_ASSERT_EQUAL(OUString("F5"), aConstraints[1].aLeftStr); + CPPUNIT_ASSERT_EQUAL(CO_EQUAL, aConstraints[1].nOperator); + CPPUNIT_ASSERT_EQUAL(OUString("500"), aConstraints[1].aRightStr); + + CPPUNIT_ASSERT_EQUAL(OUString("D1:D5"), aConstraints[2].aLeftStr); + CPPUNIT_ASSERT_EQUAL(CO_BINARY, aConstraints[2].nOperator); + CPPUNIT_ASSERT_EQUAL(OUString(""), aConstraints[2].aRightStr); +} + +/* This test creates a model in a single tab and test if the model info + * is correctly stored in the object + */ +CPPUNIT_TEST_FIXTURE(SolverTest, testSingleModel) +{ + createScDoc(); + ScDocument* pDoc = getScDoc(); + ScTable* pTable = pDoc->FetchTable(0); + std::shared_ptr pSettings = pTable->GetSolverSettings(); + CPPUNIT_ASSERT(pSettings); + + // Test solver default settings on an empty tab + // Here we only test default settings that are not engine-dependent + CPPUNIT_ASSERT_EQUAL(OUString(""), pSettings->GetParameter(SP_OBJ_CELL)); + CPPUNIT_ASSERT_EQUAL(static_cast(OT_MAXIMIZE), + pSettings->GetParameter(SP_OBJ_TYPE).toInt32()); + CPPUNIT_ASSERT_EQUAL(OUString(""), pSettings->GetParameter(SP_OBJ_VAL)); + CPPUNIT_ASSERT_EQUAL(OUString(""), pSettings->GetParameter(SP_VAR_CELLS)); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pSettings->GetParameter(SP_CONSTR_COUNT).toInt32()); + + // Create a simple model + pSettings->SetParameter(SP_OBJ_CELL, OUString("A1")); + pSettings->SetParameter(SP_OBJ_TYPE, OUString::number(OT_MINIMIZE)); + pSettings->SetParameter(SP_OBJ_VAL, OUString::number(0)); + pSettings->SetParameter(SP_VAR_CELLS, OUString("D1:D5")); + std::vector aConstraints = CreateConstraintsModelA(); + pSettings->SetConstraints(aConstraints); + + // Test if the model parameters were set + CPPUNIT_ASSERT_EQUAL(OUString("A1"), pSettings->GetParameter(SP_OBJ_CELL)); + CPPUNIT_ASSERT_EQUAL(static_cast(OT_MINIMIZE), + pSettings->GetParameter(SP_OBJ_TYPE).toInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("0"), pSettings->GetParameter(SP_OBJ_VAL)); + CPPUNIT_ASSERT_EQUAL(OUString("D1:D5"), pSettings->GetParameter(SP_VAR_CELLS)); + + // Test if the constraints were correctly set before saving + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pSettings->GetParameter(SP_CONSTR_COUNT).toInt32()); + TestConstraintsModelA(pSettings.get()); + + // Save and reload the file + pSettings->SaveSolverSettings(); + saveAndReload("calc8"); + pDoc = getScDoc(); + pTable = pDoc->FetchTable(0); + pSettings = pTable->GetSolverSettings(); + CPPUNIT_ASSERT(pSettings); + + // Test if the model parameters remain set in the file + CPPUNIT_ASSERT_EQUAL(OUString("A1"), pSettings->GetParameter(SP_OBJ_CELL)); + CPPUNIT_ASSERT_EQUAL(static_cast(OT_MINIMIZE), + pSettings->GetParameter(SP_OBJ_TYPE).toInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("0"), pSettings->GetParameter(SP_OBJ_VAL)); + CPPUNIT_ASSERT_EQUAL(OUString("D1:D5"), pSettings->GetParameter(SP_VAR_CELLS)); + + // Test if the constraints remain correct after saving + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pSettings->GetParameter(SP_CONSTR_COUNT).toInt32()); + TestConstraintsModelA(pSettings.get()); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/source/core/data/SolverSettings.cxx b/sc/source/core/data/SolverSettings.cxx new file mode 100644 index 000000000000..bf1662576d50 --- /dev/null +++ b/sc/source/core/data/SolverSettings.cxx @@ -0,0 +1,504 @@ +/* -*- 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 +#include +#include +#include +#include +#include + +namespace sc +{ +SolverSettings::SolverSettings(ScTable& rTable) + : m_rTable(rTable) + , m_rDoc(m_rTable.GetDoc()) + , m_pDocShell(dynamic_cast(m_rDoc.GetDocumentShell())) +{ + // Get the named range manager for this tab + std::map rRangeMap; + m_rDoc.GetRangeNameMap(rRangeMap); + m_pRangeName = rRangeMap.find(m_rTable.GetName())->second; + + Initialize(); +} + +void SolverSettings::Initialize() +{ + // Assign default values for the solver parameters + ResetToDefaults(); + + // Read the parameter values in the sheet + ReadParamValue(SP_OBJ_CELL, m_sObjCell); + ReadParamValue(SP_OBJ_VAL, m_sObjVal); + ReadParamValue(SP_VAR_CELLS, m_sVariableCells); + + // Read the objective type + OUString sObjType; + if (ReadParamValue(SP_OBJ_TYPE, sObjType)) + { + switch (sObjType.toInt32()) + { + case 1: + m_eObjType = ObjectiveType::OT_MAXIMIZE; + break; + case 2: + m_eObjType = ObjectiveType::OT_MINIMIZE; + break; + case 3: + m_eObjType = ObjectiveType::OT_VALUE; + break; + default: + m_eObjType = ObjectiveType::OT_MAXIMIZE; + } + } + + // Read all constraints in the tab + ReadConstraints(); + + // Read the solver engine being used + ReadEngine(); + + // Read engine options + ReadParamValue(SP_INTEGER, m_sInteger); + ReadParamValue(SP_NON_NEGATIVE, m_sNonNegative); + ReadParamValue(SP_EPSILON_LEVEL, m_sEpsilonLevel); + ReadParamValue(SP_LIMIT_BBDEPTH, m_sLimitBBDepth); + ReadParamValue(SP_TIMEOUT, m_sTimeout); + ReadParamValue(SP_ALGORITHM, m_sAlgorithm); +} + +// Returns the current value of the parameter in the object as a string +OUString SolverSettings::GetParameter(SolverParameter eParam) +{ + switch (eParam) + { + case SP_OBJ_CELL: + return m_sObjCell; + break; + case SP_OBJ_TYPE: + return OUString::number(m_eObjType); + break; + case SP_OBJ_VAL: + return m_sObjVal; + break; + case SP_VAR_CELLS: + return m_sVariableCells; + break; + case SP_CONSTR_COUNT: + return OUString::number(m_aConstraints.size()); + break; + case SP_LO_ENGINE: + return m_sLOEngineName; + break; + case SP_MS_ENGINE: + return m_sMSEngineId; + break; + case SP_INTEGER: + return m_sInteger; + break; + case SP_NON_NEGATIVE: + return m_sNonNegative; + break; + case SP_EPSILON_LEVEL: + return m_sEpsilonLevel; + break; + case SP_LIMIT_BBDEPTH: + return m_sLimitBBDepth; + break; + case SP_TIMEOUT: + return m_sTimeout; + break; + case SP_ALGORITHM: + return m_sAlgorithm; + break; + default: + return ""; + } +} + +// Sets the value of a single solver parameter in the object +void SolverSettings::SetParameter(SolverParameter eParam, OUString sValue) +{ + switch (eParam) + { + case SP_OBJ_CELL: + m_sObjCell = sValue; + break; + case SP_OBJ_TYPE: + { + sal_Int32 nObjType = sValue.toInt32(); + switch (nObjType) + { + case OT_MAXIMIZE: + m_eObjType = ObjectiveType::OT_MAXIMIZE; + break; + case OT_MINIMIZE: + m_eObjType = ObjectiveType::OT_MINIMIZE; + break; + case OT_VALUE: + m_eObjType = ObjectiveType::OT_VALUE; + break; + default: + m_eObjType = ObjectiveType::OT_MAXIMIZE; + break; + } + break; + } + case SP_OBJ_VAL: + m_sObjVal = sValue; + break; + case SP_VAR_CELLS: + m_sVariableCells = sValue; + break; + case SP_LO_ENGINE: + m_sLOEngineName = sValue; + break; + case SP_INTEGER: + { + if (sValue == "0" || sValue == "1") + m_sInteger = sValue; + } + break; + case SP_NON_NEGATIVE: + { + if (sValue == "1" || sValue == "2") + m_sNonNegative = sValue; + } + break; + case SP_EPSILON_LEVEL: + m_sEpsilonLevel = sValue; + break; + case SP_LIMIT_BBDEPTH: + m_sLimitBBDepth = sValue; + break; + case SP_TIMEOUT: + m_sTimeout = sValue; + break; + case SP_ALGORITHM: + { + if (sValue == "1" || sValue == "2" || sValue == "3") + m_sAlgorithm = sValue; + } + break; + default: + break; + } +} + +void SolverSettings::SetObjectiveType(ObjectiveType eType) { m_eObjType = eType; } + +// Loads all constraints in the tab +void SolverSettings::ReadConstraints() +{ + // Condition indices start at 1 for MS compatibility + // The number of "lhs", "rel" and "rhs" entries will always be the same + tools::Long nConstraint = 1; + m_aConstraints.clear(); + OUString sValue; + + while (ReadConstraintPart(CP_LEFT_HAND_SIDE, nConstraint, sValue)) + { + // Left hand side + ModelConstraint aNewCondition; + aNewCondition.aLeftStr = sValue; + + // Right hand side + if (ReadConstraintPart(CP_RIGHT_HAND_SIDE, nConstraint, sValue)) + aNewCondition.aRightStr = sValue; + + // Relation (operator) + if (ReadConstraintPart(CP_OPERATOR, nConstraint, sValue)) + aNewCondition.nOperator = static_cast(sValue.toInt32()); + + m_aConstraints.push_back(aNewCondition); + nConstraint++; + } +} + +// Writes all constraints to the file +void SolverSettings::WriteConstraints() +{ + // Condition indices start at 1 for MS compatibility + tools::Long nConstraint = 1; + + for (auto& aConstraint : m_aConstraints) + { + // Left hand side + WriteConstraintPart(CP_LEFT_HAND_SIDE, nConstraint, aConstraint.aLeftStr); + // Relation (operator) + WriteConstraintPart(CP_OPERATOR, nConstraint, OUString::number(aConstraint.nOperator)); + // Right hand side + WriteConstraintPart(CP_RIGHT_HAND_SIDE, nConstraint, aConstraint.aRightStr); + nConstraint++; + } +} + +// Write a single constraint part to the file +void SolverSettings::WriteConstraintPart(ConstraintPart ePart, tools::Long nIndex, OUString sValue) +{ + // Empty named ranges cannot be written to the file (this corrupts MS files) + if (sValue.isEmpty()) + return; + + OUString sRange = m_aConstraintParts[ePart] + OUString::number(nIndex); + ScRangeData* pNewEntry = new ScRangeData(m_rDoc, sRange, sValue); + m_pRangeName->insert(pNewEntry); +} + +// Reads a single constraint part from its associated named range; returns false if the named +// range does not exist in the file +bool SolverSettings::ReadConstraintPart(ConstraintPart ePart, tools::Long nIndex, OUString& rValue) +{ + OUString sRange = m_aConstraintParts[ePart] + OUString::number(nIndex); + ScRangeData* pRangeData + = m_pRangeName->findByUpperName(ScGlobal::getCharClass().uppercase(sRange)); + if (pRangeData) + { + rValue = pRangeData->GetSymbol(); + return true; + } + return false; +} + +/* Reads the engine name parameter as informed in the file in the format used in LO. + * If only a MS engine is informed, then it is converted to a LO-equivalent engine + */ +void SolverSettings::ReadEngine() +{ + if (!ReadParamValue(SP_LO_ENGINE, m_sLOEngineName, true)) + { + // If no engine is defined, use CoinMP solver as default + m_sLOEngineName = "com.sun.star.comp.Calc.CoinMPSolver"; + } + + if (SolverNamesToExcelEngines.count(m_sLOEngineName)) + { + // Find equivalent MS engine code + m_sMSEngineId = SolverNamesToExcelEngines.find(m_sLOEngineName)->second; + } +} + +// Write solver LO and MS-equivalent engine names +void SolverSettings::WriteEngine() +{ + WriteParamValue(SP_LO_ENGINE, m_sLOEngineName, true); + // Find equivalent MS engine code + if (SolverNamesToExcelEngines.count(m_sLOEngineName)) + { + m_sMSEngineId = SolverNamesToExcelEngines.find(m_sLOEngineName)->second; + WriteParamValue(SP_MS_ENGINE, m_sMSEngineId); + } +} + +// Assigns a new constraints vector +void SolverSettings::SetConstraints(std::vector aConstraints) +{ + m_aConstraints = std::move(aConstraints); +} + +// Saves all solver settings into the file +void SolverSettings::SaveSolverSettings() +{ + // Before saving, remove all existing named ranges related to the solver + DeleteAllNamedRanges(); + + WriteParamValue(SP_OBJ_CELL, m_sObjCell); + WriteParamValue(SP_OBJ_TYPE, OUString::number(m_eObjType)); + WriteParamValue(SP_OBJ_VAL, m_sObjVal); + WriteParamValue(SP_VAR_CELLS, m_sVariableCells); + + WriteConstraints(); + WriteEngine(); + + sal_Int32 nConstrCount = m_aConstraints.size(); + WriteParamValue(SP_CONSTR_COUNT, OUString::number(nConstrCount)); + + WriteParamValue(SP_INTEGER, m_sInteger); + WriteParamValue(SP_NON_NEGATIVE, m_sNonNegative); + WriteParamValue(SP_EPSILON_LEVEL, m_sEpsilonLevel); + WriteParamValue(SP_LIMIT_BBDEPTH, m_sLimitBBDepth); + WriteParamValue(SP_TIMEOUT, m_sTimeout); + WriteParamValue(SP_ALGORITHM, m_sAlgorithm); + + if (m_pDocShell) + m_pDocShell->SetDocumentModified(); +} + +/* Reads the current value of the parameter in the named range into rValue + * If the value does not exist, the rValue is left unchanged + * This is private because it is only used during initialization + * Returns true if the value exits; returns false otherwise + */ +bool SolverSettings::ReadParamValue(SolverParameter eParam, OUString& rValue, bool bRemoveQuotes) +{ + OUString sRange = m_mNamedRanges.find(eParam)->second; + ScRangeData* pRangeData + = m_pRangeName->findByUpperName(ScGlobal::getCharClass().uppercase(sRange)); + if (pRangeData) + { + rValue = pRangeData->GetSymbol(); + if (bRemoveQuotes) + ScGlobal::EraseQuotes(rValue, '"'); + return true; + } + return false; +} + +/* Writes a parameter value to the file as a named range. + * Argument bQuoted indicates whether the value should be enclosed with quotes or not (used + * for string expressions that must be enclosed with quotes) + */ +void SolverSettings::WriteParamValue(SolverParameter eParam, OUString sValue, bool bQuoted) +{ + // Empty parameters cannot be written to the file (this corrupts MS files) + // There's no problem if the parameter is missing both for LO and MS + if (sValue.isEmpty()) + return; + + if (bQuoted) + ScGlobal::AddQuotes(sValue, '"'); + + OUString sRange = m_mNamedRanges.find(eParam)->second; + ScRangeData* pNewEntry = new ScRangeData(m_rDoc, sRange, sValue); + m_pRangeName->insert(pNewEntry); +} + +void SolverSettings::GetEngineOptions(css::uno::Sequence& aOptions) +{ + sal_Int32 nOptionsSize = aOptions.getLength(); + auto pParamValues = aOptions.getArray(); + + for (auto i = 0; i < nOptionsSize; i++) + { + css::beans::PropertyValue aProp = aOptions[i]; + OUString sLOParamName = aProp.Name; + // Only try to get the parameter value if it is an expected parameter name + if (SolverParamNames.count(sLOParamName)) + { + TParamInfo aParamInfo; + aParamInfo = SolverParamNames.find(sLOParamName)->second; + SolverParameter eParamId = std::get(aParamInfo[0]); + OUString sParamType = std::get(aParamInfo[2]); + OUString sParamValue = GetParameter(eParamId); + if (sParamType == "int") + { + css::uno::Any nValue(sParamValue.toInt32()); + pParamValues[i] = css::beans::PropertyValue(sLOParamName, -1, nValue, + css::beans::PropertyState_DIRECT_VALUE); + } + if (sParamType == "bool") + { + // The parameter NonNegative is a special case for MS compatibility + // It uses "1" for "true" and "2" for "false" + bool bTmpValue; + if (sLOParamName == "NonNegative") + bTmpValue = sParamValue == "1" ? true : false; + else + bTmpValue = sParamValue.toBoolean(); + + css::uno::Any bValue(bTmpValue); + pParamValues[i] = css::beans::PropertyValue(sLOParamName, -1, bValue, + css::beans::PropertyState_DIRECT_VALUE); + } + } + } +} + +// Updates the object members related to solver engine options using aOptions info +void SolverSettings::SetEngineOptions(css::uno::Sequence& aOptions) +{ + sal_Int32 nOptionsSize = aOptions.getLength(); + + for (auto i = 0; i < nOptionsSize; i++) + { + css::beans::PropertyValue aProp = aOptions[i]; + OUString sLOParamName = aProp.Name; + // Only try to set the parameter value if it is an expected parameter name + if (SolverParamNames.count(sLOParamName)) + { + TParamInfo aParamInfo; + aParamInfo = SolverParamNames.find(sLOParamName)->second; + SolverParameter eParamId = std::get(aParamInfo[0]); + OUString sParamType = std::get(aParamInfo[2]); + if (sParamType == "int") + { + sal_Int32 nValue; + aProp.Value >>= nValue; + SetParameter(eParamId, OUString::number(nValue)); + } + if (sParamType == "bool") + { + bool bValue; + aProp.Value >>= bValue; + if (sLOParamName == "NonNegative") + { + // The parameter NonNegative is a special case for MS compatibility + // It uses "1" for "true" and "2" for "false" + if (bValue) + SetParameter(eParamId, OUString::number(1)); + else + SetParameter(eParamId, OUString::number(2)); + } + else + { + SetParameter(eParamId, OUString::number(sal_Int32(bValue))); + } + } + } + } +} + +// Deletes all named ranges in the current tab that are related to the solver (i.e. start with "solver_") +void SolverSettings::DeleteAllNamedRanges() +{ + std::vector aItemsToErase; + + // Indices in m_pRangeName start at 1 + for (size_t i = 1; i <= m_pRangeName->size(); ++i) + { + ScRangeData* pData = m_pRangeName->findByIndex(i); + if (pData && pData->GetName().startsWith("solver_")) + aItemsToErase.push_back(pData); + } + + for (auto pItem : aItemsToErase) + m_pRangeName->erase(*pItem); +} + +/* Sets all solver parameters to their default values and clear all constraints. + * This method only resets the object properties, but does not save changes to the + * document. To save changes, call SaveSolverSettings(). + */ +void SolverSettings::ResetToDefaults() +{ + m_sObjCell = ""; + m_eObjType = ObjectiveType::OT_MAXIMIZE; + m_sObjVal = ""; + m_sVariableCells = ""; + m_sMSEngineId = "1"; + + // The default solver engine is the first implementation available + css::uno::Sequence aEngineNames; + css::uno::Sequence aDescriptions; + ScSolverUtil::GetImplementations(aEngineNames, aDescriptions); + m_sLOEngineName = aEngineNames[0]; + + // Default engine options + m_aEngineOptions = ScSolverUtil::GetDefaults(m_sLOEngineName); + + // Default solver engine options + SetEngineOptions(m_aEngineOptions); + + // Clear all constraints + m_aConstraints.clear(); +} + +} // namespace sc diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx index 7755bc6176de..230833e8b7a6 100644 --- a/sc/source/core/data/table7.cxx +++ b/sc/source/core/data/table7.cxx @@ -655,4 +655,12 @@ void ScTable::CollectBroadcasterState(sc::BroadcasterState& rState) const pCol->CollectBroadcasterState(rState); } +std::shared_ptr ScTable::GetSolverSettings() +{ + if (!m_pSolverSettings) + m_pSolverSettings = std::make_shared(*this); + + return m_pSolverSettings; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh.cxx b/sc/source/ui/docshell/docsh.cxx index f5fad1fd1f3b..b817d2db4308 100644 --- a/sc/source/ui/docshell/docsh.cxx +++ b/sc/source/ui/docshell/docsh.cxx @@ -115,7 +115,6 @@ #include #include #include -#include #include #include #include @@ -2948,7 +2947,6 @@ ScDocShell::~ScDocShell() m_pPaintLockData.reset(); - m_pSolverSaveData.reset(); m_pSheetSaveData.reset(); m_pFormatSaveData.reset(); m_pOldAutoDBRange.reset(); @@ -3112,11 +3110,6 @@ weld::Window* ScDocShell::GetActiveDialogParent() return Application::GetDefDialogParent(); } -void ScDocShell::SetSolverSaveData( std::unique_ptr pData ) -{ - m_pSolverSaveData = std::move(pData); -} - ScSheetSaveData* ScDocShell::GetSheetSaveData() { if (!m_pSheetSaveData) diff --git a/sc/source/ui/inc/docsh.hxx b/sc/source/ui/inc/docsh.hxx index 6df3ebc010e9..d92c93e73ff3 100644 --- a/sc/source/ui/inc/docsh.hxx +++ b/sc/source/ui/inc/docsh.hxx @@ -54,7 +54,6 @@ class ScPaintLockData; class ScChangeAction; class ScImportOptions; class ScDocShellModificator; -class ScOptSolverSave; class ScSheetSaveData; class ScFlatBoolRowSegments; struct ScColWidthParam; @@ -103,7 +102,6 @@ class SC_DLLPUBLIC ScDocShell final: public SfxObjectShell, public SfxListener std::unique_ptr m_pAutoStyleList; std::unique_ptr m_pPaintLockData; - std::unique_ptr m_pSolverSaveData; std::unique_ptr m_pSheetSaveData; std::unique_ptr m_pFormatSaveData; @@ -412,8 +410,6 @@ public: virtual HiddenInformation GetHiddenInformationState( HiddenInformation nStates ) override; - const ScOptSolverSave* GetSolverSaveData() const { return m_pSolverSaveData.get(); } // may be null - void SetSolverSaveData( std::unique_ptr pData ); ScSheetSaveData* GetSheetSaveData(); ScFormatSaveData* GetFormatSaveData(); diff --git a/sc/source/ui/inc/optsolver.hxx b/sc/source/ui/inc/optsolver.hxx index 94a12f9d859c..4f28a59d5fcd 100644 --- a/sc/source/ui/inc/optsolver.hxx +++ b/sc/source/ui/inc/optsolver.hxx @@ -22,6 +22,7 @@ #include #include "anyrefdg.hxx" #include "docsh.hxx" +#include #include #include @@ -44,49 +45,6 @@ protected: DECL_LINK(KeyInputHdl, const KeyEvent&, bool); }; -/// The dialog's content for a row, not yet parsed -struct ScOptConditionRow -{ - OUString aLeftStr; - sal_uInt16 nOperator; - OUString aRightStr; - - ScOptConditionRow() : nOperator(0) {} - bool IsDefault() const { return aLeftStr.isEmpty() && aRightStr.isEmpty() && nOperator == 0; } -}; - -/// All settings from the dialog, saved with the DocShell for the next call -class ScOptSolverSave -{ - OUString maObjective; - bool mbMax; - bool mbMin; - bool mbValue; - OUString maTarget; - OUString maVariable; - std::vector maConditions; - OUString maEngine; - css::uno::Sequence maProperties; - -public: - ScOptSolverSave( OUString aObjective, bool bMax, bool bMin, bool bValue, - OUString aTarget, OUString aVariable, - std::vector&& rConditions, - OUString aEngine, - const css::uno::Sequence& rProperties ); - - const OUString& GetObjective() const { return maObjective; } - bool GetMax() const { return mbMax; } - bool GetMin() const { return mbMin; } - bool GetValue() const { return mbValue; } - const OUString& GetTarget() const { return maTarget; } - const OUString& GetVariable() const { return maVariable; } - const std::vector& GetConditions() const { return maConditions; } - const OUString& GetEngine() const { return maEngine; } - const css::uno::Sequence& GetProperties() const - { return maProperties; } -}; - class ScSolverOptionsDialog; class ScOptSolverDlg : public ScAnyRefDlgController @@ -110,7 +68,7 @@ private: const SCTAB mnCurTab; bool mbDlgLostFocus; - std::vector maConditions; + std::vector m_aConditions; tools::Long nScrollPos; css::uno::Sequence maImplNames; @@ -183,6 +141,7 @@ private: std::unique_ptr m_xContents; std::shared_ptr m_xOptDlg; + std::shared_ptr m_pSolverSettings; void Init(const ScAddress& rCursorPos); bool CallSolver(); @@ -192,6 +151,11 @@ private: bool ParseRef( ScRange& rRange, const OUString& rInput, bool bAllowRange ); bool FindTimeout( sal_Int32& rTimeout ); void ShowError( bool bCondition, formula::RefEdit* pFocus ); + void LoadSolverSettings(); + void SaveSolverSettings(); + bool IsEngineAvailable(std::u16string_view sEngineName); + + static sc::ConstraintOperator OperatorIndexToConstraintOperator(sal_Int32 nIndex); DECL_LINK( BtnHdl, weld::Button&, void ); DECL_LINK( DelBtnHdl, weld::Button&, void ); diff --git a/sc/source/ui/miscdlgs/optsolver.cxx b/sc/source/ui/miscdlgs/optsolver.cxx index 42fdf95ae1a7..bf40b0092062 100644 --- a/sc/source/ui/miscdlgs/optsolver.cxx +++ b/sc/source/ui/miscdlgs/optsolver.cxx @@ -35,8 +35,9 @@ #include #include #include - +#include #include +#include #include #include @@ -132,23 +133,6 @@ IMPL_LINK(ScCursorRefEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) return formula::RefEdit::KeyInput(rKEvt); } -ScOptSolverSave::ScOptSolverSave( OUString aObjective, bool bMax, bool bMin, bool bValue, - OUString aTarget, OUString aVariable, - std::vector&& rConditions, - OUString aEngine, - const uno::Sequence& rProperties ) : - maObjective( std::move(aObjective) ), - mbMax( bMax ), - mbMin( bMin ), - mbValue( bValue ), - maTarget( std::move(aTarget) ), - maVariable( std::move(aVariable) ), - maConditions( std::move(rConditions) ), - maEngine( std::move(aEngine) ), - maProperties( rProperties ) -{ -} - ScOptSolverDlg::ScOptSolverDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent, ScDocShell* pDocSh, const ScAddress& aCursorPos) : ScAnyRefDlgController(pB, pCW, pParent, "modules/scalc/ui/solverdlg.ui", "SolverDialog") @@ -205,6 +189,7 @@ ScOptSolverDlg::ScOptSolverDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Windo , m_xBtnResetAll(m_xBuilder->weld_button("resetall")) , m_xResultFT(m_xBuilder->weld_label("result")) , m_xContents(m_xBuilder->weld_widget("grid")) + , m_pSolverSettings(mrDoc.FetchTable(mnCurTab)->GetSolverSettings()) { m_xEdObjectiveCell->SetReferences(this, m_xFtObjectiveCell.get()); m_xRBObjectiveCell->SetReferences(this, m_xEdObjectiveCell.get()); @@ -336,33 +321,20 @@ void ScOptSolverDlg::Init(const ScAddress& rCursorPos) // get available solver implementations //! sort by descriptions? ScSolverUtil::GetImplementations( maImplNames, maDescriptions ); - bool bImplHasElements = maImplNames.hasElements(); - const ScOptSolverSave* pOldData = mpDocShell->GetSolverSaveData(); - if ( pOldData ) - { - m_xEdObjectiveCell->SetRefString( pOldData->GetObjective() ); - m_xRbMax->set_active( pOldData->GetMax() ); - m_xRbMin->set_active( pOldData->GetMin() ); - m_xRbValue->set_active( pOldData->GetValue() ); - m_xEdTargetValue->SetRefString( pOldData->GetTarget() ); - m_xEdVariableCells->SetRefString( pOldData->GetVariable() ); - maConditions = pOldData->GetConditions(); - maEngine = pOldData->GetEngine(); - maProperties = pOldData->GetProperties(); - } - else - { - m_xRbMax->set_active(true); - OUString aCursorStr; - if ( !mrDoc.GetRangeAtBlock( ScRange(rCursorPos), aCursorStr ) ) - aCursorStr = rCursorPos.Format(ScRefFlags::ADDR_ABS, nullptr, mrDoc.GetAddressConvention()); - m_xEdObjectiveCell->SetRefString( aCursorStr ); - if ( bImplHasElements ) - maEngine = maImplNames[0]; // use first implementation - } + // Load existing settings stored in the tab + LoadSolverSettings(); ShowConditions(); + // If no objective cell has been loaded, then use the selected cell + if (m_xEdObjectiveCell->GetText().isEmpty()) + { + OUString aCursorStr; + if (!mrDoc.GetRangeAtBlock(ScRange(rCursorPos), aCursorStr)) + aCursorStr = rCursorPos.Format(ScRefFlags::ADDR_ABS, nullptr, mrDoc.GetAddressConvention()); + m_xEdObjectiveCell->SetRefString(aCursorStr); + } + m_xEdObjectiveCell->GrabFocus(); mpEdActive = m_xEdObjectiveCell.get(); } @@ -371,23 +343,23 @@ void ScOptSolverDlg::ReadConditions() { for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow ) { - ScOptConditionRow aRowEntry; + sc::ModelConstraint aRowEntry; aRowEntry.aLeftStr = mpLeftEdit[nRow]->GetText(); aRowEntry.aRightStr = mpRightEdit[nRow]->GetText(); - aRowEntry.nOperator = mpOperator[nRow]->get_active(); + aRowEntry.nOperator = OperatorIndexToConstraintOperator(mpOperator[nRow]->get_active()); tools::Long nVecPos = nScrollPos + nRow; - if ( nVecPos >= static_cast(maConditions.size()) && !aRowEntry.IsDefault() ) - maConditions.resize( nVecPos + 1 ); + if ( nVecPos >= static_cast(m_aConditions.size()) && !aRowEntry.IsDefault() ) + m_aConditions.resize( nVecPos + 1 ); - if ( nVecPos < static_cast(maConditions.size()) ) - maConditions[nVecPos] = aRowEntry; + if ( nVecPos < static_cast(m_aConditions.size()) ) + m_aConditions[nVecPos] = aRowEntry; // remove default entries at the end - size_t nSize = maConditions.size(); - while ( nSize > 0 && maConditions[ nSize-1 ].IsDefault() ) + size_t nSize = m_aConditions.size(); + while ( nSize > 0 && m_aConditions[ nSize-1 ].IsDefault() ) --nSize; - maConditions.resize( nSize ); + m_aConditions.resize( nSize ); } } @@ -395,20 +367,20 @@ void ScOptSolverDlg::ShowConditions() { for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow ) { - ScOptConditionRow aRowEntry; + sc::ModelConstraint aRowEntry; tools::Long nVecPos = nScrollPos + nRow; - if ( nVecPos < static_cast(maConditions.size()) ) - aRowEntry = maConditions[nVecPos]; + if ( nVecPos < static_cast(m_aConditions.size()) ) + aRowEntry = m_aConditions[nVecPos]; mpLeftEdit[nRow]->SetRefString( aRowEntry.aLeftStr ); mpRightEdit[nRow]->SetRefString( aRowEntry.aRightStr ); - mpOperator[nRow]->set_active( aRowEntry.nOperator ); + mpOperator[nRow]->set_active( aRowEntry.nOperator - 1); } // allow to scroll one page behind the visible or stored rows tools::Long nVisible = nScrollPos + EDIT_ROW_COUNT; - tools::Long nMax = std::max( nVisible, static_cast(maConditions.size()) ); + tools::Long nMax = std::max( nVisible, static_cast(m_aConditions.size()) ); m_xScrollBar->vadjustment_configure(nScrollPos, 0, nMax + EDIT_ROW_COUNT, 1, EDIT_ROW_COUNT - 1, EDIT_ROW_COUNT); @@ -420,7 +392,7 @@ void ScOptSolverDlg::EnableButtons() for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow ) { tools::Long nVecPos = nScrollPos + nRow; - mpDelButton[nRow]->set_sensitive(nVecPos < static_cast(maConditions.size())); + mpDelButton[nRow]->set_sensitive(nVecPos < static_cast(m_aConditions.size())); } } @@ -503,6 +475,74 @@ bool ScOptSolverDlg::IsRefInputMode() const return mpEdActive != nullptr; } +// Loads solver settings into the dialog +void ScOptSolverDlg::LoadSolverSettings() +{ + m_xEdObjectiveCell->SetRefString(m_pSolverSettings->GetParameter(sc::SP_OBJ_CELL)); + m_xEdTargetValue->SetRefString(m_pSolverSettings->GetParameter(sc::SP_OBJ_VAL)); + m_xEdVariableCells->SetRefString(m_pSolverSettings->GetParameter(sc::SP_VAR_CELLS)); + + // Objective type + sc::ObjectiveType eType = m_pSolverSettings->GetObjectiveType(); + switch (eType) + { + case sc::OT_MAXIMIZE : m_xRbMax->set_active(true); break; + case sc::OT_MINIMIZE : m_xRbMin->set_active(true); break; + case sc::OT_VALUE : m_xRbValue->set_active(true); break; + } + + // Model constraints + m_aConditions = m_pSolverSettings->GetConstraints(); + + // Loads solver engine name + // If the solver engine in the current settings are not supported, use the first available + maEngine = m_pSolverSettings->GetParameter(sc::SP_LO_ENGINE); + if (!IsEngineAvailable(maEngine)) + { + maEngine = maImplNames[0]; + m_pSolverSettings->SetParameter(sc::SP_LO_ENGINE, maEngine); + } + + // Query current engine options + maProperties = ScSolverUtil::GetDefaults(maEngine); + m_pSolverSettings->GetEngineOptions(maProperties); +} + +// Set solver settings and save them +void ScOptSolverDlg::SaveSolverSettings() +{ + m_pSolverSettings->SetParameter(sc::SP_OBJ_CELL, m_xEdObjectiveCell->GetText()); + m_pSolverSettings->SetParameter(sc::SP_OBJ_VAL, m_xEdTargetValue->GetText()); + m_pSolverSettings->SetParameter(sc::SP_VAR_CELLS, m_xEdVariableCells->GetText()); + + // Objective type + if (m_xRbMax->get_active()) + m_pSolverSettings->SetObjectiveType(sc::OT_MAXIMIZE); + else if (m_xRbMin->get_active()) + m_pSolverSettings->SetObjectiveType(sc::OT_MINIMIZE); + else if (m_xRbValue->get_active()) + m_pSolverSettings->SetObjectiveType(sc::OT_VALUE); + + // Model constraints + m_pSolverSettings->SetConstraints(m_aConditions); + + // Solver engine name + m_pSolverSettings->SetParameter(sc::SP_LO_ENGINE, maEngine); + + // Solver engine options + m_pSolverSettings->SetEngineOptions(maProperties); + + // Effectively save settings to file + m_pSolverSettings->SaveSolverSettings(); +} + +// Test if a LO engine implementation exists +bool ScOptSolverDlg::IsEngineAvailable(std::u16string_view sEngineName) +{ + auto nIndex = comphelper::findValue(maImplNames, sEngineName); + return nIndex != -1; +} + // Handler: IMPL_LINK(ScOptSolverDlg, BtnHdl, weld::Button&, rBtn, void) @@ -523,10 +563,7 @@ IMPL_LINK(ScOptSolverDlg, BtnHdl, weld::Button&, rBtn, void) { // Close: write dialog settings to DocShell for subsequent calls ReadConditions(); - std::unique_ptr pSave( new ScOptSolverSave( - m_xEdObjectiveCell->GetText(), m_xRbMax->get_active(), m_xRbMin->get_active(), m_xRbValue->get_active(), - m_xEdTargetValue->GetText(), m_xEdVariableCells->GetText(), std::vector(maConditions), maEngine, maProperties ) ); - mpDocShell->SetSolverSaveData( std::move(pSave) ); + SaveSolverSettings(); response(RET_CLOSE); } else @@ -560,11 +597,7 @@ IMPL_LINK(ScOptSolverDlg, BtnHdl, weld::Button&, rBtn, void) maProperties = ScSolverUtil::GetDefaults( maEngine ); // Clear all conditions (Constraints) - maConditions.clear(); - std::unique_ptr pEmpty( new ScOptSolverSave( - sEmpty, true, false, false, - sEmpty, sEmpty, std::vector(maConditions), maEngine, maProperties ) ); - mpDocShell->SetSolverSaveData( std::move(pEmpty) ); + m_aConditions.clear(); ShowConditions(); m_xRbMax->set_active(true); @@ -653,9 +686,9 @@ IMPL_LINK(ScOptSolverDlg, DelBtnHdl, weld::Button&, rBtn, void) ReadConditions(); tools::Long nVecPos = nScrollPos + nRow; - if ( nVecPos < static_cast(maConditions.size()) ) + if ( nVecPos < static_cast(m_aConditions.size()) ) { - maConditions.erase( maConditions.begin() + nVecPos ); + m_aConditions.erase( m_aConditions.begin() + nVecPos ); ShowConditions(); if ( bHadFocus && !rBtn.get_sensitive() ) @@ -761,6 +794,20 @@ IMPL_LINK( ScOptSolverDlg, CursorDownHdl, ScCursorRefEdit&, rEdit, void ) } } +// Converts the position of the operator in the dropdown menu to a ConstraintOperator type +sc::ConstraintOperator ScOptSolverDlg::OperatorIndexToConstraintOperator(sal_Int32 nIndex) +{ + switch(nIndex) + { + case 0 : return sc::CO_LESS_EQUAL; break; + case 1 : return sc::CO_EQUAL; break; + case 2 : return sc::CO_GREATER_EQUAL; break; + case 3 : return sc::CO_INTEGER; break; + case 4 : return sc::CO_BINARY; break; + default : return sc::CO_LESS_EQUAL; break; + } +} + void ScOptSolverDlg::ShowError( bool bCondition, formula::RefEdit* pFocus ) { OUString aMessage = bCondition ? maConditionError : maInputError; @@ -870,13 +917,15 @@ bool ScOptSolverDlg::CallSolver() // return true -> close dialog after cal uno::Sequence aConstraints; sal_Int32 nConstrPos = 0; - for ( const auto& rConstr : maConditions ) + for ( const auto& rConstr : m_aConditions ) { if ( !rConstr.aLeftStr.isEmpty() ) { sheet::SolverConstraint aConstraint; - // order of list box entries must match enum values - aConstraint.Operator = static_cast(rConstr.nOperator); + // Order of list box entries must match enum values. + // The enum SolverConstraintOperator starts at zero, whereas ConstraintOperator starts at 1 + // hence we need to subtract -1 here + aConstraint.Operator = static_cast(rConstr.nOperator - 1); ScRange aLeftRange; if ( !ParseRef( aLeftRange, rConstr.aLeftStr, true ) )