From 5d86cc81fef7d538440c630a503a5d94c6a3be4c Mon Sep 17 00:00:00 2001 From: Rafael Lima Date: Fri, 3 Mar 2023 14:35:51 +0000 Subject: [PATCH] tdf#38948 Save solver settings to file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch implements the mechanism to save solver settings in LO Calc as well as export/import them from XLSX files. In MS Excel solver settings are saved as hidden named ranges, so in this patch I used the same strategy to save solver settings in Calc, i.e. by creating named ranges to store the solver settings using the same terminology used in Excel. With this we gain the ability to save solver settings by tab, as well as export/import since we already have "named ranges/expressions" import/export implemented in LO. Change-Id: Id41bca261dc3cd8e6888643f0ed6a97b26097876 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148112 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl --- sc/CppunitTest_sc_ucalc_solver.mk | 75 ++++ sc/Library_sc.mk | 1 + sc/Module_sc.mk | 1 + sc/inc/SolverSettings.hxx | 209 ++++++++++ sc/inc/document.hxx | 2 +- sc/inc/table.hxx | 6 + sc/qa/unit/ucalc_solver.cxx | 133 +++++++ sc/source/core/data/SolverSettings.cxx | 504 +++++++++++++++++++++++++ sc/source/core/data/table7.cxx | 8 + sc/source/ui/docshell/docsh.cxx | 7 - sc/source/ui/inc/docsh.hxx | 4 - sc/source/ui/inc/optsolver.hxx | 52 +-- sc/source/ui/miscdlgs/optsolver.cxx | 191 ++++++---- 13 files changed, 1066 insertions(+), 127 deletions(-) create mode 100644 sc/CppunitTest_sc_ucalc_solver.mk create mode 100644 sc/inc/SolverSettings.hxx create mode 100644 sc/qa/unit/ucalc_solver.cxx create mode 100644 sc/source/core/data/SolverSettings.cxx 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 ) )