1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
11 #include <rtl/math.hxx>
12 #include <vcl/svapp.hxx>
13 #include <solveruno.hxx>
15 #include <docfunc.hxx>
16 #include <address.hxx>
18 #include <convuno.hxx>
19 #include <compiler.hxx>
20 #include <solverutil.hxx>
21 #include <rangeutl.hxx>
22 #include <scresid.hxx>
23 #include <globstr.hrc>
24 #include <optsolver.hxx>
25 #include <unonames.hxx>
26 #include <SolverSettings.hxx>
27 #include <o3tl/string_view.hxx>
28 #include <cppuhelper/supportsservice.hxx>
29 #include <comphelper/sequence.hxx>
30 #include <com/sun/star/sheet/XSolver.hpp>
31 #include <com/sun/star/sheet/XSolverDescription.hpp>
35 constexpr OUString SC_SOLVERSETTINGS_SERVICE
= u
"com.sun.star.sheet.SolverSettings"_ustr
;
39 // Returns the sc::ConstraintOperator equivalent to the Uno operator
40 sc::ConstraintOperator
getScOperatorFromUno(sheet::SolverConstraintOperator aOperator
)
42 sc::ConstraintOperator
aRet(sc::ConstraintOperator::CO_LESS_EQUAL
);
46 case sheet::SolverConstraintOperator_EQUAL
:
47 aRet
= sc::ConstraintOperator::CO_EQUAL
;
49 case sheet::SolverConstraintOperator_GREATER_EQUAL
:
50 aRet
= sc::ConstraintOperator::CO_GREATER_EQUAL
;
52 case sheet::SolverConstraintOperator_BINARY
:
53 aRet
= sc::ConstraintOperator::CO_BINARY
;
55 case sheet::SolverConstraintOperator_INTEGER
:
56 aRet
= sc::ConstraintOperator::CO_INTEGER
;
60 // This should never be reached
66 // Returns the sheet::SolverConstraintOperator equivalent to sc::ConstraintOperator
67 sheet::SolverConstraintOperator
getUnoOperatorFromSc(sc::ConstraintOperator nOperator
)
69 sheet::SolverConstraintOperator
aRet(sheet::SolverConstraintOperator_LESS_EQUAL
);
73 case sc::ConstraintOperator::CO_EQUAL
:
74 aRet
= sheet::SolverConstraintOperator_EQUAL
;
76 case sc::ConstraintOperator::CO_GREATER_EQUAL
:
77 aRet
= sheet::SolverConstraintOperator_GREATER_EQUAL
;
79 case sc::ConstraintOperator::CO_BINARY
:
80 aRet
= sheet::SolverConstraintOperator_BINARY
;
82 case sc::ConstraintOperator::CO_INTEGER
:
83 aRet
= sheet::SolverConstraintOperator_INTEGER
;
87 // This should never be reached
93 // Returns the CellRangeAddress struct from a ScRange
94 table::CellRangeAddress
getRangeAddress(ScRange aRange
)
96 table::CellRangeAddress aRet
;
97 aRet
.Sheet
= aRange
.aStart
.Tab();
98 aRet
.StartColumn
= aRange
.aStart
.Col();
99 aRet
.StartRow
= aRange
.aStart
.Row();
100 aRet
.EndColumn
= aRange
.aEnd
.Col();
101 aRet
.EndRow
= aRange
.aEnd
.Row();
105 // Tests if a string is a valid number
106 bool isValidNumber(const OUString
& sValue
, double& fValue
)
108 if (sValue
.isEmpty())
111 rtl_math_ConversionStatus eConvStatus
;
113 fValue
= rtl::math::stringToDouble(sValue
, ScGlobal::getLocaleData().getNumDecimalSep()[0],
114 ScGlobal::getLocaleData().getNumThousandSep()[0],
115 &eConvStatus
, &nEnd
);
116 // A conversion is only valid if nEnd is equal to the string length (all chars processed)
117 return nEnd
== sValue
.getLength();
121 ScSolverSettings::ScSolverSettings(ScDocShell
* pDocSh
, uno::Reference
<container::XNamed
> xSheet
)
122 : m_pDocShell(pDocSh
)
123 , m_rDoc(m_pDocShell
->GetDocument())
124 , m_xSheet(std::move(xSheet
))
125 , m_nStatus(sheet::SolverStatus::NONE
)
126 , m_bSuppressDialog(false)
129 // Initialize member variables with information about the current sheet
130 OUString aName
= m_xSheet
->getName();
132 if (m_rDoc
.GetTable(aName
, nTab
))
134 m_pTable
= m_rDoc
.FetchTable(nTab
);
135 m_pSettings
= m_pTable
->GetSolverSettings();
139 ScSolverSettings::~ScSolverSettings() {}
141 bool ScSolverSettings::ParseRef(ScRange
& rRange
, const OUString
& rInput
, bool bAllowRange
)
143 ScAddress::Details
aDetails(m_rDoc
.GetAddressConvention(), 0, 0);
144 ScRefFlags nFlags
= rRange
.ParseAny(rInput
, m_rDoc
, aDetails
);
145 SCTAB
nCurTab(m_pTable
->GetTab());
146 if (nFlags
& ScRefFlags::VALID
)
148 if ((nFlags
& ScRefFlags::TAB_3D
) == ScRefFlags::ZERO
)
149 rRange
.aStart
.SetTab(nCurTab
);
150 if ((nFlags
& ScRefFlags::TAB2_3D
) == ScRefFlags::ZERO
)
151 rRange
.aEnd
.SetTab(rRange
.aStart
.Tab());
152 return (bAllowRange
|| rRange
.aStart
== rRange
.aEnd
);
154 else if (ScRangeUtil::MakeRangeFromName(rInput
, m_rDoc
, nCurTab
, rRange
, RUTL_NAMES
, aDetails
))
155 return (bAllowRange
|| rRange
.aStart
== rRange
.aEnd
);
160 bool ScSolverSettings::ParseWithNames(ScRangeList
& rRanges
, std::u16string_view rInput
)
165 ScAddress::Details
aDetails(m_rDoc
.GetAddressConvention(), 0, 0);
166 SCTAB
nCurTab(m_pTable
->GetTab());
167 sal_Unicode cDelimiter
= ScCompiler::GetNativeSymbolChar(OpCode::ocSep
);
173 OUString
aRangeStr(o3tl::getToken(rInput
, 0, cDelimiter
, nIdx
));
174 ScRefFlags nFlags
= aRange
.ParseAny(aRangeStr
, m_rDoc
, aDetails
);
175 if (nFlags
& ScRefFlags::VALID
)
177 if ((nFlags
& ScRefFlags::TAB_3D
) == ScRefFlags::ZERO
)
178 aRange
.aStart
.SetTab(nCurTab
);
179 if ((nFlags
& ScRefFlags::TAB2_3D
) == ScRefFlags::ZERO
)
180 aRange
.aEnd
.SetTab(aRange
.aStart
.Tab());
181 rRanges
.push_back(aRange
);
183 else if (ScRangeUtil::MakeRangeFromName(aRangeStr
, m_rDoc
, nCurTab
, aRange
, RUTL_NAMES
,
185 rRanges
.push_back(aRange
);
193 void ScSolverSettings::ShowErrorMessage(const OUString
& rMessage
)
195 std::unique_ptr
<weld::MessageDialog
> xBox(Application::CreateMessageDialog(
196 Application::GetDefDialogParent(), VclMessageType::Warning
, VclButtonsType::Ok
, rMessage
));
201 sal_Int8 SAL_CALL
ScSolverSettings::getObjectiveType()
203 sal_Int8
aRet(sheet::SolverObjectiveType::MAXIMIZE
);
204 switch (m_pSettings
->GetObjectiveType())
206 case sc::ObjectiveType::OT_MINIMIZE
:
207 aRet
= sheet::SolverObjectiveType::MINIMIZE
;
209 case sc::ObjectiveType::OT_VALUE
:
210 aRet
= sheet::SolverObjectiveType::VALUE
;
214 // This should never be reached
220 void SAL_CALL
ScSolverSettings::setObjectiveType(sal_Int8 aObjType
)
222 sc::ObjectiveType
eType(sc::ObjectiveType::OT_MAXIMIZE
);
225 case sheet::SolverObjectiveType::MINIMIZE
:
226 eType
= sc::ObjectiveType::OT_MINIMIZE
;
228 case sheet::SolverObjectiveType::VALUE
:
229 eType
= sc::ObjectiveType::OT_VALUE
;
233 // This should never be reached
236 m_pSettings
->SetObjectiveType(eType
);
239 uno::Any SAL_CALL
ScSolverSettings::getObjectiveCell()
241 // The objective cell must be a valid cell address
242 OUString
sValue(m_pSettings
->GetParameter(sc::SolverParameter::SP_OBJ_CELL
));
244 // Test if it is a valid cell reference; if so, return its CellAddress
246 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
247 bool bOk
= (aRange
.ParseAny(sValue
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
253 aRange
.GetVars(nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
);
254 table::CellAddress
aAddress(nTab1
, nCol1
, nRow1
);
255 return uno::Any(aAddress
);
258 // If converting to a CellAddress fails, returns the raw string
259 return uno::Any(sValue
);
262 // The value being set must be either a string referencing a single cell or
263 // a CellAddress instance
264 void SAL_CALL
ScSolverSettings::setObjectiveCell(const uno::Any
& aValue
)
266 // Check if a string value is being used
268 bool bIsString(aValue
>>= sValue
);
271 // The string must correspond to a valid range; if not, an empty string is set
274 ScDocument
& rDoc
= m_pDocShell
->GetDocument();
275 const formula::FormulaGrammar::AddressConvention eConv
= rDoc
.GetAddressConvention();
276 bool bOk
= (aRange
.ParseAny(sValue
, rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
282 aRange
.GetVars(nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
);
283 // The range must consist of a single cell
284 if (nTab1
== nTab2
&& nCol1
== nCol2
&& nRow1
== nRow2
)
287 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_CELL
, sRet
);
291 // Check if a CellAddress is being used
292 table::CellAddress aUnoAddress
;
293 bool bIsAddress(aValue
>>= aUnoAddress
);
297 ScAddress
aAdress(aUnoAddress
.Column
, aUnoAddress
.Row
, aUnoAddress
.Sheet
);
298 sRet
= aAdress
.Format(ScRefFlags::RANGE_ABS
, &m_rDoc
);
299 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_CELL
, sRet
);
303 // If all fails, set an empty string
304 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_CELL
, "");
307 uno::Any SAL_CALL
ScSolverSettings::getGoalValue()
309 OUString
sValue(m_pSettings
->GetParameter(sc::SolverParameter::SP_OBJ_VAL
));
311 // Test if it is a valid cell reference; if so, return its CellAddress
313 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
314 bool bOk
= (aRange
.ParseAny(sValue
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
320 aRange
.GetVars(nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
);
321 table::CellAddress
aAddress(nTab1
, nCol1
, nRow1
);
322 return uno::Any(aAddress
);
326 bool bValid
= isValidNumber(sValue
, fValue
);
328 return uno::Any(fValue
);
330 // If the conversion was not successful, return "empty"
334 void SAL_CALL
ScSolverSettings::setGoalValue(const uno::Any
& aValue
)
336 // Check if a numeric value is being used
338 bool bIsDouble(aValue
>>= fValue
);
341 // The value must be set as a localized number
342 OUString sLocalizedValue
= rtl::math::doubleToUString(
343 fValue
, rtl_math_StringFormat_Automatic
, rtl_math_DecimalPlaces_Max
,
344 ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
345 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_VAL
, sLocalizedValue
);
349 // Check if a string value is being used
351 bool bIsString(aValue
>>= sValue
);
354 // The string must correspond to a valid range; if not, an empty string is set
357 ScDocument
& rDoc
= m_pDocShell
->GetDocument();
358 const formula::FormulaGrammar::AddressConvention eConv
= rDoc
.GetAddressConvention();
359 bool bOk
= (aRange
.ParseAny(sValue
, rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
365 aRange
.GetVars(nCol1
, nRow1
, nTab1
, nCol2
, nRow2
, nTab2
);
366 // The range must consist of a single cell
367 if (nTab1
== nTab2
&& nCol1
== nCol2
&& nRow1
== nRow2
)
370 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_VAL
, sRet
);
374 // Check if a CellAddress is being used
375 table::CellAddress aUnoAddress
;
376 bool bIsAddress(aValue
>>= aUnoAddress
);
380 ScAddress
aAdress(aUnoAddress
.Column
, aUnoAddress
.Row
, aUnoAddress
.Sheet
);
381 sRet
= aAdress
.Format(ScRefFlags::RANGE_ABS
, &m_rDoc
);
382 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_VAL
, sRet
);
386 // If all fails, set an empty string
387 m_pSettings
->SetParameter(sc::SolverParameter::SP_OBJ_VAL
, "");
390 OUString SAL_CALL
ScSolverSettings::getEngine()
392 return m_pSettings
->GetParameter(sc::SP_LO_ENGINE
);
395 void SAL_CALL
ScSolverSettings::setEngine(const OUString
& sEngine
)
397 // Only change the engine if the new engine exists; otherwise leave it unchanged
398 uno::Sequence
<OUString
> arrEngineNames
;
399 uno::Sequence
<OUString
> arrDescriptions
;
400 ScSolverUtil::GetImplementations(arrEngineNames
, arrDescriptions
);
401 if (comphelper::findValue(arrEngineNames
, sEngine
) == -1)
404 m_pSettings
->SetParameter(sc::SP_LO_ENGINE
, sEngine
);
407 uno::Sequence
<OUString
> SAL_CALL
ScSolverSettings::getAvailableEngines()
409 uno::Sequence
<OUString
> arrEngineNames
;
410 uno::Sequence
<OUString
> arrDescriptions
;
411 ScSolverUtil::GetImplementations(arrEngineNames
, arrDescriptions
);
412 return arrEngineNames
;
415 uno::Sequence
<uno::Any
> SAL_CALL
ScSolverSettings::getVariableCells()
417 // Variable cells parameter is stored as a single string composed of valid ranges
418 // separated using the formula separator character
419 OUString
sVarCells(m_pSettings
->GetParameter(sc::SP_VAR_CELLS
));
420 // Delimiter character to separate ranges
421 sal_Unicode cDelimiter
= ScCompiler::GetNativeSymbolChar(OpCode::ocSep
);
422 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
423 uno::Sequence
<uno::Any
> aRangeSeq
;
425 sal_Int32
nArrPos(0);
429 OUString
aRangeStr(o3tl::getToken(sVarCells
, 0, cDelimiter
, nIdx
));
430 // Check if range is valid
433 = (aRange
.ParseAny(aRangeStr
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
436 table::CellRangeAddress
aRangeAddress(getRangeAddress(aRange
));
437 aRangeSeq
.realloc(nArrPos
+ 1);
438 auto pArrRanges
= aRangeSeq
.getArray();
439 pArrRanges
[nArrPos
] <<= aRangeAddress
;
447 void SAL_CALL
ScSolverSettings::setVariableCells(const uno::Sequence
<uno::Any
>& aRanges
)
451 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
452 OUStringChar
cDelimiter(ScCompiler::GetNativeSymbolChar(OpCode::ocSep
));
454 for (const auto& rRange
: aRanges
)
457 bool bIsString(rRange
>>= sRange
);
462 bOk
= (aRange
.ParseAny(sRange
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
465 table::CellRangeAddress aRangeAddress
;
466 bool bIsRangeAddress(rRange
>>= aRangeAddress
);
470 ScRange
aRange(aRangeAddress
.StartColumn
, aRangeAddress
.StartRow
, aRangeAddress
.Sheet
,
471 aRangeAddress
.EndColumn
, aRangeAddress
.EndRow
, aRangeAddress
.Sheet
);
472 sRange
= aRange
.Format(m_rDoc
, ScRefFlags::RANGE_ABS
);
484 sVarCells
+= cDelimiter
+ sRange
;
489 m_pSettings
->SetParameter(sc::SP_VAR_CELLS
, sVarCells
);
492 uno::Sequence
<sheet::ModelConstraint
> SAL_CALL
ScSolverSettings::getConstraints()
494 uno::Sequence
<sheet::ModelConstraint
> aRet
;
495 std::vector
<sc::ModelConstraint
> vConstraints
= m_pSettings
->GetConstraints();
496 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
499 for (const auto& rConst
: vConstraints
)
501 sheet::ModelConstraint aConstraint
;
503 // Left side: must be valid string representing a cell range
506 = (aLeftRange
.ParseAny(rConst
.aLeftStr
, m_rDoc
, eConv
) & ScRefFlags::VALID
)
507 == ScRefFlags::VALID
;
509 aConstraint
.Left
<<= getRangeAddress(aLeftRange
);
512 aConstraint
.Operator
= getUnoOperatorFromSc(rConst
.nOperator
);
514 // Right side: must be either
515 // - valid string representing a cell range or
519 = (aRightRange
.ParseAny(rConst
.aRightStr
, m_rDoc
, eConv
) & ScRefFlags::VALID
)
520 == ScRefFlags::VALID
;
523 aConstraint
.Right
<<= getRangeAddress(aRightRange
);
528 bool bValid
= isValidNumber(rConst
.aRightStr
, fValue
);
530 aConstraint
.Right
<<= fValue
;
532 aConstraint
.Right
= uno::Any();
535 // Adds the constraint to the sequence
536 aRet
.realloc(nCount
+ 1);
537 auto pArrConstraints
= aRet
.getArray();
538 pArrConstraints
[nCount
] = std::move(aConstraint
);
546 ScSolverSettings::setConstraints(const uno::Sequence
<sheet::ModelConstraint
>& aConstraints
)
548 const formula::FormulaGrammar::AddressConvention eConv
= m_rDoc
.GetAddressConvention();
549 std::vector
<sc::ModelConstraint
> vRetConstraints
;
551 for (const auto& rConst
: aConstraints
)
553 sc::ModelConstraint aNewConst
;
558 bool bIsString(rConst
.Left
>>= sLeft
);
563 = (aRange
.ParseAny(sLeft
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
566 table::CellRangeAddress aLeftRangeAddress
;
567 bool bIsRangeAddress(rConst
.Left
>>= aLeftRangeAddress
);
571 ScRange
aRange(aLeftRangeAddress
.StartColumn
, aLeftRangeAddress
.StartRow
,
572 aLeftRangeAddress
.Sheet
, aLeftRangeAddress
.EndColumn
,
573 aLeftRangeAddress
.EndRow
, aLeftRangeAddress
.Sheet
);
574 sLeft
= aRange
.Format(m_rDoc
, ScRefFlags::RANGE_ABS
);
578 aNewConst
.aLeftStr
= sLeft
;
580 // Constraint operator
581 aNewConst
.nOperator
= getScOperatorFromUno(rConst
.Operator
);
583 // Right side (may have numeric values)
585 bool bOkRight(false);
588 bool bIsDouble(rConst
.Right
>>= fValue
);
592 // The value must be set as a localized number
593 sRight
= rtl::math::doubleToUString(
594 fValue
, rtl_math_StringFormat_Automatic
, rtl_math_DecimalPlaces_Max
,
595 ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
598 bIsString
= (rConst
.Right
>>= sRight
);
603 = (aRange
.ParseAny(sRight
, m_rDoc
, eConv
) & ScRefFlags::VALID
) == ScRefFlags::VALID
;
606 table::CellRangeAddress aRightRangeAddress
;
607 bIsRangeAddress
= (rConst
.Right
>>= aRightRangeAddress
);
611 ScRange
aRange(aRightRangeAddress
.StartColumn
, aRightRangeAddress
.StartRow
,
612 aRightRangeAddress
.Sheet
, aRightRangeAddress
.EndColumn
,
613 aRightRangeAddress
.EndRow
, aRightRangeAddress
.Sheet
);
614 sRight
= aRange
.Format(m_rDoc
, ScRefFlags::RANGE_ABS
);
618 aNewConst
.aRightStr
= sRight
;
620 vRetConstraints
.push_back(aNewConst
);
623 m_pSettings
->SetConstraints(std::move(vRetConstraints
));
626 sal_Int32 SAL_CALL
ScSolverSettings::getConstraintCount()
631 return static_cast<sal_Int32
>(m_pSettings
->GetConstraints().size());
634 uno::Sequence
<beans::PropertyValue
> SAL_CALL
ScSolverSettings::getEngineOptions()
636 uno::Sequence
<beans::PropertyValue
> aRet
= ScSolverUtil::GetDefaults(getEngine());
637 m_pSettings
->GetEngineOptions(aRet
);
641 void SAL_CALL
ScSolverSettings::setEngineOptions(const uno::Sequence
<beans::PropertyValue
>& rProps
)
643 m_pSettings
->SetEngineOptions(rProps
);
646 sal_Int8 SAL_CALL
ScSolverSettings::getStatus() { return m_nStatus
; }
648 OUString SAL_CALL
ScSolverSettings::getErrorMessage() { return m_sErrorMessage
; }
650 sal_Bool SAL_CALL
ScSolverSettings::getSuppressDialog() { return m_bSuppressDialog
; }
652 void SAL_CALL
ScSolverSettings::setSuppressDialog(sal_Bool bSuppress
)
654 m_bSuppressDialog
= bSuppress
;
657 void SAL_CALL
ScSolverSettings::reset() { m_pSettings
->ResetToDefaults(); }
659 void SAL_CALL
ScSolverSettings::solve()
661 // Show the progress dialog
662 auto xProgress
= std::make_shared
<ScSolverProgressDialog
>(Application::GetDefDialogParent());
663 if (!m_bSuppressDialog
)
665 // Get the value of the timeout property of the solver engine
666 uno::Sequence
<beans::PropertyValue
> aProps(getEngineOptions());
667 sal_Int32
nTimeout(0);
668 sal_Int32
nPropCount(aProps
.getLength());
669 bool bHasTimeout(false);
670 for (sal_Int32 nProp
= 0; nProp
< nPropCount
&& !bHasTimeout
; ++nProp
)
672 const beans::PropertyValue
& rValue
= aProps
[nProp
];
673 if (rValue
.Name
== SC_UNONAME_TIMEOUT
)
674 bHasTimeout
= (rValue
.Value
>>= nTimeout
);
678 xProgress
->SetTimeLimit(nTimeout
);
680 xProgress
->HideTimeLimit();
682 weld::DialogController::runAsync(xProgress
, [](sal_Int32
/*nResult*/) {});
683 // try to make sure the progress dialog is painted before continuing
684 Application::Reschedule(true);
687 // Check the validity of the objective cell
689 if (!ParseRef(aObjRange
, m_pSettings
->GetParameter(sc::SP_OBJ_CELL
), false))
691 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
692 m_sErrorMessage
= ScResId(STR_SOLVER_OBJCELL_FAIL
);
693 if (!m_bSuppressDialog
)
694 ScSolverSettings::ShowErrorMessage(m_sErrorMessage
);
697 table::CellAddress
aObjCell(aObjRange
.aStart
.Tab(), aObjRange
.aStart
.Col(),
698 aObjRange
.aStart
.Row());
700 // Check the validity of the variable cells
701 ScRangeList aVarRanges
;
702 if (!ParseWithNames(aVarRanges
, m_pSettings
->GetParameter(sc::SP_VAR_CELLS
)))
704 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
705 m_sErrorMessage
= ScResId(STR_SOLVER_VARCELL_FAIL
);
706 if (!m_bSuppressDialog
)
707 ScSolverSettings::ShowErrorMessage(m_sErrorMessage
);
711 // Resolve ranges into single cells
712 uno::Sequence
<table::CellAddress
> aVariableCells
;
713 sal_Int32
nVarPos(0);
714 for (size_t nRangePos
= 0, nRange
= aVarRanges
.size(); nRangePos
< nRange
; ++nRangePos
)
716 ScRange
aRange(aVarRanges
[nRangePos
]);
718 SCTAB nTab
= aRange
.aStart
.Tab();
720 sal_Int32 nAdd
= (aRange
.aEnd
.Col() - aRange
.aStart
.Col() + 1)
721 * (aRange
.aEnd
.Row() - aRange
.aStart
.Row() + 1);
722 aVariableCells
.realloc(nVarPos
+ nAdd
);
723 auto pVariables
= aVariableCells
.getArray();
725 for (SCROW nRow
= aRange
.aStart
.Row(); nRow
<= aRange
.aEnd
.Row(); ++nRow
)
726 for (SCCOL nCol
= aRange
.aStart
.Col(); nCol
<= aRange
.aEnd
.Col(); ++nCol
)
727 pVariables
[nVarPos
++] = table::CellAddress(nTab
, nCol
, nRow
);
730 // Prepare model constraints
731 uno::Sequence
<sheet::SolverConstraint
> aConstraints
;
732 sal_Int32 nConstrPos
= 0;
733 for (const auto& rConstr
: m_pSettings
->GetConstraints())
735 if (!rConstr
.aLeftStr
.isEmpty())
737 sheet::SolverConstraint aConstraint
;
738 aConstraint
.Operator
= getUnoOperatorFromSc(rConstr
.nOperator
);
740 // The left side of the constraint must be a valid range or a single cell
742 if (!ParseRef(aLeftRange
, rConstr
.aLeftStr
, true))
744 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
745 m_sErrorMessage
= ScResId(STR_INVALIDCONDITION
);
746 if (!m_bSuppressDialog
)
747 ScSolverSettings::ShowErrorMessage(m_sErrorMessage
);
751 // The right side can be either a cell range, a single cell or a numeric value
752 bool bIsRange(false);
754 if (ParseRef(aRightRange
, rConstr
.aRightStr
, true))
756 if (aRightRange
.aStart
== aRightRange
.aEnd
)
758 <<= table::CellAddress(aRightRange
.aStart
.Tab(), aRightRange
.aStart
.Col(),
759 aRightRange
.aStart
.Row());
761 else if (aRightRange
.aEnd
.Col() - aRightRange
.aStart
.Col()
762 == aLeftRange
.aEnd
.Col() - aLeftRange
.aStart
.Col()
763 && aRightRange
.aEnd
.Row() - aRightRange
.aStart
.Row()
764 == aLeftRange
.aEnd
.Row() - aLeftRange
.aStart
.Row())
765 // If the right side of the constraint is a range, it must have the
766 // same shape as the left side
770 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
771 m_sErrorMessage
= ScResId(STR_INVALIDCONDITION
);
772 if (!m_bSuppressDialog
)
773 ScSolverSettings::ShowErrorMessage(m_sErrorMessage
);
780 // Test if the right side is a numeric value
781 sal_uInt32 nFormat
= 0;
783 if (m_rDoc
.GetFormatTable()->IsNumberFormat(rConstr
.aRightStr
, nFormat
, fValue
))
784 aConstraint
.Right
<<= fValue
;
785 else if (aConstraint
.Operator
!= sheet::SolverConstraintOperator_INTEGER
786 && aConstraint
.Operator
!= sheet::SolverConstraintOperator_BINARY
)
788 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
789 m_sErrorMessage
= ScResId(STR_INVALIDCONDITION
);
790 if (!m_bSuppressDialog
)
791 ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDCONDITION
));
796 // Resolve constraint into single cells
797 sal_Int32 nAdd
= (aLeftRange
.aEnd
.Col() - aLeftRange
.aStart
.Col() + 1)
798 * (aLeftRange
.aEnd
.Row() - aLeftRange
.aStart
.Row() + 1);
799 aConstraints
.realloc(nConstrPos
+ nAdd
);
800 auto pConstraints
= aConstraints
.getArray();
802 for (SCROW nRow
= aLeftRange
.aStart
.Row(); nRow
<= aLeftRange
.aEnd
.Row(); ++nRow
)
803 for (SCCOL nCol
= aLeftRange
.aStart
.Col(); nCol
<= aLeftRange
.aEnd
.Col(); ++nCol
)
805 aConstraint
.Left
= table::CellAddress(aLeftRange
.aStart
.Tab(), nCol
, nRow
);
807 aConstraint
.Right
<<= table::CellAddress(
808 aRightRange
.aStart
.Tab(),
809 aRightRange
.aStart
.Col() + (nCol
- aLeftRange
.aStart
.Col()),
810 aRightRange
.aStart
.Row() + (nRow
- aLeftRange
.aStart
.Row()));
811 pConstraints
[nConstrPos
++] = aConstraint
;
816 // Type of the objective function
817 // If the objective is of type VALUE then a minimization model is used
818 sc::ObjectiveType
aObjType(m_pSettings
->GetObjectiveType());
819 bool bMaximize
= aObjType
== sc::ObjectiveType::OT_MAXIMIZE
;
821 if (aObjType
== sc::ObjectiveType::OT_VALUE
)
823 // An additional constraint is added to the model forcing
824 // the objective cell to be equal to a given value
825 sheet::SolverConstraint aConstraint
;
826 aConstraint
.Left
= aObjCell
;
827 aConstraint
.Operator
= sheet::SolverConstraintOperator_EQUAL
;
829 OUString aValStr
= m_pSettings
->GetParameter(sc::SP_OBJ_VAL
);
832 if (ParseRef(aRightRange
, aValStr
, false))
833 aConstraint
.Right
<<= table::CellAddress(
834 aRightRange
.aStart
.Tab(), aRightRange
.aStart
.Col(), aRightRange
.aStart
.Row());
837 // Test if the right side is a numeric value
838 sal_uInt32 nFormat
= 0;
840 if (m_rDoc
.GetFormatTable()->IsNumberFormat(aValStr
, nFormat
, fValue
))
841 aConstraint
.Right
<<= fValue
;
844 m_nStatus
= sheet::SolverStatus::PARSE_ERROR
;
845 m_sErrorMessage
= ScResId(STR_SOLVER_TARGETVALUE_FAIL
);
846 if (!m_bSuppressDialog
)
847 ScSolverSettings::ShowErrorMessage(m_sErrorMessage
);
852 aConstraints
.realloc(nConstrPos
+ 1);
853 aConstraints
.getArray()[nConstrPos
++] = std::move(aConstraint
);
856 // Create a copy of document values in case the user chooses to restore them
857 sal_Int32 nVarCount
= aVariableCells
.getLength();
858 uno::Sequence
<double> aOldValues(nVarCount
);
859 std::transform(std::cbegin(aVariableCells
), std::cend(aVariableCells
), aOldValues
.getArray(),
860 [this](const table::CellAddress
& rVariable
) -> double {
862 ScUnoConversion::FillScAddress(aCellPos
, rVariable
);
863 return m_rDoc
.GetValue(aCellPos
);
866 // Create and initialize solver
867 uno::Reference
<sheet::XSolver
> xSolver
= ScSolverUtil::GetSolver(getEngine());
868 OSL_ENSURE(xSolver
.is(), "Unable to get solver component");
871 if (!m_bSuppressDialog
)
872 ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDINPUT
));
873 m_nStatus
= sheet::SolverStatus::ENGINE_ERROR
;
874 m_sErrorMessage
= ScResId(STR_SOLVER_LOAD_FAIL
);
878 rtl::Reference
<ScModelObj
> xDocument(m_pDocShell
->GetModel());
879 xSolver
->setDocument(xDocument
);
880 xSolver
->setObjective(aObjCell
);
881 xSolver
->setVariables(aVariableCells
);
882 xSolver
->setConstraints(aConstraints
);
883 xSolver
->setMaximize(bMaximize
);
885 // Set engine options
886 uno::Reference
<beans::XPropertySet
> xOptProp(xSolver
, uno::UNO_QUERY
);
889 for (const beans::PropertyValue
& rValue
: getEngineOptions())
893 xOptProp
->setPropertyValue(rValue
.Name
, rValue
.Value
);
895 catch (uno::Exception
&)
897 OSL_FAIL("Unable to set solver option property");
903 bool bSuccess
= xSolver
->getSuccess();
905 // Close progress dialog
906 if (!m_bSuppressDialog
&& xProgress
)
907 xProgress
->response(RET_CLOSE
);
911 m_nStatus
= sheet::SolverStatus::SOLUTION_FOUND
;
912 // Write solution to the document
913 uno::Sequence
<double> aSolution
= xSolver
->getSolution();
914 if (aSolution
.getLength() == nVarCount
)
916 m_pDocShell
->LockPaint();
917 ScDocFunc
& rFunc
= m_pDocShell
->GetDocFunc();
918 for (nVarPos
= 0; nVarPos
< nVarCount
; ++nVarPos
)
921 ScUnoConversion::FillScAddress(aCellPos
, aVariableCells
[nVarPos
]);
922 rFunc
.SetValueCell(aCellPos
, aSolution
[nVarPos
], false);
924 m_pDocShell
->UnlockPaint();
928 OSL_FAIL("Wrong number of variables in the solver solution");
931 // Show success dialog
932 if (!m_bSuppressDialog
)
934 // Get formatted result from document to show in the Success dialog
935 OUString aResultStr
= m_rDoc
.GetString(static_cast<SCCOL
>(aObjCell
.Column
),
936 static_cast<SCROW
>(aObjCell
.Row
),
937 static_cast<SCTAB
>(aObjCell
.Sheet
));
939 ScSolverSuccessDialog
xSuccessDialog(Application::GetDefDialogParent(), aResultStr
);
941 if (xSuccessDialog
.run() == RET_OK
)
942 // Keep results in the document
947 // Restore values to the document
948 m_pDocShell
->LockPaint();
949 ScDocFunc
& rFunc
= m_pDocShell
->GetDocFunc();
950 for (nVarPos
= 0; nVarPos
< nVarCount
; ++nVarPos
)
953 ScUnoConversion::FillScAddress(aCellPos
, aVariableCells
[nVarPos
]);
954 rFunc
.SetValueCell(aCellPos
, aOldValues
[nVarPos
], false);
956 m_pDocShell
->UnlockPaint();
962 // The solver failed to find a solution
963 m_nStatus
= sheet::SolverStatus::SOLUTION_NOT_FOUND
;
964 uno::Reference
<sheet::XSolverDescription
> xDesc(xSolver
, uno::UNO_QUERY
);
965 // Get error message reported by the solver
967 m_sErrorMessage
= xDesc
->getStatusDescription();
968 if (!m_bSuppressDialog
)
970 ScSolverNoSolutionDialog
aDialog(Application::GetDefDialogParent(), m_sErrorMessage
);
976 void SAL_CALL
ScSolverSettings::saveToFile() { m_pSettings
->SaveSolverSettings(); }
979 OUString SAL_CALL
ScSolverSettings::getImplementationName() { return u
"ScSolverSettings"_ustr
; }
981 sal_Bool SAL_CALL
ScSolverSettings::supportsService(const OUString
& rServiceName
)
983 return cppu::supportsService(this, rServiceName
);
986 uno::Sequence
<OUString
> SAL_CALL
ScSolverSettings::getSupportedServiceNames()
988 return { SC_SOLVERSETTINGS_SERVICE
};