tdf#38948 Save solver settings to file

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 <quikee@gmail.com>
diff --git a/sc/CppunitTest_sc_ucalc_solver.mk b/sc/CppunitTest_sc_ucalc_solver.mk
new file mode 100644
index 0000000..6b1f66f
--- /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 49c36cf..82bab43 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 e9710ba..7531130 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 0000000..ec1ef99
--- /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 <memory>
#include <utility>
#include <variant>
#include <rtl/ustring.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>

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<css::beans::PropertyValue> m_aEngineOptions;

    std::vector<ModelConstraint> 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<SolverParameter, OUString> 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<OUString, OUString> 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<OUString, OUString> 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<std::variant<OUString, SolverParameter>> TParamInfo;
    std::map<OUString, TParamInfo> 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<OUString> 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<css::beans::PropertyValue>& aOptions);
    SC_DLLPUBLIC void SetEngineOptions(css::uno::Sequence<css::beans::PropertyValue>& aOptions);
    SC_DLLPUBLIC std::vector<ModelConstraint> GetConstraints() { return m_aConstraints; }
    SC_DLLPUBLIC void SetConstraints(std::vector<ModelConstraint> 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 9c6e376..f044eaf 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 b5fa4e9..5eefb5f 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 <algorithm>
#include <atomic>
@@ -257,6 +258,9 @@ private:
    /** this is touched from formula group threading context */
    std::atomic<bool> bStreamValid;

    // Solver settings in current tab
    std::shared_ptr<sc::SolverSettings> 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<sc::SolverSettings> 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 0000000..7a8d76c
--- /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 <sal/config.h>
#include "helper/qahelper.hxx"
#include <document.hxx>
#include <table.hxx>
#include <SolverSettings.hxx>

using namespace sc;

class SolverTest : public ScModelTestBase
{
public:
    SolverTest()
        : ScModelTestBase("sc/qa/unit/data")
    {
    }

    std::vector<ModelConstraint> CreateConstraintsModelA();
    void TestConstraintsModelA(SolverSettings* pSettings);
};

// Creates a simple set of constraints for testing
std::vector<ModelConstraint> SolverTest::CreateConstraintsModelA()
{
    std::vector<ModelConstraint> 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<ModelConstraint> 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<sc::SolverSettings> 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<sal_Int32>(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<ModelConstraint> 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<sal_Int32>(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<sal_Int32>(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 0000000..bf16625
--- /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 <global.hxx>
#include <table.hxx>
#include <docsh.hxx>
#include <solverutil.hxx>
#include <unotools/charclass.hxx>
#include <SolverSettings.hxx>

namespace sc
{
SolverSettings::SolverSettings(ScTable& rTable)
    : m_rTable(rTable)
    , m_rDoc(m_rTable.GetDoc())
    , m_pDocShell(dynamic_cast<ScDocShell*>(m_rDoc.GetDocumentShell()))
{
    // Get the named range manager for this tab
    std::map<OUString, ScRangeName*> 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<sc::ConstraintOperator>(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<ModelConstraint> 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<css::beans::PropertyValue>& 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<SolverParameter>(aParamInfo[0]);
            OUString sParamType = std::get<OUString>(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<css::beans::PropertyValue>& 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<SolverParameter>(aParamInfo[0]);
            OUString sParamType = std::get<OUString>(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<ScRangeData*> 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<OUString> aEngineNames;
    css::uno::Sequence<OUString> 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 7755bc6..230833e 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<sc::SolverSettings> ScTable::GetSolverSettings()
{
    if (!m_pSolverSettings)
        m_pSolverSettings = std::make_shared<sc::SolverSettings>(*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 f5fad1f..b817d2db 100644
--- a/sc/source/ui/docshell/docsh.cxx
+++ b/sc/source/ui/docshell/docsh.cxx
@@ -115,7 +115,6 @@
#include <scextopt.hxx>
#include <compiler.hxx>
#include <warnpassword.hxx>
#include <optsolver.hxx>
#include <sheetdata.hxx>
#include <table.hxx>
#include <tabprotection.hxx>
@@ -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<ScOptSolverSave> 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 6df3ebc..d92c93e 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<ScAutoStyleList>    m_pAutoStyleList;
    std::unique_ptr<ScPaintLockData>    m_pPaintLockData;
    std::unique_ptr<ScOptSolverSave>    m_pSolverSaveData;
    std::unique_ptr<ScSheetSaveData>    m_pSheetSaveData;
    std::unique_ptr<ScFormatSaveData>   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<ScOptSolverSave> pData );
    ScSheetSaveData* GetSheetSaveData();
    ScFormatSaveData* GetFormatSaveData();

diff --git a/sc/source/ui/inc/optsolver.hxx b/sc/source/ui/inc/optsolver.hxx
index 94a12f9..4f28a59 100644
--- a/sc/source/ui/inc/optsolver.hxx
+++ b/sc/source/ui/inc/optsolver.hxx
@@ -22,6 +22,7 @@
#include <address.hxx>
#include "anyrefdg.hxx"
#include "docsh.hxx"
#include <SolverSettings.hxx>
#include <com/sun/star/uno/Sequence.hxx>

#include <string_view>
@@ -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<ScOptConditionRow> maConditions;
    OUString    maEngine;
    css::uno::Sequence<css::beans::PropertyValue> maProperties;

public:
            ScOptSolverSave( OUString aObjective, bool bMax, bool bMin, bool bValue,
                             OUString aTarget, OUString aVariable,
                             std::vector<ScOptConditionRow>&& rConditions,
                             OUString aEngine,
                             const css::uno::Sequence<css::beans::PropertyValue>& 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<ScOptConditionRow>& GetConditions() const { return maConditions; }
    const OUString&   GetEngine() const       { return maEngine; }
    const css::uno::Sequence<css::beans::PropertyValue>& GetProperties() const
                                            { return maProperties; }
};

class ScSolverOptionsDialog;

class ScOptSolverDlg : public ScAnyRefDlgController
@@ -110,7 +68,7 @@ private:
    const SCTAB     mnCurTab;
    bool            mbDlgLostFocus;

    std::vector<ScOptConditionRow> maConditions;
    std::vector<sc::ModelConstraint> m_aConditions;
    tools::Long            nScrollPos;

    css::uno::Sequence<OUString> maImplNames;
@@ -183,6 +141,7 @@ private:
    std::unique_ptr<weld::Widget> m_xContents;

    std::shared_ptr<ScSolverOptionsDialog> m_xOptDlg;
    std::shared_ptr<sc::SolverSettings> 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 42fdf95..bf40b00 100644
--- a/sc/source/ui/miscdlgs/optsolver.cxx
+++ b/sc/source/ui/miscdlgs/optsolver.cxx
@@ -35,8 +35,9 @@
#include <solverutil.hxx>
#include <globstr.hrc>
#include <scresid.hxx>

#include <comphelper/sequence.hxx>
#include <optsolver.hxx>
#include <table.hxx>

#include <com/sun/star/sheet/SolverConstraint.hpp>
#include <com/sun/star/sheet/SolverConstraintOperator.hpp>
@@ -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<ScOptConditionRow>&& rConditions,
                             OUString aEngine,
                             const uno::Sequence<beans::PropertyValue>& 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<tools::Long>(maConditions.size()) && !aRowEntry.IsDefault() )
            maConditions.resize( nVecPos + 1 );
        if ( nVecPos >= static_cast<tools::Long>(m_aConditions.size()) && !aRowEntry.IsDefault() )
            m_aConditions.resize( nVecPos + 1 );

        if ( nVecPos < static_cast<tools::Long>(maConditions.size()) )
            maConditions[nVecPos] = aRowEntry;
        if ( nVecPos < static_cast<tools::Long>(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<tools::Long>(maConditions.size()) )
            aRowEntry = maConditions[nVecPos];
        if ( nVecPos < static_cast<tools::Long>(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<tools::Long>(maConditions.size()) );
    tools::Long nMax = std::max( nVisible, static_cast<tools::Long>(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<tools::Long>(maConditions.size()));
        mpDelButton[nRow]->set_sensitive(nVecPos < static_cast<tools::Long>(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<ScOptSolverSave> 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<ScOptSolverSave> 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<tools::Long>(maConditions.size()) )
            if ( nVecPos < static_cast<tools::Long>(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<sheet::SolverConstraint> 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<sheet::SolverConstraintOperator>(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<sheet::SolverConstraintOperator>(rConstr.nOperator - 1);

            ScRange aLeftRange;
            if ( !ParseRef( aLeftRange, rConstr.aLeftStr, true ) )