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:
parent
646479d234
commit
911a57c4d6
19 changed files with 1595 additions and 4 deletions
|
@ -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 \
|
||||
|
|
46
offapi/com/sun/star/sheet/ModelConstraint.idl
Normal file
46
offapi/com/sun/star/sheet/ModelConstraint.idl
Normal 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: */
|
34
offapi/com/sun/star/sheet/SolverObjectiveType.idl
Normal file
34
offapi/com/sun/star/sheet/SolverObjectiveType.idl
Normal 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: */
|
33
offapi/com/sun/star/sheet/SolverSettings.idl
Normal file
33
offapi/com/sun/star/sheet/SolverSettings.idl
Normal 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: */
|
41
offapi/com/sun/star/sheet/SolverStatus.idl
Normal file
41
offapi/com/sun/star/sheet/SolverStatus.idl
Normal 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: */
|
97
offapi/com/sun/star/sheet/XSolverSettings.idl
Normal file
97
offapi/com/sun/star/sheet/XSolverSettings.idl
Normal 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: */
|
14
sc/CppunitTest_sc_solverobj.mk
Normal file
14
sc/CppunitTest_sc_solverobj.mk
Normal 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:
|
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
99
sc/inc/solveruno.hxx
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
218
sc/qa/extras/scsolverobj.cxx
Normal file
218
sc/qa/extras/scsolverobj.cxx
Normal 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: */
|
BIN
sc/qa/extras/testdocuments/knapsack.ods
Normal file
BIN
sc/qa/extras/testdocuments/knapsack.ods
Normal file
Binary file not shown.
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
988
sc/source/ui/unoobj/solveruno.cxx
Normal file
988
sc/source/ui/unoobj/solveruno.cxx
Normal 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 };
|
||||
}
|
Loading…
Reference in a new issue