tdf#160062 Uno API to set solver settings at the sheet level

This patch creates the API necessary to create solver models via macros, so that the model is visible in the Solver dialog, and the model is saved to the file.

This patch also includes a comprehensive unit test in scsolverobj.cxx.

Visit the bug ticket for a file with some examples of such macros.

Change-Id: Ib9cfbbdaab8243cd6b901a2bcd99e46c27be97e5
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171769
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
This commit is contained in:
Rafael Lima 2024-08-12 20:12:20 +02:00 committed by Tomaž Vajngerl
parent 646479d234
commit 911a57c4d6
19 changed files with 1595 additions and 4 deletions

View file

@ -1245,6 +1245,7 @@ $(eval $(call gb_UnoApi_add_idlfiles_noheader,offapi,com/sun/star/sheet,\
SheetRangesQuery \
SheetSortDescriptor \
SheetSortDescriptor2 \
SolverSettings \
Spreadsheet \
SpreadsheetDocument \
SpreadsheetDocumentSettings \
@ -3457,6 +3458,7 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/sheet,\
LocalizedName \
MemberResult \
MemberResultFlags \
ModelConstraint \
MoveDirection \
NamedRangeFlag \
NameToken \
@ -3469,6 +3471,8 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/sheet,\
SingleReference \
SolverConstraint \
SolverConstraintOperator \
SolverStatus \
SolverObjectiveType \
SpreadsheetViewObjectsMode \
StatusBarFunction \
SubTotalColumn \
@ -3590,6 +3594,7 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/sheet,\
XSheetPastable \
XSolver \
XSolverDescription \
XSolverSettings \
XSpreadsheet \
XSpreadsheetDocument \
XSpreadsheetView \

View file

@ -0,0 +1,46 @@
/* -*- 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/.
*
*/
module com { module sun { module star { module sheet {
/** This is used to specify a constraint for a solver model.
* In this struct, both sides of the model constraint can be expressed as cell ranges.
* Note that com::sun::star::sheet::SolverConstraint only supports single cell addresses
*
* @since LibreOffice 25.2
*/
struct ModelConstraint
{
/** The range of cells in the left side of the constraint
* Must be either:
* 1) com::sun::star::table::CellRangeAddress
* 2) A valid string representing a cell range
*/
any Left;
/** Constraint operator
*/
SolverConstraintOperator Operator;
/** The range of cells or numeric value in the right side of the constraint
* Must be either:
* 1) com::sun::star::table::CellRangeAddress
* 2) A valid string representing a cell range
* 3) A numeric value
*/
any Right;
};
}; }; }; };
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -0,0 +1,34 @@
/* -*- 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/.
*
*/
module com { module sun { module star { module sheet {
/** Specifies the objective type of the solver model
*
* @since LibreOffice 25.2
*/
constants SolverObjectiveType
{
/// Maximizes the objective function.
const byte MAXIMIZE = 0;
/// Minimizes the objective function.
const byte MINIMIZE = 1;
/// Finds a solution where the objective function has a given value
const byte VALUE = 2;
};
}; }; }; };
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -0,0 +1,33 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
module com { module sun { module star { module sheet {
/** A solver model defined in a sheet and accessible via the solver dialog
*
* @since LibreOffice 25.2
*/
service SolverSettings: XSolverSettings;
}; }; }; };
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -0,0 +1,41 @@
/* -*- 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/.
*
*/
module com { module sun { module star { module sheet {
/** Describes the status reported by the solver
* Used in com::sun::star::sheet::XSolverSettings
*
* @since LibreOffice 25.2
*/
constants SolverStatus
{
/// No status to report. Happens when the solver has not been called.
const byte NONE = 0;
/// The solver finished running and a solution was successfully found
const byte SOLUTION_FOUND = 1;
/// A parse error occurred after running the solver
const byte PARSE_ERROR = 2;
/// An error occurred while loading or running the solver
const byte ENGINE_ERROR = 3;
/// The solver finished running but a solution was not found
const byte SOLUTION_NOT_FOUND = 4;
};
}; }; }; };
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -0,0 +1,97 @@
/* -*- 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/.
*
*/
module com { module sun { module star { module sheet {
/** Provides access to the solver settings stored in a specific Calc sheet
*
* @since LibreOffice 25.2
*/
interface XSolverSettings: com::sun::star::uno::XInterface
{
/** Determines the model objective function type
Possible values defined in css::sheet::SolverObjectiveType
*/
[attribute] byte ObjectiveType;
/** Cell pointing to the objective function of the model
*/
[attribute] any ObjectiveCell;
/** Value or cell address used in the "Value of" field.
To set this attribute, the value must be either:
1) a numeric value;
2) a string representing the address of a single cell or;
3) a CellAddress struct instance
*/
[attribute] any GoalValue;
/** Set the solver engine using its implementation name
*/
[attribute] string Engine;
/** Returns a sequence of strings containing all available solver implementation names
*/
[attribute, readonly] sequence<string> AvailableEngines;
/** Variable cells in the model, represented as a sequence of cell ranges
*/
[attribute] sequence<any> VariableCells;
/** Constraints of the solver model
*/
[attribute] sequence<ModelConstraint> Constraints;
/** Returns the number of constraints in the solver model
*/
[attribute, readonly] long ConstraintCount;
/** Solver engine options expressed as a sequence of property values.
Each solver engine has its own set of supported properties.
Only the options supported by the current solver engine are returned.
*/
[attribute] sequence<com::sun::star::beans::PropertyValue> EngineOptions;
/** Last reported solver status. This can be used f.i. to check if an
error occurred or if a solution was found.
Possible values defined in css::sheet::SolverStatus
*/
[attribute, readonly] byte Status;
/** Stores the last error message reported after calling the "solve()" method
*/
[attribute, readonly] string ErrorMessage;
/** Set this attrivute to True to suppress dialogs shown the method "solve()"
*/
[attribute] boolean SuppressDialog;
/** Resets the solver model to its defaults settings
*/
void reset();
/** Run the solver model
Check the values of the Status and ErrorMessage attributes to know
what happened after calling this method
*/
void solve();
/** Save the solver model to the file.
The next time the file is loaded, the model will be in the sheet where it was created.
*/
void saveToFile();
};
}; }; }; };
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -0,0 +1,14 @@
# -*- 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 sc_unoapi_common,solverobj))
# vim: set noet sw=4 ts=4:

View file

@ -616,6 +616,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
sc/source/ui/unoobj/ChartTools \
sc/source/ui/unoobj/servuno \
sc/source/ui/unoobj/shapeuno \
sc/source/ui/unoobj/solveruno \
sc/source/ui/unoobj/srchuno \
sc/source/ui/unoobj/styleuno \
sc/source/ui/unoobj/targuno \

View file

@ -224,6 +224,7 @@ $(eval $(call gb_Module_add_subsequentcheck_targets,sc,\
CppunitTest_sc_shapeobj \
CppunitTest_sc_sheetlinkobj \
CppunitTest_sc_sheetlinksobj \
CppunitTest_sc_solverobj \
CppunitTest_sc_sortdescriptorbaseobj \
CppunitTest_sc_spreadsheetsettings \
CppunitTest_sc_spreadsheetsettingsobj \

View file

@ -295,7 +295,8 @@ public:
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 void
SetEngineOptions(const css::uno::Sequence<css::beans::PropertyValue>& aOptions);
SC_DLLPUBLIC std::vector<ModelConstraint> GetConstraints() { return m_aConstraints; }
SC_DLLPUBLIC void SetConstraints(std::vector<ModelConstraint> aConstraints);

View file

@ -522,7 +522,11 @@
#define STR_NOFORMULA NC_("STR_NOFORMULA", "Formula cell must contain a formula.")
#define STR_SOLVER_ENGINE_ERROR NC_("STR_SOLVER_ENGINE_ERROR", "An internal error occurred while running the solver engine.")
#define STR_INVALIDINPUT NC_("STR_INVALIDINPUT", "Invalid input.")
#define STR_INVALIDCONDITION NC_("STR_INVALIDCONDITION", "Invalid condition.")
#define STR_INVALIDCONDITION NC_("STR_INVALIDCONDITION", "Invalid constraint.")
#define STR_SOLVER_LOAD_FAIL NC_("STR_SOLVER_LOAD_FAIL", "Unable to load solver engine.")
#define STR_SOLVER_OBJCELL_FAIL NC_("STR_SOLVER_OBJCELL_FAIL", "Invalid objective cell.")
#define STR_SOLVER_VARCELL_FAIL NC_("STR_SOLVER_VARCELL_FAIL", "Invalid variable cells.")
#define STR_SOLVER_TARGETVALUE_FAIL NC_("STR_SOLVER_TARGETVALUE_FAIL", "Invalid target value.")
#define STR_QUERYREMOVE NC_("STR_QUERYREMOVE", "Should the entry\n#\nbe deleted?")
#define STR_COPYLIST NC_("STR_COPYLIST", "Copy List")
#define STR_COPYFROM NC_("STR_COPYFROM", "List from")

99
sc/inc/solveruno.hxx Normal file
View file

@ -0,0 +1,99 @@
/* -*- 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 <cppuhelper/implbase.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/sheet/XSolverSettings.hpp>
#include <com/sun/star/sheet/SolverObjectiveType.hpp>
#include <com/sun/star/sheet/SolverStatus.hpp>
#include <com/sun/star/sheet/SolverConstraint.hpp>
namespace com::sun::star::container
{
class XNamed;
}
namespace sc
{
class SolverSettings;
}
class ScDocument;
class ScDocShell;
class ScTable;
class ScRange;
class ScRangeList;
class ScSolverSettings final
: public ::cppu::WeakImplHelper<css::sheet::XSolverSettings, css::lang::XServiceInfo>
{
private:
ScDocShell* m_pDocShell;
ScDocument& m_rDoc;
css::uno::Reference<css::container::XNamed> m_xSheet;
// Status uses constants in css::uno::sheet::SolverStatus
sal_Int8 m_nStatus;
bool m_bSuppressDialog;
OUString m_sErrorMessage;
ScTable* m_pTable;
std::shared_ptr<sc::SolverSettings> m_pSettings;
// Parses a reference string (named ranges are also parsed)
// If bAllowRange is "false" then only single cell ranges are acceptable,
// which is the case of the objective cell
bool ParseRef(ScRange& rRange, const OUString& rInput, bool bAllowRange);
// Parses a reference string composed of various ranges
bool ParseWithNames(ScRangeList& rRanges, std::u16string_view rInput);
static void ShowErrorMessage(const OUString& rMessage);
public:
ScSolverSettings(ScDocShell* pDocSh, css::uno::Reference<css::container::XNamed> xSheet);
~ScSolverSettings();
// XSolverSettings attributes
virtual sal_Int8 SAL_CALL getObjectiveType() override;
virtual void SAL_CALL setObjectiveType(sal_Int8 aObjType) override;
virtual css::uno::Any SAL_CALL getObjectiveCell() override;
virtual void SAL_CALL setObjectiveCell(const css::uno::Any& aValue) override;
virtual css::uno::Any SAL_CALL getGoalValue() override;
virtual void SAL_CALL setGoalValue(const css::uno::Any& aValue) override;
virtual OUString SAL_CALL getEngine() override;
virtual void SAL_CALL setEngine(const OUString& sEngine) override;
virtual css::uno::Sequence<OUString> SAL_CALL getAvailableEngines() override;
virtual css::uno::Sequence<css::uno::Any> SAL_CALL getVariableCells() override;
virtual void SAL_CALL
setVariableCells(const css::uno::Sequence<css::uno::Any>& aRanges) override;
virtual css::uno::Sequence<css::sheet::ModelConstraint> SAL_CALL getConstraints() override;
virtual void SAL_CALL
setConstraints(const css::uno::Sequence<css::sheet::ModelConstraint>& aConstraints) override;
virtual sal_Int32 SAL_CALL getConstraintCount() override;
virtual css::uno::Sequence<css::beans::PropertyValue> SAL_CALL getEngineOptions() override;
virtual void SAL_CALL
setEngineOptions(const css::uno::Sequence<css::beans::PropertyValue>& rProps) override;
virtual sal_Int8 SAL_CALL getStatus() override;
virtual OUString SAL_CALL getErrorMessage() override;
virtual sal_Bool SAL_CALL getSuppressDialog() override;
virtual void SAL_CALL setSuppressDialog(sal_Bool bSuppress) override;
// XSolverSettings methods
virtual void SAL_CALL reset() override;
virtual void SAL_CALL solve() override;
virtual void SAL_CALL saveToFile() override;
// XServiceInfo
virtual OUString SAL_CALL getImplementationName() override;
virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
};

View file

@ -48,6 +48,7 @@ inline constexpr OUString SC_UNO_SHEETLINKS = u"SheetLinks"_ustr;
inline constexpr OUString SC_UNO_FORBIDDEN = u"ForbiddenCharacters"_ustr;
inline constexpr OUString SC_UNO_HASDRAWPAGES = u"HasDrawPages"_ustr;
inline constexpr OUString SC_UNO_THEME = u"Theme"_ustr;
inline constexpr OUString SC_UNO_SOLVERSETTINGS = u"SolverSettings"_ustr;
// CharacterProperties
inline constexpr OUString SC_UNONAME_CCOLOR = u"CharColor"_ustr;

View file

@ -74,7 +74,8 @@
#define SC_WID_UNO_FORMATID ( SC_WID_UNO_START + 45 )
#define SC_WID_UNO_FORMRT2 ( SC_WID_UNO_START + 46 )
#define SC_WID_UNO_CELLCONTENTTYPE ( SC_WID_UNO_START + 47 )
#define SC_WID_UNO_END ( SC_WID_UNO_START + 47 )
#define SC_WID_UNO_SOLVERSETTINGS ( SC_WID_UNO_START + 48 )
#define SC_WID_UNO_END ( SC_WID_UNO_START + 48 )
inline bool IsScUnoWid( sal_uInt16 nWid )
{

View file

@ -0,0 +1,218 @@
/* -*- 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 <test/unoapi_test.hxx>
#include <comphelper/propertyvalue.hxx>
#include <com/sun/star/sheet/SolverObjectiveType.hpp>
#include <com/sun/star/sheet/XSolverSettings.hpp>
#include <com/sun/star/sheet/XSpreadsheet.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <com/sun/star/table/CellAddress.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/sheet/ModelConstraint.hpp>
#include <com/sun/star/sheet/SolverConstraintOperator.hpp>
#include <com/sun/star/table/CellRangeAddress.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using namespace css;
namespace sc_apitest
{
class ScSolverSettingsObj : public UnoApiTest
{
public:
ScSolverSettingsObj();
virtual void setUp() override;
void testXSolverSettings();
void testCellAddress(const table::CellAddress& rExpected, const uno::Any& rActual);
void testCellRangeAddress(const uno::Any& rExpected, const uno::Any& rActual);
CPPUNIT_TEST_SUITE(ScSolverSettingsObj);
CPPUNIT_TEST(testXSolverSettings);
CPPUNIT_TEST_SUITE_END();
};
ScSolverSettingsObj::ScSolverSettingsObj()
: UnoApiTest(u"/sc/qa/extras/testdocuments"_ustr)
{
}
void ScSolverSettingsObj::testCellAddress(const table::CellAddress& rExpected,
const uno::Any& rActual)
{
table::CellAddress aActualAddress;
rActual >>= aActualAddress;
CPPUNIT_ASSERT_EQUAL(rExpected.Column, aActualAddress.Column);
CPPUNIT_ASSERT_EQUAL(rExpected.Row, aActualAddress.Row);
CPPUNIT_ASSERT_EQUAL(rExpected.Sheet, aActualAddress.Sheet);
}
void ScSolverSettingsObj::testCellRangeAddress(const uno::Any& rExpected, const uno::Any& rActual)
{
table::CellRangeAddress aActualAddress;
table::CellRangeAddress aExpectedAddress;
rActual >>= aActualAddress;
rExpected >>= aExpectedAddress;
CPPUNIT_ASSERT_EQUAL(aExpectedAddress.Sheet, aActualAddress.Sheet);
CPPUNIT_ASSERT_EQUAL(aExpectedAddress.StartRow, aActualAddress.StartRow);
CPPUNIT_ASSERT_EQUAL(aExpectedAddress.StartColumn, aActualAddress.StartColumn);
CPPUNIT_ASSERT_EQUAL(aExpectedAddress.EndRow, aActualAddress.EndRow);
CPPUNIT_ASSERT_EQUAL(aExpectedAddress.EndColumn, aActualAddress.EndColumn);
}
// Creates a model using the XSolverSettings API checks if it is accessible via the API
void ScSolverSettingsObj::testXSolverSettings()
{
uno::Reference<sheet::XSpreadsheetDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
uno::Reference<container::XIndexAccess> xIndex(xDoc->getSheets(), uno::UNO_QUERY_THROW);
uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
uno::Reference<beans::XPropertySet> xPropSet(xSheet, uno::UNO_QUERY_THROW);
uno::Reference<sheet::XSolverSettings> xSolverModel(
xPropSet->getPropertyValue("SolverSettings"), uno::UNO_QUERY_THROW);
// Objective cell is in G7
table::CellAddress aObjCell(0, 6, 6);
xSolverModel->setObjectiveCell(uno::Any(aObjCell));
xSolverModel->setObjectiveType(sheet::SolverObjectiveType::MAXIMIZE);
// Variable cells (E3:E17)
uno::Sequence<uno::Any> aVarCells(1);
auto pVarCells = aVarCells.getArray();
pVarCells[0] <<= table::CellRangeAddress(0, 4, 2, 4, 16);
xSolverModel->setVariableCells(aVarCells);
// Constraints
uno::Sequence<sheet::ModelConstraint> aConstraints(2);
auto pConstraints = aConstraints.getArray();
pConstraints[0].Left <<= OUString("$E$3:$E$17");
pConstraints[0].Operator = sheet::SolverConstraintOperator_BINARY;
pConstraints[1].Left <<= OUString("$G$5");
pConstraints[1].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
pConstraints[1].Right <<= OUString("$G$3");
xSolverModel->setConstraints(aConstraints);
// Set solver engine options
xSolverModel->setEngine(u"com.sun.star.comp.Calc.LpsolveSolver"_ustr);
uno::Sequence<beans::PropertyValue> aEngineOptions{
comphelper::makePropertyValue(u"Timeout"_ustr, uno::Any(static_cast<sal_Int32>(10))),
comphelper::makePropertyValue(u"NonNegative"_ustr, true),
};
xSolverModel->setEngineOptions(aEngineOptions);
// Run the model and check the results
xSolverModel->saveToFile();
xSolverModel->setSuppressDialog(true);
xSolverModel->solve();
// The correct solution value is 6981 (using LpsolveSolver)
CPPUNIT_ASSERT_EQUAL(static_cast<double>(6981), xSheet->getCellByPosition(6, 6)->getValue());
// Check objective function and variable cells
testCellAddress(aObjCell, xSolverModel->getObjectiveCell());
CPPUNIT_ASSERT_EQUAL(sheet::SolverObjectiveType::MAXIMIZE, xSolverModel->getObjectiveType());
uno::Sequence<uno::Any> aSeq = xSolverModel->getVariableCells();
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aSeq.getLength());
testCellRangeAddress(aVarCells[0], aSeq[0]);
// Check if constraints were set
uno::Sequence<sheet::ModelConstraint> aSeqConstr = xSolverModel->getConstraints();
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aSeqConstr.getLength());
CPPUNIT_ASSERT_EQUAL(sheet::SolverConstraintOperator::SolverConstraintOperator_BINARY,
aSeqConstr[0].Operator);
table::CellRangeAddress aLeft1(0, 4, 2, 4, 16);
table::CellRangeAddress aRight1(0, 0, 0, 0, 0);
testCellRangeAddress(uno::Any(aLeft1), aSeqConstr[0].Left);
testCellRangeAddress(uno::Any(aRight1), aSeqConstr[0].Right);
CPPUNIT_ASSERT_EQUAL(sheet::SolverConstraintOperator::SolverConstraintOperator_LESS_EQUAL,
aSeqConstr[1].Operator);
table::CellRangeAddress aLeft2(0, 6, 4, 6, 4);
table::CellRangeAddress aRight2(0, 6, 2, 6, 2);
testCellRangeAddress(uno::Any(aLeft2), aSeqConstr[1].Left);
testCellRangeAddress(uno::Any(aRight2), aSeqConstr[1].Right);
// Check solver engine options
CPPUNIT_ASSERT_EQUAL(u"com.sun.star.comp.Calc.LpsolveSolver"_ustr, xSolverModel->getEngine());
// Check solver engine options
uno::Sequence<beans::PropertyValue> aEngProps = xSolverModel->getEngineOptions();
CPPUNIT_ASSERT_EQUAL(OUString("EpsilonLevel"), aEngProps[0].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(0)), aEngProps[0].Value);
CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps[1].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps[1].Value);
CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps[2].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps[2].Value);
CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps[3].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps[3].Value);
CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps[4].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps[4].Value);
// Save file and reload to check if solver settings are still there
saveAndReload(u"calc8"_ustr);
uno::Reference<sheet::XSpreadsheetDocument> xDoc2(mxComponent, uno::UNO_QUERY_THROW);
uno::Reference<container::XIndexAccess> xIndex2(xDoc2->getSheets(), uno::UNO_QUERY_THROW);
uno::Reference<sheet::XSpreadsheet> xSheet2(xIndex2->getByIndex(0), uno::UNO_QUERY_THROW);
uno::Reference<beans::XPropertySet> xPropSet2(xSheet2, uno::UNO_QUERY_THROW);
uno::Reference<sheet::XSolverSettings> xSolverModel2(
xPropSet2->getPropertyValue("SolverSettings"), uno::UNO_QUERY_THROW);
// // Check objective function
testCellAddress(aObjCell, xSolverModel2->getObjectiveCell());
CPPUNIT_ASSERT_EQUAL(sheet::SolverObjectiveType::MAXIMIZE, xSolverModel2->getObjectiveType());
// Check variable cells
uno::Sequence<uno::Any> aVarCells2 = xSolverModel2->getVariableCells();
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aVarCells2.getLength());
testCellRangeAddress(aVarCells[0], aVarCells2[0]);
// Check constraints
uno::Sequence<sheet::ModelConstraint> aSeqConstr2 = xSolverModel2->getConstraints();
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aSeqConstr2.getLength());
CPPUNIT_ASSERT_EQUAL(sheet::SolverConstraintOperator::SolverConstraintOperator_BINARY,
aSeqConstr2[0].Operator);
testCellRangeAddress(uno::Any(aLeft1), aSeqConstr2[0].Left);
testCellRangeAddress(uno::Any(aRight1), aSeqConstr2[0].Right);
CPPUNIT_ASSERT_EQUAL(sheet::SolverConstraintOperator::SolverConstraintOperator_LESS_EQUAL,
aSeqConstr2[1].Operator);
testCellRangeAddress(uno::Any(aLeft2), aSeqConstr2[1].Left);
testCellRangeAddress(uno::Any(aRight2), aSeqConstr2[1].Right);
// Check solver engine options
uno::Sequence<beans::PropertyValue> aEngProps2 = xSolverModel2->getEngineOptions();
CPPUNIT_ASSERT_EQUAL(OUString("EpsilonLevel"), aEngProps2[0].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(0)), aEngProps2[0].Value);
CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps2[1].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps2[1].Value);
CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps2[2].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps2[2].Value);
CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps2[3].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps2[3].Value);
CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps2[4].Name);
CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps2[4].Value);
}
void ScSolverSettingsObj::setUp()
{
UnoApiTest::setUp();
loadFromFile(u"knapsack.ods");
}
CPPUNIT_TEST_SUITE_REGISTRATION(ScSolverSettingsObj);
} // namespace sc_apitest
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Binary file not shown.

View file

@ -685,7 +685,7 @@ void SolverSettings::GetEngineOptions(css::uno::Sequence<css::beans::PropertyVal
}
// Updates the object members related to solver engine options using aOptions info
void SolverSettings::SetEngineOptions(css::uno::Sequence<css::beans::PropertyValue>& aOptions)
void SolverSettings::SetEngineOptions(const css::uno::Sequence<css::beans::PropertyValue>& aOptions)
{
sal_Int32 nOptionsSize = aOptions.getLength();

View file

@ -66,6 +66,7 @@
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/text/textfield/Type.hpp>
#include <com/sun/star/sheet/XConditionalFormats.hpp>
#include <com/sun/star/sheet/XSolverSettings.hpp>
#include <autoform.hxx>
#include <cellvalue.hxx>
@ -138,6 +139,7 @@
#include <refundo.hxx>
#include <columnspanset.hxx>
#include <CommonProperties.hxx>
#include <solveruno.hxx>
#include <memory>
@ -784,6 +786,7 @@ static const SfxItemPropertySet* lcl_GetSheetPropertySet()
{ SC_UNONAME_TABCOLOR, SC_WID_UNO_TABCOLOR, cppu::UnoType<sal_Int32>::get(), 0, 0 },
{ SC_UNO_CODENAME, SC_WID_UNO_CODENAME, cppu::UnoType<OUString>::get(), 0, 0},
{ SC_UNO_NAMEDRANGES, SC_WID_UNO_NAMES, cppu::UnoType<sheet::XNamedRanges>::get(), 0, 0 },
{ SC_UNO_SOLVERSETTINGS, SC_WID_UNO_SOLVERSETTINGS, cppu::UnoType<sheet::XSolverSettings>::get(), 0, 0 },
};
static SfxItemPropertySet aSheetPropertySet( aSheetPropertyMap_Impl );
return &aSheetPropertySet;
@ -8203,6 +8206,10 @@ void ScTableSheetObj::GetOnePropertyValue( const SfxItemPropertyMapEntry* pEntry
{
rAny <<= uno::Reference<sheet::XConditionalFormats>(new ScCondFormatsObj(pDocSh, nTab));
}
else if (pEntry->nWID == SC_WID_UNO_SOLVERSETTINGS)
{
rAny <<= uno::Reference<sheet::XSolverSettings>(new ScSolverSettings(pDocSh, this));
}
else
ScCellRangeObj::GetOnePropertyValue(pEntry, rAny);
}

View file

@ -0,0 +1,988 @@
/* -*- 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 <rtl/math.hxx>
#include <vcl/svapp.hxx>
#include <solveruno.hxx>
#include <docsh.hxx>
#include <docfunc.hxx>
#include <address.hxx>
#include <table.hxx>
#include <convuno.hxx>
#include <compiler.hxx>
#include <solverutil.hxx>
#include <rangeutl.hxx>
#include <scresid.hxx>
#include <globstr.hrc>
#include <optsolver.hxx>
#include <unonames.hxx>
#include <SolverSettings.hxx>
#include <o3tl/string_view.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/sheet/XSolver.hpp>
#include <com/sun/star/sheet/XSolverDescription.hpp>
using namespace css;
constexpr OUString SC_SOLVERSETTINGS_SERVICE = u"com.sun.star.sheet.SolverSettings"_ustr;
namespace
{
// Returns the sc::ConstraintOperator equivalent to the Uno operator
sc::ConstraintOperator getScOperatorFromUno(sheet::SolverConstraintOperator aOperator)
{
sc::ConstraintOperator aRet(sc::ConstraintOperator::CO_LESS_EQUAL);
switch (aOperator)
{
case sheet::SolverConstraintOperator_EQUAL:
aRet = sc::ConstraintOperator::CO_EQUAL;
break;
case sheet::SolverConstraintOperator_GREATER_EQUAL:
aRet = sc::ConstraintOperator::CO_GREATER_EQUAL;
break;
case sheet::SolverConstraintOperator_BINARY:
aRet = sc::ConstraintOperator::CO_BINARY;
break;
case sheet::SolverConstraintOperator_INTEGER:
aRet = sc::ConstraintOperator::CO_INTEGER;
break;
default:
{
// This should never be reached
}
}
return aRet;
}
// Returns the sheet::SolverConstraintOperator equivalent to sc::ConstraintOperator
sheet::SolverConstraintOperator getUnoOperatorFromSc(sc::ConstraintOperator nOperator)
{
sheet::SolverConstraintOperator aRet(sheet::SolverConstraintOperator_LESS_EQUAL);
switch (nOperator)
{
case sc::ConstraintOperator::CO_EQUAL:
aRet = sheet::SolverConstraintOperator_EQUAL;
break;
case sc::ConstraintOperator::CO_GREATER_EQUAL:
aRet = sheet::SolverConstraintOperator_GREATER_EQUAL;
break;
case sc::ConstraintOperator::CO_BINARY:
aRet = sheet::SolverConstraintOperator_BINARY;
break;
case sc::ConstraintOperator::CO_INTEGER:
aRet = sheet::SolverConstraintOperator_INTEGER;
break;
default:
{
// This should never be reached
}
}
return aRet;
}
// Returns the CellRangeAddress struct from a ScRange
table::CellRangeAddress getRangeAddress(ScRange aRange)
{
table::CellRangeAddress aRet;
aRet.Sheet = aRange.aStart.Tab();
aRet.StartColumn = aRange.aStart.Col();
aRet.StartRow = aRange.aStart.Row();
aRet.EndColumn = aRange.aEnd.Col();
aRet.EndRow = aRange.aEnd.Row();
return aRet;
}
// Tests if a string is a valid number
bool isValidNumber(const OUString& sValue, double& fValue)
{
if (sValue.isEmpty())
return false;
rtl_math_ConversionStatus eConvStatus;
sal_Int32 nEnd;
fValue = rtl::math::stringToDouble(sValue, ScGlobal::getLocaleData().getNumDecimalSep()[0],
ScGlobal::getLocaleData().getNumThousandSep()[0],
&eConvStatus, &nEnd);
// A conversion is only valid if nEnd is equal to the string length (all chars processed)
return nEnd == sValue.getLength();
}
}
ScSolverSettings::ScSolverSettings(ScDocShell* pDocSh, uno::Reference<container::XNamed> xSheet)
: m_pDocShell(pDocSh)
, m_rDoc(m_pDocShell->GetDocument())
, m_xSheet(std::move(xSheet))
, m_nStatus(sheet::SolverStatus::NONE)
, m_bSuppressDialog(false)
{
// Initialize member variables with information about the current sheet
OUString aName = m_xSheet->getName();
SCTAB nTab;
if (m_rDoc.GetTable(aName, nTab))
{
m_pTable = m_rDoc.FetchTable(nTab);
m_pSettings = m_pTable->GetSolverSettings();
}
}
ScSolverSettings::~ScSolverSettings() {}
bool ScSolverSettings::ParseRef(ScRange& rRange, const OUString& rInput, bool bAllowRange)
{
ScAddress::Details aDetails(m_rDoc.GetAddressConvention(), 0, 0);
ScRefFlags nFlags = rRange.ParseAny(rInput, m_rDoc, aDetails);
SCTAB nCurTab(m_pTable->GetTab());
if (nFlags & ScRefFlags::VALID)
{
if ((nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
rRange.aStart.SetTab(nCurTab);
if ((nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
rRange.aEnd.SetTab(rRange.aStart.Tab());
return (bAllowRange || rRange.aStart == rRange.aEnd);
}
else if (ScRangeUtil::MakeRangeFromName(rInput, m_rDoc, nCurTab, rRange, RUTL_NAMES, aDetails))
return (bAllowRange || rRange.aStart == rRange.aEnd);
return false;
}
bool ScSolverSettings::ParseWithNames(ScRangeList& rRanges, std::u16string_view rInput)
{
if (rInput.empty())
return true;
ScAddress::Details aDetails(m_rDoc.GetAddressConvention(), 0, 0);
SCTAB nCurTab(m_pTable->GetTab());
sal_Unicode cDelimiter = ScCompiler::GetNativeSymbolChar(OpCode::ocSep);
bool bError = false;
sal_Int32 nIdx(0);
do
{
ScRange aRange;
OUString aRangeStr(o3tl::getToken(rInput, 0, cDelimiter, nIdx));
ScRefFlags nFlags = aRange.ParseAny(aRangeStr, m_rDoc, aDetails);
if (nFlags & ScRefFlags::VALID)
{
if ((nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
aRange.aStart.SetTab(nCurTab);
if ((nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
aRange.aEnd.SetTab(aRange.aStart.Tab());
rRanges.push_back(aRange);
}
else if (ScRangeUtil::MakeRangeFromName(aRangeStr, m_rDoc, nCurTab, aRange, RUTL_NAMES,
aDetails))
rRanges.push_back(aRange);
else
bError = true;
} while (nIdx > 0);
return !bError;
}
void ScSolverSettings::ShowErrorMessage(const OUString& rMessage)
{
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
Application::GetDefDialogParent(), VclMessageType::Warning, VclButtonsType::Ok, rMessage));
xBox->run();
}
// XSolverSettings
sal_Int8 SAL_CALL ScSolverSettings::getObjectiveType()
{
sal_Int8 aRet(sheet::SolverObjectiveType::MAXIMIZE);
switch (m_pSettings->GetObjectiveType())
{
case sc::ObjectiveType::OT_MINIMIZE:
aRet = sheet::SolverObjectiveType::MINIMIZE;
break;
case sc::ObjectiveType::OT_VALUE:
aRet = sheet::SolverObjectiveType::VALUE;
break;
default:
{
// This should never be reached
}
}
return aRet;
}
void SAL_CALL ScSolverSettings::setObjectiveType(sal_Int8 aObjType)
{
sc::ObjectiveType eType(sc::ObjectiveType::OT_MAXIMIZE);
switch (aObjType)
{
case sheet::SolverObjectiveType::MINIMIZE:
eType = sc::ObjectiveType::OT_MINIMIZE;
break;
case sheet::SolverObjectiveType::VALUE:
eType = sc::ObjectiveType::OT_VALUE;
break;
default:
{
// This should never be reached
}
}
m_pSettings->SetObjectiveType(eType);
}
uno::Any SAL_CALL ScSolverSettings::getObjectiveCell()
{
// The objective cell must be a valid cell address
OUString sValue(m_pSettings->GetParameter(sc::SolverParameter::SP_OBJ_CELL));
// Test if it is a valid cell reference; if so, return its CellAddress
ScRange aRange;
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
bool bOk = (aRange.ParseAny(sValue, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
table::CellAddress aAddress(nTab1, nCol1, nRow1);
return uno::Any(aAddress);
}
// If converting to a CellAddress fails, returns the raw string
return uno::Any(sValue);
}
// The value being set must be either a string referencing a single cell or
// a CellAddress instance
void SAL_CALL ScSolverSettings::setObjectiveCell(const uno::Any& aValue)
{
// Check if a string value is being used
OUString sValue;
bool bIsString(aValue >>= sValue);
if (bIsString)
{
// The string must correspond to a valid range; if not, an empty string is set
ScRange aRange;
OUString sRet;
ScDocument& rDoc = m_pDocShell->GetDocument();
const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
// The range must consist of a single cell
if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
sRet = sValue;
}
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet);
return;
}
// Check if a CellAddress is being used
table::CellAddress aUnoAddress;
bool bIsAddress(aValue >>= aUnoAddress);
if (bIsAddress)
{
OUString sRet;
ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet);
return;
}
// If all fails, set an empty string
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, "");
}
uno::Any SAL_CALL ScSolverSettings::getGoalValue()
{
OUString sValue(m_pSettings->GetParameter(sc::SolverParameter::SP_OBJ_VAL));
// Test if it is a valid cell reference; if so, return its CellAddress
ScRange aRange;
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
bool bOk = (aRange.ParseAny(sValue, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
table::CellAddress aAddress(nTab1, nCol1, nRow1);
return uno::Any(aAddress);
}
double fValue;
bool bValid = isValidNumber(sValue, fValue);
if (bValid)
return uno::Any(fValue);
// If the conversion was not successfull, return "empty"
return uno::Any();
}
void SAL_CALL ScSolverSettings::setGoalValue(const uno::Any& aValue)
{
// Check if a numeric value is being used
double fValue;
bool bIsDouble(aValue >>= fValue);
if (bIsDouble)
{
// The value must be set as a localized number
OUString sLocalizedValue = rtl::math::doubleToUString(
fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sLocalizedValue);
return;
}
// Check if a string value is being used
OUString sValue;
bool bIsString(aValue >>= sValue);
if (bIsString)
{
// The string must correspond to a valid range; if not, an empty string is set
ScRange aRange;
OUString sRet;
ScDocument& rDoc = m_pDocShell->GetDocument();
const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
// The range must consist of a single cell
if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
sRet = sValue;
}
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet);
return;
}
// Check if a CellAddress is being used
table::CellAddress aUnoAddress;
bool bIsAddress(aValue >>= aUnoAddress);
if (bIsAddress)
{
OUString sRet;
ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet);
return;
}
// If all fails, set an empty string
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, "");
}
OUString SAL_CALL ScSolverSettings::getEngine()
{
return m_pSettings->GetParameter(sc::SP_LO_ENGINE);
}
void SAL_CALL ScSolverSettings::setEngine(const OUString& sEngine)
{
// Only change the engine if the new engine exists; otherwise leave it unchanged
uno::Sequence<OUString> arrEngineNames;
uno::Sequence<OUString> arrDescriptions;
ScSolverUtil::GetImplementations(arrEngineNames, arrDescriptions);
if (comphelper::findValue(arrEngineNames, sEngine) == -1)
return;
m_pSettings->SetParameter(sc::SP_LO_ENGINE, sEngine);
}
uno::Sequence<OUString> SAL_CALL ScSolverSettings::getAvailableEngines()
{
uno::Sequence<OUString> arrEngineNames;
uno::Sequence<OUString> arrDescriptions;
ScSolverUtil::GetImplementations(arrEngineNames, arrDescriptions);
return arrEngineNames;
}
uno::Sequence<uno::Any> SAL_CALL ScSolverSettings::getVariableCells()
{
// Variable cells parameter is stored as a single string composed of valid ranges
// separated using the formula separator character
OUString sVarCells(m_pSettings->GetParameter(sc::SP_VAR_CELLS));
// Delimiter character to separate ranges
sal_Unicode cDelimiter = ScCompiler::GetNativeSymbolChar(OpCode::ocSep);
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
uno::Sequence<uno::Any> aRangeSeq;
sal_Int32 nIdx(0);
sal_Int32 nArrPos(0);
do
{
OUString aRangeStr(o3tl::getToken(sVarCells, 0, cDelimiter, nIdx));
// Check if range is valid
ScRange aRange;
bool bOk
= (aRange.ParseAny(aRangeStr, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
if (bOk)
{
table::CellRangeAddress aRangeAddress(getRangeAddress(aRange));
aRangeSeq.realloc(nArrPos + 1);
auto pArrRanges = aRangeSeq.getArray();
pArrRanges[nArrPos] <<= aRangeAddress;
nArrPos++;
}
} while (nIdx > 0);
return aRangeSeq;
}
void SAL_CALL ScSolverSettings::setVariableCells(const uno::Sequence<uno::Any>& aRanges)
{
OUString sVarCells;
bool bFirst(true);
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
OUStringChar cDelimiter(ScCompiler::GetNativeSymbolChar(OpCode::ocSep));
for (const auto& rRange : aRanges)
{
OUString sRange;
bool bIsString(rRange >>= sRange);
bool bOk(false);
if (bIsString)
{
ScRange aRange;
bOk = (aRange.ParseAny(sRange, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
}
table::CellRangeAddress aRangeAddress;
bool bIsRangeAddress(rRange >>= aRangeAddress);
if (bIsRangeAddress)
{
bOk = true;
ScRange aRange(aRangeAddress.StartColumn, aRangeAddress.StartRow, aRangeAddress.Sheet,
aRangeAddress.EndColumn, aRangeAddress.EndRow, aRangeAddress.Sheet);
sRange = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
}
if (bOk)
{
if (bFirst)
{
sVarCells = sRange;
bFirst = false;
}
else
{
sVarCells += cDelimiter + sRange;
}
}
}
m_pSettings->SetParameter(sc::SP_VAR_CELLS, sVarCells);
}
uno::Sequence<sheet::ModelConstraint> SAL_CALL ScSolverSettings::getConstraints()
{
uno::Sequence<sheet::ModelConstraint> aRet;
std::vector<sc::ModelConstraint> vConstraints = m_pSettings->GetConstraints();
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
sal_Int32 nCount(0);
for (const auto& rConst : vConstraints)
{
sheet::ModelConstraint aConstraint;
// Left side: must be valid string representing a cell range
ScRange aLeftRange;
bool bIsLeftRange
= (aLeftRange.ParseAny(rConst.aLeftStr, m_rDoc, eConv) & ScRefFlags::VALID)
== ScRefFlags::VALID;
if (bIsLeftRange)
aConstraint.Left <<= getRangeAddress(aLeftRange);
// Operator
aConstraint.Operator = getUnoOperatorFromSc(rConst.nOperator);
// Right side: must be either
// - valid string representing a cell range or
// - a numeric value
ScRange aRightRange;
bool bIsRightRange
= (aRightRange.ParseAny(rConst.aRightStr, m_rDoc, eConv) & ScRefFlags::VALID)
== ScRefFlags::VALID;
if (bIsRightRange)
{
aConstraint.Right <<= getRangeAddress(aRightRange);
}
else
{
double fValue;
bool bValid = isValidNumber(rConst.aRightStr, fValue);
if (bValid)
aConstraint.Right <<= fValue;
else
aConstraint.Right = uno::Any();
}
// Adds the constraint to the sequence
aRet.realloc(nCount + 1);
auto pArrConstraints = aRet.getArray();
pArrConstraints[nCount] = aConstraint;
nCount++;
}
return aRet;
}
void SAL_CALL
ScSolverSettings::setConstraints(const uno::Sequence<sheet::ModelConstraint>& aConstraints)
{
const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
std::vector<sc::ModelConstraint> vRetConstraints;
for (const auto& rConst : aConstraints)
{
sc::ModelConstraint aNewConst;
// Left side
OUString sLeft;
bool bOkLeft(false);
bool bIsString(rConst.Left >>= sLeft);
if (bIsString)
{
ScRange aRange;
bOkLeft
= (aRange.ParseAny(sLeft, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
}
table::CellRangeAddress aLeftRangeAddress;
bool bIsRangeAddress(rConst.Left >>= aLeftRangeAddress);
if (bIsRangeAddress)
{
bOkLeft = true;
ScRange aRange(aLeftRangeAddress.StartColumn, aLeftRangeAddress.StartRow,
aLeftRangeAddress.Sheet, aLeftRangeAddress.EndColumn,
aLeftRangeAddress.EndRow, aLeftRangeAddress.Sheet);
sLeft = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
}
if (bOkLeft)
aNewConst.aLeftStr = sLeft;
// Constraint operator
aNewConst.nOperator = getScOperatorFromUno(rConst.Operator);
// Right side (may have numeric values)
OUString sRight;
bool bOkRight(false);
double fValue;
bool bIsDouble(rConst.Right >>= fValue);
if (bIsDouble)
{
bOkRight = true;
// The value must be set as a localized number
sRight = rtl::math::doubleToUString(
fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
}
bIsString = (rConst.Right >>= sRight);
if (bIsString)
{
ScRange aRange;
bOkRight
= (aRange.ParseAny(sRight, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
}
table::CellRangeAddress aRightRangeAddress;
bIsRangeAddress = (rConst.Right >>= aRightRangeAddress);
if (bIsRangeAddress)
{
bOkRight = true;
ScRange aRange(aRightRangeAddress.StartColumn, aRightRangeAddress.StartRow,
aRightRangeAddress.Sheet, aRightRangeAddress.EndColumn,
aRightRangeAddress.EndRow, aRightRangeAddress.Sheet);
sRight = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
}
if (bOkRight)
aNewConst.aRightStr = sRight;
vRetConstraints.push_back(aNewConst);
}
m_pSettings->SetConstraints(vRetConstraints);
}
sal_Int32 SAL_CALL ScSolverSettings::getConstraintCount()
{
if (!m_pTable)
return -1;
return static_cast<sal_Int32>(m_pSettings->GetConstraints().size());
}
uno::Sequence<beans::PropertyValue> SAL_CALL ScSolverSettings::getEngineOptions()
{
uno::Sequence<beans::PropertyValue> aRet = ScSolverUtil::GetDefaults(getEngine());
m_pSettings->GetEngineOptions(aRet);
return aRet;
}
void SAL_CALL ScSolverSettings::setEngineOptions(const uno::Sequence<beans::PropertyValue>& rProps)
{
m_pSettings->SetEngineOptions(rProps);
}
sal_Int8 SAL_CALL ScSolverSettings::getStatus() { return m_nStatus; }
OUString SAL_CALL ScSolverSettings::getErrorMessage() { return m_sErrorMessage; }
sal_Bool SAL_CALL ScSolverSettings::getSuppressDialog() { return m_bSuppressDialog; }
void SAL_CALL ScSolverSettings::setSuppressDialog(sal_Bool bSuppress)
{
m_bSuppressDialog = bSuppress;
}
void SAL_CALL ScSolverSettings::reset() { m_pSettings->ResetToDefaults(); }
void SAL_CALL ScSolverSettings::solve()
{
// Show the progress dialog
auto xProgress = std::make_shared<ScSolverProgressDialog>(Application::GetDefDialogParent());
if (!m_bSuppressDialog)
{
// Get the value of the timeout property of the solver engine
uno::Sequence<beans::PropertyValue> aProps(getEngineOptions());
sal_Int32 nTimeout(0);
sal_Int32 nPropCount(aProps.getLength());
bool bHasTimeout(false);
for (sal_Int32 nProp = 0; nProp < nPropCount && !bHasTimeout; ++nProp)
{
const beans::PropertyValue& rValue = aProps[nProp];
if (rValue.Name == SC_UNONAME_TIMEOUT)
bHasTimeout = (rValue.Value >>= nTimeout);
}
if (bHasTimeout)
xProgress->SetTimeLimit(nTimeout);
else
xProgress->HideTimeLimit();
weld::DialogController::runAsync(xProgress, [](sal_Int32 /*nResult*/) {});
// try to make sure the progress dialog is painted before continuing
Application::Reschedule(true);
}
// Check the validity of the objective cell
ScRange aObjRange;
if (!ParseRef(aObjRange, m_pSettings->GetParameter(sc::SP_OBJ_CELL), false))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_OBJCELL_FAIL);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
table::CellAddress aObjCell(aObjRange.aStart.Tab(), aObjRange.aStart.Col(),
aObjRange.aStart.Row());
// Check the validity of the variable cells
ScRangeList aVarRanges;
if (!ParseWithNames(aVarRanges, m_pSettings->GetParameter(sc::SP_VAR_CELLS)))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_VARCELL_FAIL);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
// Resolve ranges into single cells
uno::Sequence<table::CellAddress> aVariableCells;
sal_Int32 nVarPos(0);
for (size_t nRangePos = 0, nRange = aVarRanges.size(); nRangePos < nRange; ++nRangePos)
{
ScRange aRange(aVarRanges[nRangePos]);
aRange.PutInOrder();
SCTAB nTab = aRange.aStart.Tab();
sal_Int32 nAdd = (aRange.aEnd.Col() - aRange.aStart.Col() + 1)
* (aRange.aEnd.Row() - aRange.aStart.Row() + 1);
aVariableCells.realloc(nVarPos + nAdd);
auto pVariables = aVariableCells.getArray();
for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
pVariables[nVarPos++] = table::CellAddress(nTab, nCol, nRow);
}
// Prepare model constraints
uno::Sequence<sheet::SolverConstraint> aConstraints;
sal_Int32 nConstrPos = 0;
for (const auto& rConstr : m_pSettings->GetConstraints())
{
if (!rConstr.aLeftStr.isEmpty())
{
sheet::SolverConstraint aConstraint;
aConstraint.Operator = getUnoOperatorFromSc(rConstr.nOperator);
// The left side of the constraint must be a valid range or a single cell
ScRange aLeftRange;
if (!ParseRef(aLeftRange, rConstr.aLeftStr, true))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
// The right side can be either a cell range, a single cell or a numeric value
bool bIsRange(false);
ScRange aRightRange;
if (ParseRef(aRightRange, rConstr.aRightStr, true))
{
if (aRightRange.aStart == aRightRange.aEnd)
aConstraint.Right
<<= table::CellAddress(aRightRange.aStart.Tab(), aRightRange.aStart.Col(),
aRightRange.aStart.Row());
else if (aRightRange.aEnd.Col() - aRightRange.aStart.Col()
== aLeftRange.aEnd.Col() - aLeftRange.aStart.Col()
&& aRightRange.aEnd.Row() - aRightRange.aStart.Row()
== aLeftRange.aEnd.Row() - aLeftRange.aStart.Row())
// If the right side of the constraint is a range, it must have the
// same shape as the left side
bIsRange = true;
else
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
}
else
{
// Test if the right side is a numeric value
sal_uInt32 nFormat;
double fValue(0);
if (m_rDoc.GetFormatTable()->IsNumberFormat(rConstr.aRightStr, nFormat, fValue))
aConstraint.Right <<= fValue;
else if (aConstraint.Operator != sheet::SolverConstraintOperator_INTEGER
&& aConstraint.Operator != sheet::SolverConstraintOperator_BINARY)
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDCONDITION));
return;
}
}
// Resolve constraint into single cells
sal_Int32 nAdd = (aLeftRange.aEnd.Col() - aLeftRange.aStart.Col() + 1)
* (aLeftRange.aEnd.Row() - aLeftRange.aStart.Row() + 1);
aConstraints.realloc(nConstrPos + nAdd);
auto pConstraints = aConstraints.getArray();
for (SCROW nRow = aLeftRange.aStart.Row(); nRow <= aLeftRange.aEnd.Row(); ++nRow)
for (SCCOL nCol = aLeftRange.aStart.Col(); nCol <= aLeftRange.aEnd.Col(); ++nCol)
{
aConstraint.Left = table::CellAddress(aLeftRange.aStart.Tab(), nCol, nRow);
if (bIsRange)
aConstraint.Right <<= table::CellAddress(
aRightRange.aStart.Tab(),
aRightRange.aStart.Col() + (nCol - aLeftRange.aStart.Col()),
aRightRange.aStart.Row() + (nRow - aLeftRange.aStart.Row()));
pConstraints[nConstrPos++] = aConstraint;
}
}
}
// Type of the objective function
// If the objective is of type VALUE then a minimization model is used
sc::ObjectiveType aObjType(m_pSettings->GetObjectiveType());
bool bMaximize = aObjType == sc::ObjectiveType::OT_MAXIMIZE;
if (aObjType == sc::ObjectiveType::OT_VALUE)
{
// An additional constraint is added to the model forcing
// the objective cell to be equal to a given value
sheet::SolverConstraint aConstraint;
aConstraint.Left = aObjCell;
aConstraint.Operator = sheet::SolverConstraintOperator_EQUAL;
OUString aValStr = m_pSettings->GetParameter(sc::SP_OBJ_VAL);
ScRange aRightRange;
if (ParseRef(aRightRange, aValStr, false))
aConstraint.Right <<= table::CellAddress(
aRightRange.aStart.Tab(), aRightRange.aStart.Col(), aRightRange.aStart.Row());
else
{
// Test if the right side is a numeric value
sal_uInt32 nFormat;
double fValue(0);
if (m_rDoc.GetFormatTable()->IsNumberFormat(aValStr, nFormat, fValue))
aConstraint.Right <<= fValue;
else
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_TARGETVALUE_FAIL);
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
}
aConstraints.realloc(nConstrPos + 1);
aConstraints.getArray()[nConstrPos++] = std::move(aConstraint);
}
// Create a copy of document values in case the user chooses to restore them
sal_Int32 nVarCount = aVariableCells.getLength();
uno::Sequence<double> aOldValues(nVarCount);
std::transform(std::cbegin(aVariableCells), std::cend(aVariableCells), aOldValues.getArray(),
[this](const table::CellAddress& rVariable) -> double {
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, rVariable);
return m_rDoc.GetValue(aCellPos);
});
// Create and initialize solver
uno::Reference<sheet::XSolver> xSolver = ScSolverUtil::GetSolver(getEngine());
OSL_ENSURE(xSolver.is(), "Unable to get solver component");
if (!xSolver.is())
{
if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDINPUT));
m_nStatus = sheet::SolverStatus::ENGINE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_LOAD_FAIL);
return;
}
rtl::Reference<ScModelObj> xDocument(m_pDocShell->GetModel());
xSolver->setDocument(xDocument);
xSolver->setObjective(aObjCell);
xSolver->setVariables(aVariableCells);
xSolver->setConstraints(aConstraints);
xSolver->setMaximize(bMaximize);
// Set engine options
uno::Reference<beans::XPropertySet> xOptProp(xSolver, uno::UNO_QUERY);
if (xOptProp.is())
{
for (const beans::PropertyValue& rValue : getEngineOptions())
{
try
{
xOptProp->setPropertyValue(rValue.Name, rValue.Value);
}
catch (uno::Exception&)
{
OSL_FAIL("Unable to set solver option property");
}
}
}
xSolver->solve();
bool bSuccess = xSolver->getSuccess();
// Close progress dialog
if (!m_bSuppressDialog && xProgress)
xProgress->response(RET_CLOSE);
if (bSuccess)
{
m_nStatus = sheet::SolverStatus::SOLUTION_FOUND;
// Write solution to the document
uno::Sequence<double> aSolution = xSolver->getSolution();
if (aSolution.getLength() == nVarCount)
{
m_pDocShell->LockPaint();
ScDocFunc& rFunc = m_pDocShell->GetDocFunc();
for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
{
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
rFunc.SetValueCell(aCellPos, aSolution[nVarPos], false);
}
m_pDocShell->UnlockPaint();
}
else
{
OSL_FAIL("Wrong number of variables in the solver solution");
}
// Show success dialog
if (!m_bSuppressDialog)
{
// Get formatted result from document to show in the Success dialog
OUString aResultStr = m_rDoc.GetString(static_cast<SCCOL>(aObjCell.Column),
static_cast<SCROW>(aObjCell.Row),
static_cast<SCTAB>(aObjCell.Sheet));
ScSolverSuccessDialog xSuccessDialog(Application::GetDefDialogParent(), aResultStr);
bool bRestore(true);
if (xSuccessDialog.run() == RET_OK)
// Keep results in the document
bRestore = false;
if (bRestore)
{
// Restore values to the document
m_pDocShell->LockPaint();
ScDocFunc& rFunc = m_pDocShell->GetDocFunc();
for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
{
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
rFunc.SetValueCell(aCellPos, aOldValues[nVarPos], false);
}
m_pDocShell->UnlockPaint();
}
}
}
else
{
// The solver failed to find a solution
m_nStatus = sheet::SolverStatus::SOLUTION_NOT_FOUND;
uno::Reference<sheet::XSolverDescription> xDesc(xSolver, uno::UNO_QUERY);
// Get error message reported by the solver
if (xDesc.is())
m_sErrorMessage = xDesc->getStatusDescription();
if (!m_bSuppressDialog)
{
ScSolverNoSolutionDialog aDialog(Application::GetDefDialogParent(), m_sErrorMessage);
aDialog.run();
}
}
}
void SAL_CALL ScSolverSettings::saveToFile() { m_pSettings->SaveSolverSettings(); }
// XServiceInfo
OUString SAL_CALL ScSolverSettings::getImplementationName() { return u"ScSolverSettings"_ustr; }
sal_Bool SAL_CALL ScSolverSettings::supportsService(const OUString& rServiceName)
{
return cppu::supportsService(this, rServiceName);
}
uno::Sequence<OUString> SAL_CALL ScSolverSettings::getSupportedServiceNames()
{
return { SC_SOLVERSETTINGS_SERVICE };
}