Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/unoobj/solveruno.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
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/.
8
 *
9
 */
10
11
#include <rtl/math.hxx>
12
#include <vcl/svapp.hxx>
13
#include <vcl/vclenum.hxx>
14
#include <vcl/weld/MessageDialog.hxx>
15
#include <solveruno.hxx>
16
#include <docsh.hxx>
17
#include <docfunc.hxx>
18
#include <address.hxx>
19
#include <table.hxx>
20
#include <convuno.hxx>
21
#include <compiler.hxx>
22
#include <solverutil.hxx>
23
#include <rangeutl.hxx>
24
#include <scresid.hxx>
25
#include <globstr.hrc>
26
#include <optsolver.hxx>
27
#include <unonames.hxx>
28
#include <SolverSettings.hxx>
29
#include <o3tl/string_view.hxx>
30
#include <cppuhelper/supportsservice.hxx>
31
#include <comphelper/sequence.hxx>
32
#include <com/sun/star/sheet/XSolver.hpp>
33
#include <com/sun/star/sheet/XSolverDescription.hpp>
34
35
using namespace css;
36
37
constexpr OUString SC_SOLVERSETTINGS_SERVICE = u"com.sun.star.sheet.SolverSettings"_ustr;
38
39
namespace
40
{
41
// Returns the sc::ConstraintOperator equivalent to the Uno operator
42
sc::ConstraintOperator getScOperatorFromUno(sheet::SolverConstraintOperator aOperator)
43
0
{
44
0
    sc::ConstraintOperator aRet(sc::ConstraintOperator::CO_LESS_EQUAL);
45
46
0
    switch (aOperator)
47
0
    {
48
0
        case sheet::SolverConstraintOperator_EQUAL:
49
0
            aRet = sc::ConstraintOperator::CO_EQUAL;
50
0
            break;
51
0
        case sheet::SolverConstraintOperator_GREATER_EQUAL:
52
0
            aRet = sc::ConstraintOperator::CO_GREATER_EQUAL;
53
0
            break;
54
0
        case sheet::SolverConstraintOperator_BINARY:
55
0
            aRet = sc::ConstraintOperator::CO_BINARY;
56
0
            break;
57
0
        case sheet::SolverConstraintOperator_INTEGER:
58
0
            aRet = sc::ConstraintOperator::CO_INTEGER;
59
0
            break;
60
0
        default:
61
0
        {
62
            // This should never be reached
63
0
        }
64
0
    }
65
0
    return aRet;
66
0
}
67
68
// Returns the sheet::SolverConstraintOperator equivalent to sc::ConstraintOperator
69
sheet::SolverConstraintOperator getUnoOperatorFromSc(sc::ConstraintOperator nOperator)
70
0
{
71
0
    sheet::SolverConstraintOperator aRet(sheet::SolverConstraintOperator_LESS_EQUAL);
72
73
0
    switch (nOperator)
74
0
    {
75
0
        case sc::ConstraintOperator::CO_EQUAL:
76
0
            aRet = sheet::SolverConstraintOperator_EQUAL;
77
0
            break;
78
0
        case sc::ConstraintOperator::CO_GREATER_EQUAL:
79
0
            aRet = sheet::SolverConstraintOperator_GREATER_EQUAL;
80
0
            break;
81
0
        case sc::ConstraintOperator::CO_BINARY:
82
0
            aRet = sheet::SolverConstraintOperator_BINARY;
83
0
            break;
84
0
        case sc::ConstraintOperator::CO_INTEGER:
85
0
            aRet = sheet::SolverConstraintOperator_INTEGER;
86
0
            break;
87
0
        default:
88
0
        {
89
            // This should never be reached
90
0
        }
91
0
    }
92
0
    return aRet;
93
0
}
94
95
// Returns the CellRangeAddress struct from a ScRange
96
table::CellRangeAddress getRangeAddress(ScRange aRange)
97
0
{
98
0
    table::CellRangeAddress aRet;
99
0
    aRet.Sheet = aRange.aStart.Tab();
100
0
    aRet.StartColumn = aRange.aStart.Col();
101
0
    aRet.StartRow = aRange.aStart.Row();
102
0
    aRet.EndColumn = aRange.aEnd.Col();
103
0
    aRet.EndRow = aRange.aEnd.Row();
104
0
    return aRet;
105
0
}
106
107
// Tests if a string is a valid number
108
bool isValidNumber(const OUString& sValue, double& fValue)
109
0
{
110
0
    if (sValue.isEmpty())
111
0
        return false;
112
113
0
    rtl_math_ConversionStatus eConvStatus;
114
0
    sal_Int32 nEnd;
115
0
    fValue = rtl::math::stringToDouble(sValue, ScGlobal::getLocaleData().getNumDecimalSep()[0],
116
0
                                       ScGlobal::getLocaleData().getNumThousandSep()[0],
117
0
                                       &eConvStatus, &nEnd);
118
    // A conversion is only valid if nEnd is equal to the string length (all chars processed)
119
0
    return nEnd == sValue.getLength();
120
0
}
121
}
122
123
ScSolverSettings::ScSolverSettings(ScDocShell* pDocSh, uno::Reference<container::XNamed> xSheet)
124
0
    : m_pDocShell(pDocSh)
125
0
    , m_rDoc(m_pDocShell->GetDocument())
126
0
    , m_xSheet(std::move(xSheet))
127
0
    , m_nStatus(sheet::SolverStatus::NONE)
128
0
    , m_bSuppressDialog(false)
129
0
    , m_pTable(nullptr)
130
0
{
131
    // Initialize member variables with information about the current sheet
132
0
    OUString aName = m_xSheet->getName();
133
0
    SCTAB nTab;
134
0
    if (m_rDoc.GetTable(aName, nTab))
135
0
    {
136
0
        m_pTable = m_rDoc.FetchTable(nTab);
137
0
        m_pSettings = m_pTable->GetSolverSettings();
138
0
    }
139
0
}
140
141
0
ScSolverSettings::~ScSolverSettings() {}
142
143
bool ScSolverSettings::ParseRef(ScRange& rRange, const OUString& rInput, bool bAllowRange)
144
0
{
145
0
    ScAddress::Details aDetails(m_rDoc.GetAddressConvention(), 0, 0);
146
0
    ScRefFlags nFlags = rRange.ParseAny(rInput, m_rDoc, aDetails);
147
0
    SCTAB nCurTab(m_pTable->GetTab());
148
0
    if (nFlags & ScRefFlags::VALID)
149
0
    {
150
0
        if ((nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
151
0
            rRange.aStart.SetTab(nCurTab);
152
0
        if ((nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
153
0
            rRange.aEnd.SetTab(rRange.aStart.Tab());
154
0
        return (bAllowRange || rRange.aStart == rRange.aEnd);
155
0
    }
156
0
    else if (ScRangeUtil::MakeRangeFromName(rInput, m_rDoc, nCurTab, rRange, RUTL_NAMES, aDetails))
157
0
        return (bAllowRange || rRange.aStart == rRange.aEnd);
158
159
0
    return false;
160
0
}
161
162
bool ScSolverSettings::ParseWithNames(ScRangeList& rRanges, std::u16string_view rInput)
163
0
{
164
0
    if (rInput.empty())
165
0
        return true;
166
167
0
    ScAddress::Details aDetails(m_rDoc.GetAddressConvention(), 0, 0);
168
0
    SCTAB nCurTab(m_pTable->GetTab());
169
0
    sal_Unicode cDelimiter = ScCompiler::GetNativeSymbolChar(OpCode::ocSep);
170
0
    bool bError = false;
171
0
    sal_Int32 nIdx(0);
172
0
    do
173
0
    {
174
0
        ScRange aRange;
175
0
        OUString aRangeStr(o3tl::getToken(rInput, 0, cDelimiter, nIdx));
176
0
        ScRefFlags nFlags = aRange.ParseAny(aRangeStr, m_rDoc, aDetails);
177
0
        if (nFlags & ScRefFlags::VALID)
178
0
        {
179
0
            if ((nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
180
0
                aRange.aStart.SetTab(nCurTab);
181
0
            if ((nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
182
0
                aRange.aEnd.SetTab(aRange.aStart.Tab());
183
0
            rRanges.push_back(aRange);
184
0
        }
185
0
        else if (ScRangeUtil::MakeRangeFromName(aRangeStr, m_rDoc, nCurTab, aRange, RUTL_NAMES,
186
0
                                                aDetails))
187
0
            rRanges.push_back(aRange);
188
0
        else
189
0
            bError = true;
190
0
    } while (nIdx > 0);
191
192
0
    return !bError;
193
0
}
194
195
void ScSolverSettings::ShowErrorMessage(const OUString& rMessage)
196
0
{
197
0
    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
198
0
        Application::GetDefDialogParent(), VclMessageType::Warning, VclButtonsType::Ok, rMessage));
199
0
    xBox->run();
200
0
}
201
202
// XSolverSettings
203
sal_Int8 SAL_CALL ScSolverSettings::getObjectiveType()
204
0
{
205
0
    sal_Int8 aRet(sheet::SolverObjectiveType::MAXIMIZE);
206
0
    switch (m_pSettings->GetObjectiveType())
207
0
    {
208
0
        case sc::ObjectiveType::OT_MINIMIZE:
209
0
            aRet = sheet::SolverObjectiveType::MINIMIZE;
210
0
            break;
211
0
        case sc::ObjectiveType::OT_VALUE:
212
0
            aRet = sheet::SolverObjectiveType::VALUE;
213
0
            break;
214
0
        default:
215
0
        {
216
            // This should never be reached
217
0
        }
218
0
    }
219
0
    return aRet;
220
0
}
221
222
void SAL_CALL ScSolverSettings::setObjectiveType(sal_Int8 aObjType)
223
0
{
224
0
    sc::ObjectiveType eType(sc::ObjectiveType::OT_MAXIMIZE);
225
0
    switch (aObjType)
226
0
    {
227
0
        case sheet::SolverObjectiveType::MINIMIZE:
228
0
            eType = sc::ObjectiveType::OT_MINIMIZE;
229
0
            break;
230
0
        case sheet::SolverObjectiveType::VALUE:
231
0
            eType = sc::ObjectiveType::OT_VALUE;
232
0
            break;
233
0
        default:
234
0
        {
235
            // This should never be reached
236
0
        }
237
0
    }
238
0
    m_pSettings->SetObjectiveType(eType);
239
0
}
240
241
uno::Any SAL_CALL ScSolverSettings::getObjectiveCell()
242
0
{
243
    // The objective cell must be a valid cell address
244
0
    OUString sValue(m_pSettings->GetParameter(sc::SolverParameter::SP_OBJ_CELL));
245
246
    // Test if it is a valid cell reference; if so, return its CellAddress
247
0
    ScRange aRange;
248
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
249
0
    bool bOk = (aRange.ParseAny(sValue, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
250
0
    if (bOk)
251
0
    {
252
0
        SCTAB nTab1, nTab2;
253
0
        SCROW nRow1, nRow2;
254
0
        SCCOL nCol1, nCol2;
255
0
        aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
256
0
        table::CellAddress aAddress(nTab1, nCol1, nRow1);
257
0
        return uno::Any(aAddress);
258
0
    }
259
260
    // If converting to a CellAddress fails, returns the raw string
261
0
    return uno::Any(sValue);
262
0
}
263
264
// The value being set must be either a string referencing a single cell or
265
// a CellAddress instance
266
void SAL_CALL ScSolverSettings::setObjectiveCell(const uno::Any& aValue)
267
0
{
268
    // Check if a string value is being used
269
0
    OUString sValue;
270
0
    bool bIsString(aValue >>= sValue);
271
0
    if (bIsString)
272
0
    {
273
        // The string must correspond to a valid range; if not, an empty string is set
274
0
        ScRange aRange;
275
0
        OUString sRet;
276
0
        ScDocument& rDoc = m_pDocShell->GetDocument();
277
0
        const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
278
0
        bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
279
0
        if (bOk)
280
0
        {
281
0
            SCTAB nTab1, nTab2;
282
0
            SCROW nRow1, nRow2;
283
0
            SCCOL nCol1, nCol2;
284
0
            aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
285
            // The range must consist of a single cell
286
0
            if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
287
0
                sRet = sValue;
288
0
        }
289
0
        m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet);
290
0
        return;
291
0
    }
292
293
    // Check if a CellAddress is being used
294
0
    table::CellAddress aUnoAddress;
295
0
    bool bIsAddress(aValue >>= aUnoAddress);
296
0
    if (bIsAddress)
297
0
    {
298
0
        OUString sRet;
299
0
        ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
300
0
        sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
301
0
        m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet);
302
0
        return;
303
0
    }
304
305
    // If all fails, set an empty string
306
0
    m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, "");
307
0
}
308
309
uno::Any SAL_CALL ScSolverSettings::getGoalValue()
310
0
{
311
0
    OUString sValue(m_pSettings->GetParameter(sc::SolverParameter::SP_OBJ_VAL));
312
313
    // Test if it is a valid cell reference; if so, return its CellAddress
314
0
    ScRange aRange;
315
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
316
0
    bool bOk = (aRange.ParseAny(sValue, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
317
0
    if (bOk)
318
0
    {
319
0
        SCTAB nTab1, nTab2;
320
0
        SCROW nRow1, nRow2;
321
0
        SCCOL nCol1, nCol2;
322
0
        aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
323
0
        table::CellAddress aAddress(nTab1, nCol1, nRow1);
324
0
        return uno::Any(aAddress);
325
0
    }
326
327
0
    double fValue;
328
0
    bool bValid = isValidNumber(sValue, fValue);
329
0
    if (bValid)
330
0
        return uno::Any(fValue);
331
332
    // If the conversion was not successful, return "empty"
333
0
    return uno::Any();
334
0
}
335
336
void SAL_CALL ScSolverSettings::setGoalValue(const uno::Any& aValue)
337
0
{
338
    // Check if a numeric value is being used
339
0
    double fValue;
340
0
    bool bIsDouble(aValue >>= fValue);
341
0
    if (bIsDouble)
342
0
    {
343
        // The value must be set as a localized number
344
0
        OUString sLocalizedValue = rtl::math::doubleToUString(
345
0
            fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
346
0
            ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
347
0
        m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sLocalizedValue);
348
0
        return;
349
0
    }
350
351
    // Check if a string value is being used
352
0
    OUString sValue;
353
0
    bool bIsString(aValue >>= sValue);
354
0
    if (bIsString)
355
0
    {
356
        // The string must correspond to a valid range; if not, an empty string is set
357
0
        ScRange aRange;
358
0
        OUString sRet;
359
0
        ScDocument& rDoc = m_pDocShell->GetDocument();
360
0
        const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
361
0
        bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
362
0
        if (bOk)
363
0
        {
364
0
            SCTAB nTab1, nTab2;
365
0
            SCROW nRow1, nRow2;
366
0
            SCCOL nCol1, nCol2;
367
0
            aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
368
            // The range must consist of a single cell
369
0
            if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
370
0
                sRet = sValue;
371
0
        }
372
0
        m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet);
373
0
        return;
374
0
    }
375
376
    // Check if a CellAddress is being used
377
0
    table::CellAddress aUnoAddress;
378
0
    bool bIsAddress(aValue >>= aUnoAddress);
379
0
    if (bIsAddress)
380
0
    {
381
0
        OUString sRet;
382
0
        ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
383
0
        sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
384
0
        m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet);
385
0
        return;
386
0
    }
387
388
    // If all fails, set an empty string
389
0
    m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, "");
390
0
}
391
392
OUString SAL_CALL ScSolverSettings::getEngine()
393
0
{
394
0
    return m_pSettings->GetParameter(sc::SP_LO_ENGINE);
395
0
}
396
397
void SAL_CALL ScSolverSettings::setEngine(const OUString& sEngine)
398
0
{
399
    // Only change the engine if the new engine exists; otherwise leave it unchanged
400
0
    uno::Sequence<OUString> arrEngineNames;
401
0
    uno::Sequence<OUString> arrDescriptions;
402
0
    ScSolverUtil::GetImplementations(arrEngineNames, arrDescriptions);
403
0
    if (comphelper::findValue(arrEngineNames, sEngine) == -1)
404
0
        return;
405
406
0
    m_pSettings->SetParameter(sc::SP_LO_ENGINE, sEngine);
407
0
}
408
409
uno::Sequence<OUString> SAL_CALL ScSolverSettings::getAvailableEngines()
410
0
{
411
0
    uno::Sequence<OUString> arrEngineNames;
412
0
    uno::Sequence<OUString> arrDescriptions;
413
0
    ScSolverUtil::GetImplementations(arrEngineNames, arrDescriptions);
414
0
    return arrEngineNames;
415
0
}
416
417
uno::Sequence<uno::Any> SAL_CALL ScSolverSettings::getVariableCells()
418
0
{
419
    // Variable cells parameter is stored as a single string composed of valid ranges
420
    // separated using the formula separator character
421
0
    OUString sVarCells(m_pSettings->GetParameter(sc::SP_VAR_CELLS));
422
    // Delimiter character to separate ranges
423
0
    sal_Unicode cDelimiter = ScCompiler::GetNativeSymbolChar(OpCode::ocSep);
424
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
425
0
    uno::Sequence<uno::Any> aRangeSeq;
426
0
    sal_Int32 nIdx(0);
427
0
    sal_Int32 nArrPos(0);
428
429
0
    do
430
0
    {
431
0
        OUString aRangeStr(o3tl::getToken(sVarCells, 0, cDelimiter, nIdx));
432
        // Check if range is valid
433
0
        ScRange aRange;
434
0
        bool bOk
435
0
            = (aRange.ParseAny(aRangeStr, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
436
0
        if (bOk)
437
0
        {
438
0
            table::CellRangeAddress aRangeAddress(getRangeAddress(aRange));
439
0
            aRangeSeq.realloc(nArrPos + 1);
440
0
            auto pArrRanges = aRangeSeq.getArray();
441
0
            pArrRanges[nArrPos] <<= aRangeAddress;
442
0
            nArrPos++;
443
0
        }
444
0
    } while (nIdx > 0);
445
446
0
    return aRangeSeq;
447
0
}
448
449
void SAL_CALL ScSolverSettings::setVariableCells(const uno::Sequence<uno::Any>& aRanges)
450
0
{
451
0
    OUString sVarCells;
452
0
    bool bFirst(true);
453
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
454
0
    OUStringChar cDelimiter(ScCompiler::GetNativeSymbolChar(OpCode::ocSep));
455
456
0
    for (const auto& rRange : aRanges)
457
0
    {
458
0
        OUString sRange;
459
0
        bool bIsString(rRange >>= sRange);
460
0
        bool bOk(false);
461
0
        if (bIsString)
462
0
        {
463
0
            ScRange aRange;
464
0
            bOk = (aRange.ParseAny(sRange, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
465
0
        }
466
467
0
        table::CellRangeAddress aRangeAddress;
468
0
        bool bIsRangeAddress(rRange >>= aRangeAddress);
469
0
        if (bIsRangeAddress)
470
0
        {
471
0
            bOk = true;
472
0
            ScRange aRange(aRangeAddress.StartColumn, aRangeAddress.StartRow, aRangeAddress.Sheet,
473
0
                           aRangeAddress.EndColumn, aRangeAddress.EndRow, aRangeAddress.Sheet);
474
0
            sRange = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
475
0
        }
476
477
0
        if (bOk)
478
0
        {
479
0
            if (bFirst)
480
0
            {
481
0
                sVarCells = sRange;
482
0
                bFirst = false;
483
0
            }
484
0
            else
485
0
            {
486
0
                sVarCells += cDelimiter + sRange;
487
0
            }
488
0
        }
489
0
    }
490
491
0
    m_pSettings->SetParameter(sc::SP_VAR_CELLS, sVarCells);
492
0
}
493
494
uno::Sequence<sheet::ModelConstraint> SAL_CALL ScSolverSettings::getConstraints()
495
0
{
496
0
    uno::Sequence<sheet::ModelConstraint> aRet;
497
0
    std::vector<sc::ModelConstraint> vConstraints = m_pSettings->GetConstraints();
498
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
499
0
    sal_Int32 nCount(0);
500
501
0
    for (const auto& rConst : vConstraints)
502
0
    {
503
0
        sheet::ModelConstraint aConstraint;
504
505
        // Left side: must be valid string representing a cell range
506
0
        ScRange aLeftRange;
507
0
        bool bIsLeftRange
508
0
            = (aLeftRange.ParseAny(rConst.aLeftStr, m_rDoc, eConv) & ScRefFlags::VALID)
509
0
              == ScRefFlags::VALID;
510
0
        if (bIsLeftRange)
511
0
            aConstraint.Left <<= getRangeAddress(aLeftRange);
512
513
        // Operator
514
0
        aConstraint.Operator = getUnoOperatorFromSc(rConst.nOperator);
515
516
        // Right side: must be either
517
        // - valid string representing a cell range or
518
        // - a numeric value
519
0
        ScRange aRightRange;
520
0
        bool bIsRightRange
521
0
            = (aRightRange.ParseAny(rConst.aRightStr, m_rDoc, eConv) & ScRefFlags::VALID)
522
0
              == ScRefFlags::VALID;
523
0
        if (bIsRightRange)
524
0
        {
525
0
            aConstraint.Right <<= getRangeAddress(aRightRange);
526
0
        }
527
0
        else
528
0
        {
529
0
            double fValue;
530
0
            bool bValid = isValidNumber(rConst.aRightStr, fValue);
531
0
            if (bValid)
532
0
                aConstraint.Right <<= fValue;
533
0
            else
534
0
                aConstraint.Right = uno::Any();
535
0
        }
536
537
        // Adds the constraint to the sequence
538
0
        aRet.realloc(nCount + 1);
539
0
        auto pArrConstraints = aRet.getArray();
540
0
        pArrConstraints[nCount] = std::move(aConstraint);
541
0
        nCount++;
542
0
    }
543
544
0
    return aRet;
545
0
}
546
547
void SAL_CALL
548
ScSolverSettings::setConstraints(const uno::Sequence<sheet::ModelConstraint>& aConstraints)
549
0
{
550
0
    const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
551
0
    std::vector<sc::ModelConstraint> vRetConstraints;
552
553
0
    for (const auto& rConst : aConstraints)
554
0
    {
555
0
        sc::ModelConstraint aNewConst;
556
557
        // Left side
558
0
        OUString sLeft;
559
0
        bool bOkLeft(false);
560
0
        bool bIsString(rConst.Left >>= sLeft);
561
0
        if (bIsString)
562
0
        {
563
0
            ScRange aRange;
564
0
            bOkLeft
565
0
                = (aRange.ParseAny(sLeft, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
566
0
        }
567
568
0
        table::CellRangeAddress aLeftRangeAddress;
569
0
        bool bIsRangeAddress(rConst.Left >>= aLeftRangeAddress);
570
0
        if (bIsRangeAddress)
571
0
        {
572
0
            bOkLeft = true;
573
0
            ScRange aRange(aLeftRangeAddress.StartColumn, aLeftRangeAddress.StartRow,
574
0
                           aLeftRangeAddress.Sheet, aLeftRangeAddress.EndColumn,
575
0
                           aLeftRangeAddress.EndRow, aLeftRangeAddress.Sheet);
576
0
            sLeft = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
577
0
        }
578
579
0
        if (bOkLeft)
580
0
            aNewConst.aLeftStr = sLeft;
581
582
        // Constraint operator
583
0
        aNewConst.nOperator = getScOperatorFromUno(rConst.Operator);
584
585
        // Right side (may have numeric values)
586
0
        OUString sRight;
587
0
        bool bOkRight(false);
588
589
0
        double fValue;
590
0
        bool bIsDouble(rConst.Right >>= fValue);
591
0
        if (bIsDouble)
592
0
        {
593
0
            bOkRight = true;
594
            // The value must be set as a localized number
595
0
            sRight = rtl::math::doubleToUString(
596
0
                fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
597
0
                ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
598
0
        }
599
600
0
        bIsString = (rConst.Right >>= sRight);
601
0
        if (bIsString)
602
0
        {
603
0
            ScRange aRange;
604
0
            bOkRight
605
0
                = (aRange.ParseAny(sRight, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID;
606
0
        }
607
608
0
        table::CellRangeAddress aRightRangeAddress;
609
0
        bIsRangeAddress = (rConst.Right >>= aRightRangeAddress);
610
0
        if (bIsRangeAddress)
611
0
        {
612
0
            bOkRight = true;
613
0
            ScRange aRange(aRightRangeAddress.StartColumn, aRightRangeAddress.StartRow,
614
0
                           aRightRangeAddress.Sheet, aRightRangeAddress.EndColumn,
615
0
                           aRightRangeAddress.EndRow, aRightRangeAddress.Sheet);
616
0
            sRight = aRange.Format(m_rDoc, ScRefFlags::RANGE_ABS);
617
0
        }
618
619
0
        if (bOkRight)
620
0
            aNewConst.aRightStr = sRight;
621
622
0
        vRetConstraints.push_back(aNewConst);
623
0
    }
624
625
0
    m_pSettings->SetConstraints(std::move(vRetConstraints));
626
0
}
627
628
sal_Int32 SAL_CALL ScSolverSettings::getConstraintCount()
629
0
{
630
0
    if (!m_pTable)
631
0
        return -1;
632
633
0
    return static_cast<sal_Int32>(m_pSettings->GetConstraints().size());
634
0
}
635
636
uno::Sequence<beans::PropertyValue> SAL_CALL ScSolverSettings::getEngineOptions()
637
0
{
638
0
    uno::Sequence<beans::PropertyValue> aRet = ScSolverUtil::GetDefaults(getEngine());
639
0
    m_pSettings->GetEngineOptions(aRet);
640
0
    return aRet;
641
0
}
642
643
void SAL_CALL ScSolverSettings::setEngineOptions(const uno::Sequence<beans::PropertyValue>& rProps)
644
0
{
645
0
    m_pSettings->SetEngineOptions(rProps);
646
0
}
647
648
0
sal_Int8 SAL_CALL ScSolverSettings::getStatus() { return m_nStatus; }
649
650
0
OUString SAL_CALL ScSolverSettings::getErrorMessage() { return m_sErrorMessage; }
651
652
0
sal_Bool SAL_CALL ScSolverSettings::getSuppressDialog() { return m_bSuppressDialog; }
653
654
void SAL_CALL ScSolverSettings::setSuppressDialog(sal_Bool bSuppress)
655
0
{
656
0
    m_bSuppressDialog = bSuppress;
657
0
}
658
659
0
void SAL_CALL ScSolverSettings::reset() { m_pSettings->ResetToDefaults(); }
660
661
void SAL_CALL ScSolverSettings::solve()
662
0
{
663
    // Show the progress dialog
664
0
    auto xProgress = std::make_shared<ScSolverProgressDialog>(Application::GetDefDialogParent());
665
0
    if (!m_bSuppressDialog)
666
0
    {
667
        // Get the value of the timeout property of the solver engine
668
0
        uno::Sequence<beans::PropertyValue> aProps(getEngineOptions());
669
0
        sal_Int32 nTimeout(0);
670
0
        sal_Int32 nPropCount(aProps.getLength());
671
0
        bool bHasTimeout(false);
672
0
        for (sal_Int32 nProp = 0; nProp < nPropCount && !bHasTimeout; ++nProp)
673
0
        {
674
0
            const beans::PropertyValue& rValue = aProps[nProp];
675
0
            if (rValue.Name == SC_UNONAME_TIMEOUT)
676
0
                bHasTimeout = (rValue.Value >>= nTimeout);
677
0
        }
678
679
0
        if (bHasTimeout)
680
0
            xProgress->SetTimeLimit(nTimeout);
681
0
        else
682
0
            xProgress->HideTimeLimit();
683
684
0
        weld::DialogController::runAsync(xProgress, [](sal_Int32 /*nResult*/) {});
685
        // try to make sure the progress dialog is painted before continuing
686
0
        Application::Reschedule(true);
687
0
    }
688
689
    // Check the validity of the objective cell
690
0
    ScRange aObjRange;
691
0
    if (!ParseRef(aObjRange, m_pSettings->GetParameter(sc::SP_OBJ_CELL), false))
692
0
    {
693
0
        m_nStatus = sheet::SolverStatus::PARSE_ERROR;
694
0
        m_sErrorMessage = ScResId(STR_SOLVER_OBJCELL_FAIL);
695
0
        if (!m_bSuppressDialog)
696
0
            ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
697
0
        return;
698
0
    }
699
0
    table::CellAddress aObjCell(aObjRange.aStart.Tab(), aObjRange.aStart.Col(),
700
0
                                aObjRange.aStart.Row());
701
702
    // Check the validity of the variable cells
703
0
    ScRangeList aVarRanges;
704
0
    if (!ParseWithNames(aVarRanges, m_pSettings->GetParameter(sc::SP_VAR_CELLS)))
705
0
    {
706
0
        m_nStatus = sheet::SolverStatus::PARSE_ERROR;
707
0
        m_sErrorMessage = ScResId(STR_SOLVER_VARCELL_FAIL);
708
0
        if (!m_bSuppressDialog)
709
0
            ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
710
0
        return;
711
0
    }
712
713
    // Resolve ranges into single cells
714
0
    uno::Sequence<table::CellAddress> aVariableCells;
715
0
    sal_Int32 nVarPos(0);
716
0
    for (size_t nRangePos = 0, nRange = aVarRanges.size(); nRangePos < nRange; ++nRangePos)
717
0
    {
718
0
        ScRange aRange(aVarRanges[nRangePos]);
719
0
        aRange.PutInOrder();
720
0
        SCTAB nTab = aRange.aStart.Tab();
721
722
0
        sal_Int32 nAdd = (aRange.aEnd.Col() - aRange.aStart.Col() + 1)
723
0
                         * (aRange.aEnd.Row() - aRange.aStart.Row() + 1);
724
0
        aVariableCells.realloc(nVarPos + nAdd);
725
0
        auto pVariables = aVariableCells.getArray();
726
727
0
        for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
728
0
            for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
729
0
                pVariables[nVarPos++] = table::CellAddress(nTab, nCol, nRow);
730
0
    }
731
732
    // Prepare model constraints
733
0
    uno::Sequence<sheet::SolverConstraint> aConstraints;
734
0
    sal_Int32 nConstrPos = 0;
735
0
    for (const auto& rConstr : m_pSettings->GetConstraints())
736
0
    {
737
0
        if (!rConstr.aLeftStr.isEmpty())
738
0
        {
739
0
            sheet::SolverConstraint aConstraint;
740
0
            aConstraint.Operator = getUnoOperatorFromSc(rConstr.nOperator);
741
742
            // The left side of the constraint must be a valid range or a single cell
743
0
            ScRange aLeftRange;
744
0
            if (!ParseRef(aLeftRange, rConstr.aLeftStr, true))
745
0
            {
746
0
                m_nStatus = sheet::SolverStatus::PARSE_ERROR;
747
0
                m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
748
0
                if (!m_bSuppressDialog)
749
0
                    ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
750
0
                return;
751
0
            }
752
753
            // The right side can be either a cell range, a single cell or a numeric value
754
0
            bool bIsRange(false);
755
0
            ScRange aRightRange;
756
0
            if (ParseRef(aRightRange, rConstr.aRightStr, true))
757
0
            {
758
0
                if (aRightRange.aStart == aRightRange.aEnd)
759
0
                    aConstraint.Right
760
0
                        <<= table::CellAddress(aRightRange.aStart.Tab(), aRightRange.aStart.Col(),
761
0
                                               aRightRange.aStart.Row());
762
763
0
                else if (aRightRange.aEnd.Col() - aRightRange.aStart.Col()
764
0
                             == aLeftRange.aEnd.Col() - aLeftRange.aStart.Col()
765
0
                         && aRightRange.aEnd.Row() - aRightRange.aStart.Row()
766
0
                                == aLeftRange.aEnd.Row() - aLeftRange.aStart.Row())
767
                    // If the right side of the constraint is a range, it must have the
768
                    // same shape as the left side
769
0
                    bIsRange = true;
770
0
                else
771
0
                {
772
0
                    m_nStatus = sheet::SolverStatus::PARSE_ERROR;
773
0
                    m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
774
0
                    if (!m_bSuppressDialog)
775
0
                        ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
776
777
0
                    return;
778
0
                }
779
0
            }
780
0
            else
781
0
            {
782
                // Test if the right side is a numeric value
783
0
                sal_uInt32 nFormat = 0;
784
0
                double fValue(0);
785
0
                if (m_rDoc.GetFormatTable()->IsNumberFormat(rConstr.aRightStr, nFormat, fValue))
786
0
                    aConstraint.Right <<= fValue;
787
0
                else if (aConstraint.Operator != sheet::SolverConstraintOperator_INTEGER
788
0
                         && aConstraint.Operator != sheet::SolverConstraintOperator_BINARY)
789
0
                {
790
0
                    m_nStatus = sheet::SolverStatus::PARSE_ERROR;
791
0
                    m_sErrorMessage = ScResId(STR_INVALIDCONDITION);
792
0
                    if (!m_bSuppressDialog)
793
0
                        ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDCONDITION));
794
0
                    return;
795
0
                }
796
0
            }
797
798
            // Resolve constraint into single cells
799
0
            sal_Int32 nAdd = (aLeftRange.aEnd.Col() - aLeftRange.aStart.Col() + 1)
800
0
                             * (aLeftRange.aEnd.Row() - aLeftRange.aStart.Row() + 1);
801
0
            aConstraints.realloc(nConstrPos + nAdd);
802
0
            auto pConstraints = aConstraints.getArray();
803
804
0
            for (SCROW nRow = aLeftRange.aStart.Row(); nRow <= aLeftRange.aEnd.Row(); ++nRow)
805
0
                for (SCCOL nCol = aLeftRange.aStart.Col(); nCol <= aLeftRange.aEnd.Col(); ++nCol)
806
0
                {
807
0
                    aConstraint.Left = table::CellAddress(aLeftRange.aStart.Tab(), nCol, nRow);
808
0
                    if (bIsRange)
809
0
                        aConstraint.Right <<= table::CellAddress(
810
0
                            aRightRange.aStart.Tab(),
811
0
                            aRightRange.aStart.Col() + (nCol - aLeftRange.aStart.Col()),
812
0
                            aRightRange.aStart.Row() + (nRow - aLeftRange.aStart.Row()));
813
0
                    pConstraints[nConstrPos++] = aConstraint;
814
0
                }
815
0
        }
816
0
    }
817
818
    // Type of the objective function
819
    // If the objective is of type VALUE then a minimization model is used
820
0
    sc::ObjectiveType aObjType(m_pSettings->GetObjectiveType());
821
0
    bool bMaximize = aObjType == sc::ObjectiveType::OT_MAXIMIZE;
822
823
0
    if (aObjType == sc::ObjectiveType::OT_VALUE)
824
0
    {
825
        // An additional constraint is added to the model forcing
826
        // the objective cell to be equal to a given value
827
0
        sheet::SolverConstraint aConstraint;
828
0
        aConstraint.Left = aObjCell;
829
0
        aConstraint.Operator = sheet::SolverConstraintOperator_EQUAL;
830
831
0
        OUString aValStr = m_pSettings->GetParameter(sc::SP_OBJ_VAL);
832
0
        ScRange aRightRange;
833
834
0
        if (ParseRef(aRightRange, aValStr, false))
835
0
            aConstraint.Right <<= table::CellAddress(
836
0
                aRightRange.aStart.Tab(), aRightRange.aStart.Col(), aRightRange.aStart.Row());
837
0
        else
838
0
        {
839
            // Test if the right side is a numeric value
840
0
            sal_uInt32 nFormat = 0;
841
0
            double fValue(0);
842
0
            if (m_rDoc.GetFormatTable()->IsNumberFormat(aValStr, nFormat, fValue))
843
0
                aConstraint.Right <<= fValue;
844
0
            else
845
0
            {
846
0
                m_nStatus = sheet::SolverStatus::PARSE_ERROR;
847
0
                m_sErrorMessage = ScResId(STR_SOLVER_TARGETVALUE_FAIL);
848
0
                if (!m_bSuppressDialog)
849
0
                    ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
850
0
                return;
851
0
            }
852
0
        }
853
854
0
        aConstraints.realloc(nConstrPos + 1);
855
0
        aConstraints.getArray()[nConstrPos++] = std::move(aConstraint);
856
0
    }
857
858
    // Create a copy of document values in case the user chooses to restore them
859
0
    sal_Int32 nVarCount = aVariableCells.getLength();
860
0
    uno::Sequence<double> aOldValues(nVarCount);
861
0
    std::transform(std::cbegin(aVariableCells), std::cend(aVariableCells), aOldValues.getArray(),
862
0
                   [this](const table::CellAddress& rVariable) -> double {
863
0
                       ScAddress aCellPos;
864
0
                       ScUnoConversion::FillScAddress(aCellPos, rVariable);
865
0
                       return m_rDoc.GetValue(aCellPos);
866
0
                   });
867
868
    // Create and initialize solver
869
0
    uno::Reference<sheet::XSolver> xSolver = ScSolverUtil::GetSolver(getEngine());
870
0
    OSL_ENSURE(xSolver.is(), "Unable to get solver component");
871
0
    if (!xSolver.is())
872
0
    {
873
0
        if (!m_bSuppressDialog)
874
0
            ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDINPUT));
875
0
        m_nStatus = sheet::SolverStatus::ENGINE_ERROR;
876
0
        m_sErrorMessage = ScResId(STR_SOLVER_LOAD_FAIL);
877
0
        return;
878
0
    }
879
880
0
    rtl::Reference<ScModelObj> xDocument(m_pDocShell->GetModel());
881
0
    xSolver->setDocument(xDocument);
882
0
    xSolver->setObjective(aObjCell);
883
0
    xSolver->setVariables(aVariableCells);
884
0
    xSolver->setConstraints(aConstraints);
885
0
    xSolver->setMaximize(bMaximize);
886
887
    // Set engine options
888
0
    uno::Reference<beans::XPropertySet> xOptProp(xSolver, uno::UNO_QUERY);
889
0
    if (xOptProp.is())
890
0
    {
891
0
        for (const beans::PropertyValue& rValue : getEngineOptions())
892
0
        {
893
0
            try
894
0
            {
895
0
                xOptProp->setPropertyValue(rValue.Name, rValue.Value);
896
0
            }
897
0
            catch (uno::Exception&)
898
0
            {
899
0
                OSL_FAIL("Unable to set solver option property");
900
0
            }
901
0
        }
902
0
    }
903
904
0
    xSolver->solve();
905
0
    bool bSuccess = xSolver->getSuccess();
906
907
    // Close progress dialog
908
0
    if (!m_bSuppressDialog && xProgress)
909
0
        xProgress->response(RET_CLOSE);
910
911
0
    if (bSuccess)
912
0
    {
913
0
        m_nStatus = sheet::SolverStatus::SOLUTION_FOUND;
914
        // Write solution to the document
915
0
        uno::Sequence<double> aSolution = xSolver->getSolution();
916
0
        if (aSolution.getLength() == nVarCount)
917
0
        {
918
0
            m_pDocShell->LockPaint();
919
0
            ScDocFunc& rFunc = m_pDocShell->GetDocFunc();
920
0
            for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
921
0
            {
922
0
                ScAddress aCellPos;
923
0
                ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
924
0
                rFunc.SetValueCell(aCellPos, aSolution[nVarPos], false);
925
0
            }
926
0
            m_pDocShell->UnlockPaint();
927
0
        }
928
0
        else
929
0
        {
930
0
            OSL_FAIL("Wrong number of variables in the solver solution");
931
0
        }
932
933
        // Show success dialog
934
0
        if (!m_bSuppressDialog)
935
0
        {
936
            // Get formatted result from document to show in the Success dialog
937
0
            OUString aResultStr = m_rDoc.GetString(static_cast<SCCOL>(aObjCell.Column),
938
0
                                                   static_cast<SCROW>(aObjCell.Row),
939
0
                                                   static_cast<SCTAB>(aObjCell.Sheet));
940
941
0
            ScSolverSuccessDialog xSuccessDialog(Application::GetDefDialogParent(), aResultStr);
942
0
            bool bRestore(true);
943
0
            if (xSuccessDialog.run() == RET_OK)
944
                // Keep results in the document
945
0
                bRestore = false;
946
947
0
            if (bRestore)
948
0
            {
949
                // Restore values to the document
950
0
                m_pDocShell->LockPaint();
951
0
                ScDocFunc& rFunc = m_pDocShell->GetDocFunc();
952
0
                for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
953
0
                {
954
0
                    ScAddress aCellPos;
955
0
                    ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
956
0
                    rFunc.SetValueCell(aCellPos, aOldValues[nVarPos], false);
957
0
                }
958
0
                m_pDocShell->UnlockPaint();
959
0
            }
960
0
        }
961
0
    }
962
0
    else
963
0
    {
964
        // The solver failed to find a solution
965
0
        m_nStatus = sheet::SolverStatus::SOLUTION_NOT_FOUND;
966
0
        uno::Reference<sheet::XSolverDescription> xDesc(xSolver, uno::UNO_QUERY);
967
        // Get error message reported by the solver
968
0
        if (xDesc.is())
969
0
            m_sErrorMessage = xDesc->getStatusDescription();
970
0
        if (!m_bSuppressDialog)
971
0
        {
972
0
            ScSolverNoSolutionDialog aDialog(Application::GetDefDialogParent(), m_sErrorMessage);
973
0
            aDialog.run();
974
0
        }
975
0
    }
976
0
}
977
978
0
void SAL_CALL ScSolverSettings::saveToFile() { m_pSettings->SaveSolverSettings(); }
979
980
// XServiceInfo
981
0
OUString SAL_CALL ScSolverSettings::getImplementationName() { return u"ScSolverSettings"_ustr; }
982
983
sal_Bool SAL_CALL ScSolverSettings::supportsService(const OUString& rServiceName)
984
0
{
985
0
    return cppu::supportsService(this, rServiceName);
986
0
}
987
988
uno::Sequence<OUString> SAL_CALL ScSolverSettings::getSupportedServiceNames()
989
0
{
990
0
    return { SC_SOLVERSETTINGS_SERVICE };
991
0
}