Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/miscdlgs/optsolver.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <rangelst.hxx>
21
#include <sfx2/bindings.hxx>
22
#include <svl/numformat.hxx>
23
#include <utility>
24
#include <vcl/commandinfoprovider.hxx>
25
#include <vcl/weld/Dialog.hxx>
26
#include <vcl/weld/MessageDialog.hxx>
27
#include <vcl/weld/weld.hxx>
28
#include <vcl/svapp.hxx>
29
30
#include <reffact.hxx>
31
#include <docsh.hxx>
32
#include <docfunc.hxx>
33
#include <rangeutl.hxx>
34
#include <convuno.hxx>
35
#include <unonames.hxx>
36
#include <solveroptions.hxx>
37
#include <solverutil.hxx>
38
#include <globstr.hrc>
39
#include <scresid.hxx>
40
#include <comphelper/sequence.hxx>
41
#include <optsolver.hxx>
42
#include <table.hxx>
43
#include <TableFillingAndNavigationTools.hxx>
44
#include <tabvwsh.hxx>
45
46
#include <com/sun/star/beans/XPropertySetInfo.hpp>
47
#include <com/sun/star/sheet/SolverConstraint.hpp>
48
#include <com/sun/star/sheet/SolverConstraintOperator.hpp>
49
#include <com/sun/star/sheet/XSolverDescription.hpp>
50
#include <com/sun/star/sheet/XSolver.hpp>
51
#include <com/sun/star/sheet/SensitivityReport.hpp>
52
53
using namespace com::sun::star;
54
55
ScSolverProgressDialog::ScSolverProgressDialog(weld::Window* pParent)
56
0
    : GenericDialogController(pParent, u"modules/scalc/ui/solverprogressdialog.ui"_ustr,
57
0
                              u"SolverProgressDialog"_ustr)
58
0
    , m_xFtTime(m_xBuilder->weld_label(u"progress"_ustr))
59
0
{
60
0
}
61
62
ScSolverProgressDialog::~ScSolverProgressDialog()
63
0
{
64
0
}
65
66
void ScSolverProgressDialog::HideTimeLimit()
67
0
{
68
0
    m_xFtTime->hide();
69
0
}
70
71
void ScSolverProgressDialog::SetTimeLimit( sal_Int32 nSeconds )
72
0
{
73
0
    OUString aOld = m_xFtTime->get_label();
74
0
    OUString aNew = aOld.replaceFirst("#", OUString::number(nSeconds));
75
0
    m_xFtTime->set_label(aNew);
76
0
}
77
78
ScSolverNoSolutionDialog::ScSolverNoSolutionDialog(weld::Window* pParent, const OUString& rErrorText)
79
0
    : GenericDialogController(pParent, u"modules/scalc/ui/nosolutiondialog.ui"_ustr, u"NoSolutionDialog"_ustr)
80
0
    , m_xFtErrorText(m_xBuilder->weld_label(u"error"_ustr))
81
0
{
82
0
    m_xFtErrorText->set_label(rErrorText);
83
0
}
84
85
ScSolverNoSolutionDialog::~ScSolverNoSolutionDialog()
86
0
{
87
0
}
88
89
ScSolverSuccessDialog::ScSolverSuccessDialog(weld::Window* pParent, std::u16string_view rSolution)
90
0
    : GenericDialogController(pParent, u"modules/scalc/ui/solversuccessdialog.ui"_ustr, u"SolverSuccessDialog"_ustr)
91
0
    , m_xFtResult(m_xBuilder->weld_label(u"result"_ustr))
92
0
    , m_xBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
93
0
    , m_xBtnCancel(m_xBuilder->weld_button(u"cancel"_ustr))
94
0
{
95
0
    m_xBtnOk->connect_clicked(LINK(this, ScSolverSuccessDialog, ClickHdl));
96
0
    m_xBtnCancel->connect_clicked(LINK(this, ScSolverSuccessDialog, ClickHdl));
97
0
    OUString aMessage = m_xFtResult->get_label() + " " + rSolution;
98
0
    m_xFtResult->set_label(aMessage);
99
0
}
100
101
ScSolverSuccessDialog::~ScSolverSuccessDialog()
102
0
{
103
0
}
104
105
IMPL_LINK(ScSolverSuccessDialog, ClickHdl, weld::Button&, rBtn, void)
106
0
{
107
0
    if (&rBtn == m_xBtnOk.get())
108
0
        m_xDialog->response(RET_OK);
109
0
    else
110
0
        m_xDialog->response(RET_CANCEL);
111
0
}
112
113
ScCursorRefEdit::ScCursorRefEdit(std::unique_ptr<weld::Entry> xControl)
114
0
    : formula::RefEdit(std::move(xControl))
115
0
{
116
0
    mxEntry->connect_key_press(Link<const KeyEvent&, bool>()); //acknowledge we first remove the old one
117
0
    mxEntry->connect_key_press(LINK(this, ScCursorRefEdit, KeyInputHdl));
118
0
}
119
120
void ScCursorRefEdit::SetCursorLinks( const Link<ScCursorRefEdit&,void>& rUp, const Link<ScCursorRefEdit&,void>& rDown )
121
0
{
122
0
    maCursorUpLink = rUp;
123
0
    maCursorDownLink = rDown;
124
0
}
125
126
IMPL_LINK(ScCursorRefEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool)
127
0
{
128
0
    vcl::KeyCode aCode = rKEvt.GetKeyCode();
129
0
    bool bUp = (aCode.GetCode() == KEY_UP);
130
0
    bool bDown = (aCode.GetCode() == KEY_DOWN);
131
0
    if ( !aCode.IsShift() && !aCode.IsMod1() && !aCode.IsMod2() && ( bUp || bDown ) )
132
0
    {
133
0
        if ( bUp )
134
0
            maCursorUpLink.Call( *this );
135
0
        else
136
0
            maCursorDownLink.Call( *this );
137
0
        return true;
138
0
    }
139
0
    return formula::RefEdit::KeyInput(rKEvt);
140
0
}
141
142
ScOptSolverDlg::ScOptSolverDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent,
143
                               ScDocShell* pDocSh, const ScAddress& aCursorPos)
144
0
    : ScAnyRefDlgController(pB, pCW, pParent, u"modules/scalc/ui/solverdlg.ui"_ustr, u"SolverDialog"_ustr)
145
0
    , maInputError(ScResId(STR_INVALIDINPUT))
146
0
    , maConditionError(ScResId(STR_INVALIDCONDITION))
147
148
0
    , mpDocShell(pDocSh)
149
0
    , mrDoc(pDocSh->GetDocument())
150
0
    , mnCurTab(aCursorPos.Tab())
151
0
    , mbDlgLostFocus(false)
152
0
    , nScrollPos(0)
153
0
    , mpEdActive(nullptr)
154
0
    , m_xFtObjectiveCell(m_xBuilder->weld_label(u"targetlabel"_ustr))
155
0
    , m_xEdObjectiveCell(new formula::RefEdit(m_xBuilder->weld_entry(u"targetedit"_ustr)))
156
0
    , m_xRBObjectiveCell(new formula::RefButton(m_xBuilder->weld_button(u"targetbutton"_ustr)))
157
0
    , m_xRbMax(m_xBuilder->weld_radio_button(u"max"_ustr))
158
0
    , m_xRbMin(m_xBuilder->weld_radio_button(u"min"_ustr))
159
0
    , m_xRbValue(m_xBuilder->weld_radio_button(u"value"_ustr))
160
0
    , m_xEdTargetValue(new formula::RefEdit(m_xBuilder->weld_entry(u"valueedit"_ustr)))
161
0
    , m_xRBTargetValue(new formula::RefButton(m_xBuilder->weld_button(u"valuebutton"_ustr)))
162
0
    , m_xFtVariableCells(m_xBuilder->weld_label(u"changelabel"_ustr))
163
0
    , m_xEdVariableCells(new formula::RefEdit(m_xBuilder->weld_entry(u"changeedit"_ustr)))
164
0
    , m_xRBVariableCells(new formula::RefButton(m_xBuilder->weld_button(u"changebutton"_ustr)))
165
0
    , m_xFtCellRef(m_xBuilder->weld_label(u"cellreflabel"_ustr))
166
0
    , m_xEdLeft1(new ScCursorRefEdit(m_xBuilder->weld_entry(u"ref1edit"_ustr)))
167
0
    , m_xRBLeft1(new formula::RefButton(m_xBuilder->weld_button(u"ref1button"_ustr)))
168
0
    , m_xLbOp1(m_xBuilder->weld_combo_box(u"op1list"_ustr))
169
0
    , m_xFtConstraint(m_xBuilder->weld_label(u"constraintlabel"_ustr))
170
0
    , m_xEdRight1(new ScCursorRefEdit(m_xBuilder->weld_entry(u"val1edit"_ustr)))
171
0
    , m_xRBRight1(new formula::RefButton(m_xBuilder->weld_button(u"val1button"_ustr)))
172
0
    , m_xBtnDel1(m_xBuilder->weld_button(u"del1"_ustr))
173
0
    , m_xEdLeft2(new ScCursorRefEdit(m_xBuilder->weld_entry(u"ref2edit"_ustr)))
174
0
    , m_xRBLeft2(new formula::RefButton(m_xBuilder->weld_button(u"ref2button"_ustr)))
175
0
    , m_xLbOp2(m_xBuilder->weld_combo_box(u"op2list"_ustr))
176
0
    , m_xEdRight2(new ScCursorRefEdit(m_xBuilder->weld_entry(u"val2edit"_ustr)))
177
0
    , m_xRBRight2(new formula::RefButton(m_xBuilder->weld_button(u"val2button"_ustr)))
178
0
    , m_xBtnDel2(m_xBuilder->weld_button(u"del2"_ustr))
179
0
    , m_xEdLeft3(new ScCursorRefEdit(m_xBuilder->weld_entry(u"ref3edit"_ustr)))
180
0
    , m_xRBLeft3(new formula::RefButton(m_xBuilder->weld_button(u"ref3button"_ustr)))
181
0
    , m_xLbOp3(m_xBuilder->weld_combo_box(u"op3list"_ustr))
182
0
    , m_xEdRight3(new ScCursorRefEdit(m_xBuilder->weld_entry(u"val3edit"_ustr)))
183
0
    , m_xRBRight3(new formula::RefButton(m_xBuilder->weld_button(u"val3button"_ustr)))
184
0
    , m_xBtnDel3(m_xBuilder->weld_button(u"del3"_ustr))
185
0
    , m_xEdLeft4(new ScCursorRefEdit(m_xBuilder->weld_entry(u"ref4edit"_ustr)))
186
0
    , m_xRBLeft4(new formula::RefButton(m_xBuilder->weld_button(u"ref4button"_ustr)))
187
0
    , m_xLbOp4(m_xBuilder->weld_combo_box(u"op4list"_ustr))
188
0
    , m_xEdRight4(new ScCursorRefEdit(m_xBuilder->weld_entry(u"val4edit"_ustr)))
189
0
    , m_xRBRight4(new formula::RefButton(m_xBuilder->weld_button(u"val4button"_ustr)))
190
0
    , m_xBtnDel4(m_xBuilder->weld_button(u"del4"_ustr))
191
0
    , m_xScrollBar(m_xBuilder->weld_scrolled_window(u"scrollbar"_ustr, true))
192
0
    , m_xBtnOpt(m_xBuilder->weld_button(u"options"_ustr))
193
0
    , m_xBtnClose(m_xBuilder->weld_button(u"close"_ustr))
194
0
    , m_xBtnSolve(m_xBuilder->weld_button(u"ok"_ustr))
195
0
    , m_xBtnResetAll(m_xBuilder->weld_button(u"resetall"_ustr))
196
0
    , m_xResultFT(m_xBuilder->weld_label(u"result"_ustr))
197
0
    , m_xContents(m_xBuilder->weld_widget(u"grid"_ustr))
198
0
    , m_pSolverSettings(mrDoc.FetchTable(mnCurTab)->GetSolverSettings())
199
0
{
200
0
    m_xEdObjectiveCell->SetReferences(this, m_xFtObjectiveCell.get());
201
0
    m_xRBObjectiveCell->SetReferences(this, m_xEdObjectiveCell.get());
202
0
    m_xEdTargetValue->SetReferences(this, m_xResultFT.get());
203
0
    m_xRBTargetValue->SetReferences(this, m_xEdTargetValue.get());
204
0
    m_xEdVariableCells->SetReferences(this, m_xFtVariableCells.get());
205
0
    m_xRBVariableCells->SetReferences(this, m_xEdVariableCells.get());
206
0
    m_xEdLeft1->SetReferences(this, m_xFtCellRef.get());
207
0
    m_xRBLeft1->SetReferences(this, m_xEdLeft1.get());
208
0
    m_xEdRight1->SetReferences(this, m_xFtConstraint.get());
209
0
    m_xRBRight1->SetReferences(this, m_xEdRight1.get());
210
0
    m_xEdLeft2->SetReferences(this, m_xFtCellRef.get());
211
0
    m_xRBLeft2->SetReferences(this, m_xEdLeft2.get());
212
0
    m_xEdRight2->SetReferences(this, m_xFtConstraint.get());
213
0
    m_xRBRight2->SetReferences(this, m_xEdRight2.get());
214
0
    m_xEdLeft3->SetReferences(this, m_xFtCellRef.get());
215
0
    m_xRBLeft3->SetReferences(this, m_xEdLeft3.get());
216
0
    m_xEdRight3->SetReferences(this, m_xFtConstraint.get());
217
0
    m_xRBRight3->SetReferences(this, m_xEdRight3.get());
218
0
    m_xEdLeft4->SetReferences(this, m_xFtCellRef.get());
219
0
    m_xRBLeft4->SetReferences(this, m_xEdLeft4.get());
220
0
    m_xEdRight4->SetReferences(this, m_xFtConstraint.get());
221
0
    m_xRBRight4->SetReferences(this, m_xEdRight4.get());
222
223
0
    mpLeftEdit[0]    = m_xEdLeft1.get();
224
0
    mpLeftButton[0]  = m_xRBLeft1.get();
225
0
    mpRightEdit[0]   = m_xEdRight1.get();
226
0
    mpRightButton[0] = m_xRBRight1.get();
227
0
    mpOperator[0]    = m_xLbOp1.get();
228
0
    mpDelButton[0]   = m_xBtnDel1.get();
229
230
0
    mpLeftEdit[1]    = m_xEdLeft2.get();
231
0
    mpLeftButton[1]  = m_xRBLeft2.get();
232
0
    mpRightEdit[1]   = m_xEdRight2.get();
233
0
    mpRightButton[1] = m_xRBRight2.get();
234
0
    mpOperator[1]    = m_xLbOp2.get();
235
0
    mpDelButton[1]   = m_xBtnDel2.get();
236
237
0
    mpLeftEdit[2]    = m_xEdLeft3.get();
238
0
    mpLeftButton[2]  = m_xRBLeft3.get();
239
0
    mpRightEdit[2]   = m_xEdRight3.get();
240
0
    mpRightButton[2] = m_xRBRight3.get();
241
0
    mpOperator[2]    = m_xLbOp3.get();
242
0
    mpDelButton[2]   = m_xBtnDel3.get();
243
244
0
    mpLeftEdit[3]    = m_xEdLeft4.get();
245
0
    mpLeftButton[3]  = m_xRBLeft4.get();
246
0
    mpRightEdit[3]   = m_xEdRight4.get();
247
0
    mpRightButton[3] = m_xRBRight4.get();
248
0
    mpOperator[3]    = m_xLbOp4.get();
249
0
    mpDelButton[3]   = m_xBtnDel4.get();
250
251
0
    Init( aCursorPos );
252
0
}
253
254
ScOptSolverDlg::~ScOptSolverDlg()
255
0
{
256
0
}
257
258
void ScOptSolverDlg::Init(const ScAddress& rCursorPos)
259
0
{
260
0
    uno::Reference<frame::XFrame> xFrame = GetBindings().GetActiveFrame();
261
0
    auto xDelNm = vcl::CommandInfoProvider::GetXGraphicForCommand(u".uno:DeleteRows"_ustr, xFrame);
262
0
    for (weld::Button* pButton : mpDelButton)
263
0
        pButton->set_image(xDelNm);
264
265
0
    m_xBtnOpt->connect_clicked( LINK( this, ScOptSolverDlg, BtnHdl ) );
266
0
    m_xBtnClose->connect_clicked( LINK( this, ScOptSolverDlg, BtnHdl ) );
267
0
    m_xBtnSolve->connect_clicked( LINK( this, ScOptSolverDlg, BtnHdl ) );
268
0
    m_xBtnResetAll->connect_clicked( LINK( this, ScOptSolverDlg, BtnHdl ) );
269
270
0
    Link<formula::RefEdit&,void> aEditLink = LINK( this, ScOptSolverDlg, GetEditFocusHdl );
271
0
    Link<formula::RefButton&,void> aButtonLink = LINK( this, ScOptSolverDlg, GetButtonFocusHdl );
272
0
    m_xEdObjectiveCell->SetGetFocusHdl( aEditLink );
273
0
    m_xRBObjectiveCell->SetGetFocusHdl( aButtonLink );
274
0
    m_xEdTargetValue->SetGetFocusHdl( aEditLink );
275
0
    m_xRBTargetValue->SetGetFocusHdl( aButtonLink );
276
0
    m_xEdVariableCells->SetGetFocusHdl( aEditLink );
277
0
    m_xRBVariableCells->SetGetFocusHdl( aButtonLink );
278
0
    Link<weld::Widget&,void> aLink = LINK(this, ScOptSolverDlg, GetFocusHdl);
279
0
    m_xRbValue->connect_focus_in(aLink);
280
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
281
0
    {
282
0
        mpLeftEdit[nRow]->SetGetFocusHdl( aEditLink );
283
0
        mpLeftButton[nRow]->SetGetFocusHdl( aButtonLink );
284
0
        mpRightEdit[nRow]->SetGetFocusHdl( aEditLink );
285
0
        mpRightButton[nRow]->SetGetFocusHdl( aButtonLink );
286
0
        mpOperator[nRow]->connect_focus_in(aLink);
287
0
    }
288
289
0
    aEditLink = LINK( this, ScOptSolverDlg, LoseEditFocusHdl );
290
0
    aButtonLink = LINK( this, ScOptSolverDlg, LoseButtonFocusHdl );
291
0
    m_xEdObjectiveCell->SetLoseFocusHdl( aEditLink );
292
0
    m_xRBObjectiveCell->SetLoseFocusHdl( aButtonLink );
293
0
    m_xEdTargetValue->SetLoseFocusHdl( aEditLink );
294
0
    m_xRBTargetValue-> SetLoseFocusHdl( aButtonLink );
295
0
    m_xEdVariableCells->SetLoseFocusHdl( aEditLink );
296
0
    m_xRBVariableCells->SetLoseFocusHdl( aButtonLink );
297
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
298
0
    {
299
0
        mpLeftEdit[nRow]->SetLoseFocusHdl( aEditLink );
300
0
        mpLeftButton[nRow]->SetLoseFocusHdl( aButtonLink );
301
0
        mpRightEdit[nRow]->SetLoseFocusHdl( aEditLink );
302
0
        mpRightButton[nRow]->SetLoseFocusHdl( aButtonLink );
303
0
    }
304
305
0
    Link<ScCursorRefEdit&,void> aCursorUp = LINK( this, ScOptSolverDlg, CursorUpHdl );
306
0
    Link<ScCursorRefEdit&,void> aCursorDown = LINK( this, ScOptSolverDlg, CursorDownHdl );
307
0
    Link<formula::RefEdit&,void> aCondModify = LINK( this, ScOptSolverDlg, CondModifyHdl );
308
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
309
0
    {
310
0
        mpLeftEdit[nRow]->SetCursorLinks( aCursorUp, aCursorDown );
311
0
        mpRightEdit[nRow]->SetCursorLinks( aCursorUp, aCursorDown );
312
0
        mpLeftEdit[nRow]->SetModifyHdl( aCondModify );
313
0
        mpRightEdit[nRow]->SetModifyHdl( aCondModify );
314
0
        mpDelButton[nRow]->connect_clicked( LINK( this, ScOptSolverDlg, DelBtnHdl ) );
315
0
        mpOperator[nRow]->connect_changed( LINK( this, ScOptSolverDlg, SelectHdl ) );
316
0
    }
317
0
    m_xEdTargetValue->SetModifyHdl( LINK( this, ScOptSolverDlg, TargetModifyHdl ) );
318
319
0
    Size aSize(m_xContents->get_preferred_size());
320
0
    m_xContents->set_size_request(aSize.Width(), aSize.Height());
321
0
    m_xScrollBar->connect_vadjustment_value_changed( LINK( this, ScOptSolverDlg, ScrollHdl ) );
322
323
0
    m_xScrollBar->vadjustment_set_page_increment( EDIT_ROW_COUNT );
324
0
    m_xScrollBar->vadjustment_set_page_size( EDIT_ROW_COUNT );
325
    // Range is set in ShowConditions
326
327
    // get available solver implementations
328
    //! sort by descriptions?
329
0
    ScSolverUtil::GetImplementations( maImplNames, maDescriptions );
330
331
    // Load existing settings stored in the tab
332
0
    LoadSolverSettings();
333
0
    ShowConditions();
334
335
    // If no objective cell has been loaded, then use the selected cell
336
0
    if (m_xEdObjectiveCell->GetText().isEmpty())
337
0
    {
338
0
        OUString aCursorStr;
339
0
        if (!mrDoc.GetRangeAtBlock(ScRange(rCursorPos), aCursorStr))
340
0
            aCursorStr = rCursorPos.Format(ScRefFlags::ADDR_ABS, nullptr, mrDoc.GetAddressConvention());
341
0
        m_xEdObjectiveCell->SetRefString(aCursorStr);
342
0
    }
343
344
0
    m_xEdObjectiveCell->GrabFocus();
345
0
    mpEdActive = m_xEdObjectiveCell.get();
346
0
}
347
348
void ScOptSolverDlg::ReadConditions()
349
0
{
350
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
351
0
    {
352
0
        sc::ModelConstraint aRowEntry;
353
0
        aRowEntry.aLeftStr = mpLeftEdit[nRow]->GetText();
354
0
        aRowEntry.aRightStr = mpRightEdit[nRow]->GetText();
355
0
        aRowEntry.nOperator = OperatorIndexToConstraintOperator(mpOperator[nRow]->get_active());
356
357
0
        tools::Long nVecPos = nScrollPos + nRow;
358
0
        if ( nVecPos >= static_cast<tools::Long>(m_aConditions.size()) && !aRowEntry.IsDefault() )
359
0
            m_aConditions.resize( nVecPos + 1 );
360
361
0
        if ( nVecPos < static_cast<tools::Long>(m_aConditions.size()) )
362
0
            m_aConditions[nVecPos] = std::move(aRowEntry);
363
364
        // remove default entries at the end
365
0
        size_t nSize = m_aConditions.size();
366
0
        while ( nSize > 0 && m_aConditions[ nSize-1 ].IsDefault() )
367
0
            --nSize;
368
0
        m_aConditions.resize( nSize );
369
0
    }
370
0
}
371
372
void ScOptSolverDlg::ShowConditions()
373
0
{
374
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
375
0
    {
376
0
        sc::ModelConstraint aRowEntry;
377
378
0
        tools::Long nVecPos = nScrollPos + nRow;
379
0
        if ( nVecPos < static_cast<tools::Long>(m_aConditions.size()) )
380
0
            aRowEntry = m_aConditions[nVecPos];
381
382
0
        mpLeftEdit[nRow]->SetRefString( aRowEntry.aLeftStr );
383
0
        mpRightEdit[nRow]->SetRefString( aRowEntry.aRightStr );
384
0
        mpOperator[nRow]->set_active( aRowEntry.nOperator - 1);
385
0
    }
386
387
    // allow to scroll one page behind the visible or stored rows
388
0
    tools::Long nVisible = nScrollPos + EDIT_ROW_COUNT;
389
0
    tools::Long nMax = std::max( nVisible, static_cast<tools::Long>(m_aConditions.size()) );
390
0
    m_xScrollBar->vadjustment_configure(nScrollPos, nMax + EDIT_ROW_COUNT, 1, EDIT_ROW_COUNT - 1,
391
0
                                        EDIT_ROW_COUNT);
392
393
0
    EnableButtons();
394
0
}
395
396
void ScOptSolverDlg::EnableButtons()
397
0
{
398
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
399
0
    {
400
0
        tools::Long nVecPos = nScrollPos + nRow;
401
0
        mpDelButton[nRow]->set_sensitive(nVecPos < static_cast<tools::Long>(m_aConditions.size()));
402
0
    }
403
0
}
404
405
void ScOptSolverDlg::Close()
406
0
{
407
0
    if (m_xOptDlg)
408
0
        m_xOptDlg->response(RET_CANCEL);
409
0
    assert(!m_xOptDlg);
410
0
    DoClose( ScOptSolverDlgWrapper::GetChildWindowId() );
411
0
}
412
413
void ScOptSolverDlg::SetActive()
414
0
{
415
0
    if ( mbDlgLostFocus )
416
0
    {
417
0
        mbDlgLostFocus = false;
418
0
        if( mpEdActive )
419
0
            mpEdActive->GrabFocus();
420
0
    }
421
0
    else
422
0
    {
423
0
        m_xDialog->grab_focus();
424
0
    }
425
0
    RefInputDone();
426
0
}
427
428
void ScOptSolverDlg::SetReference( const ScRange& rRef, ScDocument& rDocP )
429
0
{
430
0
    if( !mpEdActive )
431
0
        return;
432
433
0
    if ( rRef.aStart != rRef.aEnd )
434
0
        RefInputStart(mpEdActive);
435
436
    // "target"/"value": single cell
437
0
    bool bSingle = ( mpEdActive == m_xEdObjectiveCell.get() || mpEdActive == m_xEdTargetValue.get() );
438
439
0
    OUString aStr;
440
0
    ScAddress aAdr = rRef.aStart;
441
0
    ScRange aNewRef( rRef );
442
0
    if ( bSingle )
443
0
        aNewRef.aEnd = aAdr;
444
445
0
    OUString aName;
446
0
    if ( rDocP.GetRangeAtBlock( aNewRef, aName ) )            // named range: show name
447
0
        aStr = aName;
448
0
    else                                                        // format cell/range reference
449
0
    {
450
0
        ScRefFlags nFmt = ( aAdr.Tab() == mnCurTab ) ? ScRefFlags::ADDR_ABS : ScRefFlags::ADDR_ABS_3D;
451
0
        if ( bSingle )
452
0
            aStr = aAdr.Format(nFmt, &rDocP, rDocP.GetAddressConvention());
453
0
        else
454
0
            aStr = rRef.Format(rDocP, nFmt | ScRefFlags::RANGE_ABS, rDocP.GetAddressConvention());
455
0
    }
456
457
    // variable cells can be several ranges, so only the selection is replaced
458
0
    if ( mpEdActive == m_xEdVariableCells.get() )
459
0
    {
460
0
        OUString aVal = mpEdActive->GetText();
461
0
        Selection aSel = mpEdActive->GetSelection();
462
0
        aSel.Normalize();
463
0
        aVal = aVal.replaceAt( aSel.Min(), aSel.Len(), aStr );
464
0
        Selection aNewSel( aSel.Min(), aSel.Min()+aStr.getLength() );
465
0
        mpEdActive->SetRefString( aVal );
466
0
        mpEdActive->SetSelection( aNewSel );
467
0
    }
468
0
    else
469
0
        mpEdActive->SetRefString( aStr );
470
471
0
    ReadConditions();
472
0
    EnableButtons();
473
474
    // select "Value of" if a ref is input into "target" edit
475
0
    if ( mpEdActive == m_xEdTargetValue.get() )
476
0
        m_xRbValue->set_active(true);
477
0
}
478
479
bool ScOptSolverDlg::IsRefInputMode() const
480
0
{
481
0
    return mpEdActive != nullptr;
482
0
}
483
484
// Loads solver settings into the dialog
485
void ScOptSolverDlg::LoadSolverSettings()
486
0
{
487
0
    m_xEdObjectiveCell->SetRefString(m_pSolverSettings->GetParameter(sc::SP_OBJ_CELL));
488
0
    m_xEdTargetValue->SetRefString(m_pSolverSettings->GetParameter(sc::SP_OBJ_VAL));
489
0
    m_xEdVariableCells->SetRefString(m_pSolverSettings->GetParameter(sc::SP_VAR_CELLS));
490
491
    // Objective type
492
0
    sc::ObjectiveType eType = m_pSolverSettings->GetObjectiveType();
493
0
    switch (eType)
494
0
    {
495
0
        case sc::OT_MAXIMIZE : m_xRbMax->set_active(true); break;
496
0
        case sc::OT_MINIMIZE : m_xRbMin->set_active(true); break;
497
0
        case sc::OT_VALUE    : m_xRbValue->set_active(true); break;
498
0
    }
499
500
    // Model constraints
501
0
    m_aConditions = m_pSolverSettings->GetConstraints();
502
503
    // Loads solver engine name
504
    // If the solver engine in the current settings are not supported, use the first available
505
0
    maEngine = m_pSolverSettings->GetParameter(sc::SP_LO_ENGINE);
506
0
    if (!IsEngineAvailable(maEngine))
507
0
    {
508
0
        maEngine = maImplNames[0];
509
0
        m_pSolverSettings->SetParameter(sc::SP_LO_ENGINE, maEngine);
510
0
    }
511
512
    // Query current engine options
513
0
    maProperties = ScSolverUtil::GetDefaults(maEngine);
514
0
    m_pSolverSettings->GetEngineOptions(maProperties);
515
0
}
516
517
// Set solver settings and save them to the file
518
// But first, checks if the settings have changed
519
void ScOptSolverDlg::SaveSolverSettings()
520
0
{
521
    // tdf#160104 If file does not have a solver model and the Solver dialog is set to its
522
    // default initial values (maximize is selected; no variable cells; no target value
523
    // and no constraints defined) then nothing needs to be saved
524
0
    if (!m_pSolverSettings->TabHasSolverModel() && m_xRbMax->get_active()
525
0
        && m_xEdTargetValue->GetText().isEmpty() && m_xEdVariableCells->GetText().isEmpty()
526
0
        && m_aConditions.size() == 0)
527
0
        return;
528
529
    // The current tab has a model; now we need to determined if it has been modified
530
0
    bool bModified = false;
531
532
    // Check objective cell, objective value and variable cells
533
0
    if (m_pSolverSettings->GetParameter(sc::SP_OBJ_CELL) != m_xEdObjectiveCell->GetText()
534
0
        || m_pSolverSettings->GetParameter(sc::SP_OBJ_VAL) != m_xEdTargetValue->GetText()
535
0
        || m_pSolverSettings->GetParameter(sc::SP_VAR_CELLS) != m_xEdVariableCells->GetText())
536
0
        bModified = true;
537
538
    // Check selected objective type and save it if changed
539
0
    sc::ObjectiveType aType = sc::OT_MAXIMIZE;
540
0
    if (m_xRbMin->get_active())
541
0
        aType = sc::OT_MINIMIZE;
542
0
    else if (m_xRbValue->get_active())
543
0
        aType = sc::OT_VALUE;
544
545
0
    if (m_pSolverSettings->GetObjectiveType() != aType)
546
0
        bModified = true;
547
548
    // Check if model constraints changed
549
0
    std::vector<sc::ModelConstraint> vCurConditions = m_pSolverSettings->GetConstraints();
550
0
    if (!bModified && vCurConditions.size() != m_aConditions.size())
551
0
        bModified = true;
552
0
    else
553
0
    {
554
        // Here the size of both vectors is the same
555
        // Now it needs to check the contents of the constraints
556
0
        for (size_t i = 0; i < vCurConditions.size(); i++)
557
0
        {
558
0
            if (vCurConditions[i].aLeftStr != m_aConditions[i].aLeftStr
559
0
                || vCurConditions[i].nOperator != m_aConditions[i].nOperator
560
0
                || vCurConditions[i].aRightStr != m_aConditions[i].aRightStr)
561
0
                bModified = true;
562
563
0
            if (bModified)
564
0
                break;
565
0
        }
566
0
    }
567
568
    // Check if the solver engine name and its options have changed
569
0
    if (m_pSolverSettings->GetParameter(sc::SP_LO_ENGINE) != maEngine)
570
0
    {
571
0
        bModified = true;
572
0
    }
573
0
    else
574
0
    {
575
        // The solver engine hasn't changed, so we need to check if engine options changed
576
        // Query current engine options; here we start by creating a copy of maProperties
577
        // to ensure the order is the same
578
0
        css::uno::Sequence<css::beans::PropertyValue> vCurOptions(maProperties);
579
0
        m_pSolverSettings->GetEngineOptions(vCurOptions);
580
581
0
        for (sal_Int32 i = 0; i < vCurOptions.getLength(); i++)
582
0
        {
583
0
            if (vCurOptions[i].Value != maProperties[i].Value)
584
0
            {
585
0
                bModified = true;
586
0
                break;
587
0
            }
588
0
        }
589
0
    }
590
591
    // Effectively save settings to file if modifications were made
592
0
    if (bModified)
593
0
    {
594
0
        m_pSolverSettings->SetParameter(sc::SP_OBJ_CELL, m_xEdObjectiveCell->GetText());
595
0
        m_pSolverSettings->SetParameter(sc::SP_OBJ_VAL, m_xEdTargetValue->GetText());
596
0
        m_pSolverSettings->SetParameter(sc::SP_VAR_CELLS, m_xEdVariableCells->GetText());
597
0
        m_pSolverSettings->SetObjectiveType(aType);
598
0
        m_pSolverSettings->SetConstraints(m_aConditions);
599
0
        m_pSolverSettings->SetParameter(sc::SP_LO_ENGINE, maEngine);
600
0
        m_pSolverSettings->SetEngineOptions(maProperties);
601
0
        m_pSolverSettings->SaveSolverSettings();
602
0
    }
603
0
}
604
// Test if a LO engine implementation exists
605
bool ScOptSolverDlg::IsEngineAvailable(std::u16string_view sEngineName)
606
0
{
607
0
    auto nIndex = comphelper::findValue(maImplNames, sEngineName);
608
0
    return nIndex != -1;
609
0
}
610
611
// Handler:
612
613
IMPL_LINK(ScOptSolverDlg, BtnHdl, weld::Button&, rBtn, void)
614
0
{
615
0
    auto xKeepAlive = shared_from_this();
616
0
    if (&rBtn == m_xBtnSolve.get() || &rBtn == m_xBtnClose.get())
617
0
    {
618
0
        bool bSolve = ( &rBtn == m_xBtnSolve.get() );
619
620
0
        SetDispatcherLock( false );
621
0
        SwitchToDocument();
622
623
0
        bool bClose = true;
624
0
        if ( bSolve )
625
0
            bClose = CallSolver();
626
627
0
        if ( bClose )
628
0
        {
629
            // Close: write dialog settings to DocShell for subsequent calls
630
0
            ReadConditions();
631
0
            SaveSolverSettings();
632
0
            response(RET_CLOSE);
633
0
        }
634
0
        else
635
0
        {
636
            // no solution -> dialog is kept open
637
0
            SetDispatcherLock( true );
638
0
        }
639
0
    }
640
0
    else if (&rBtn == m_xBtnOpt.get())
641
0
    {
642
        //! move options dialog to UI lib?
643
0
        m_xOptDlg = std::make_shared<ScSolverOptionsDialog>(m_xDialog.get(), maImplNames, maDescriptions, maEngine, maProperties);
644
0
        weld::DialogController::runAsync(m_xOptDlg, [this](sal_Int32 nResult){
645
0
            if (nResult == RET_OK)
646
0
            {
647
0
                maEngine = m_xOptDlg->GetEngine();
648
0
                maProperties = m_xOptDlg->GetProperties();
649
0
            }
650
0
            m_xOptDlg.reset();
651
0
        });
652
0
    }
653
0
    else if (&rBtn == m_xBtnResetAll.get())
654
0
    {
655
0
        OUString sEmpty;
656
0
        m_xEdObjectiveCell->SetText(sEmpty);
657
0
        m_xEdTargetValue->SetText(sEmpty);
658
0
        m_xEdVariableCells->SetText(sEmpty);
659
660
        // Get default property values of solver implementations
661
0
        maEngine = maImplNames[0];
662
0
        maProperties = ScSolverUtil::GetDefaults( maEngine );
663
664
        // Clear all conditions (Constraints)
665
0
        m_aConditions.clear();
666
0
        ShowConditions();
667
668
0
        m_xRbMax->set_active(true);
669
0
        m_xEdObjectiveCell->GrabFocus();
670
0
        mpEdActive = m_xEdObjectiveCell.get();
671
0
    }
672
0
}
673
674
IMPL_LINK( ScOptSolverDlg, GetEditFocusHdl, formula::RefEdit&, rCtrl, void )
675
0
{
676
0
    formula::RefEdit* pEdit = nullptr;
677
0
    mpEdActive = nullptr;
678
679
0
    if( &rCtrl == m_xEdObjectiveCell.get() )
680
0
        pEdit = mpEdActive = m_xEdObjectiveCell.get();
681
0
    else if( &rCtrl == m_xEdTargetValue.get() )
682
0
        pEdit = mpEdActive = m_xEdTargetValue.get();
683
0
    else if( &rCtrl == m_xEdVariableCells.get() )
684
0
        pEdit = mpEdActive = m_xEdVariableCells.get();
685
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
686
0
    {
687
0
        if( &rCtrl == mpLeftEdit[nRow]  )
688
0
            pEdit = mpEdActive = mpLeftEdit[nRow];
689
0
        else if( &rCtrl == mpRightEdit[nRow]  )
690
0
            pEdit = mpEdActive = mpRightEdit[nRow];
691
0
    }
692
693
0
    if( pEdit )
694
0
        pEdit->SelectAll();
695
0
}
696
697
IMPL_LINK( ScOptSolverDlg, GetButtonFocusHdl, formula::RefButton&, rCtrl, void )
698
0
{
699
0
    formula::RefEdit* pEdit = nullptr;
700
0
    mpEdActive = nullptr;
701
702
0
    if( &rCtrl == m_xRBObjectiveCell.get() )
703
0
        pEdit = mpEdActive = m_xEdObjectiveCell.get();
704
0
    else if( &rCtrl == m_xRBTargetValue.get() )
705
0
        pEdit = mpEdActive = m_xEdTargetValue.get();
706
0
    else if( &rCtrl == m_xRBVariableCells.get() )
707
0
        pEdit = mpEdActive = m_xEdVariableCells.get();
708
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
709
0
    {
710
0
        if( &rCtrl == mpLeftButton[nRow] )
711
0
            pEdit = mpEdActive = mpLeftEdit[nRow];
712
0
        else if( &rCtrl == mpRightButton[nRow] )
713
0
            pEdit = mpEdActive = mpRightEdit[nRow];
714
0
    }
715
716
0
    if( pEdit )
717
0
        pEdit->SelectAll();
718
0
}
719
720
721
IMPL_LINK(ScOptSolverDlg, GetFocusHdl, weld::Widget&, rCtrl, void)
722
0
{
723
0
    if( &rCtrl == m_xRbValue.get() )                   // focus on "Value of" radio button
724
0
        mpEdActive = m_xEdTargetValue.get();          // use value edit for ref input, but don't change selection
725
0
    else
726
0
    {
727
0
        for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
728
0
        {
729
0
            if( &rCtrl == mpOperator[nRow] )    // focus on "operator" list box
730
0
                mpEdActive = mpRightEdit[nRow];     // use right edit for ref input, but don't change selection
731
0
        }
732
0
    }
733
0
}
734
735
IMPL_LINK_NOARG(ScOptSolverDlg, LoseEditFocusHdl, formula::RefEdit&, void)
736
0
{
737
0
    mbDlgLostFocus = !m_xDialog->has_toplevel_focus();
738
0
}
739
740
IMPL_LINK_NOARG(ScOptSolverDlg, LoseButtonFocusHdl, formula::RefButton&, void)
741
0
{
742
0
    mbDlgLostFocus = !m_xDialog->has_toplevel_focus();
743
0
}
744
745
IMPL_LINK(ScOptSolverDlg, DelBtnHdl, weld::Button&, rBtn, void)
746
0
{
747
0
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
748
0
        if (&rBtn == mpDelButton[nRow])
749
0
        {
750
0
            bool bHadFocus = rBtn.has_focus();
751
752
0
            ReadConditions();
753
0
            tools::Long nVecPos = nScrollPos + nRow;
754
0
            if ( nVecPos < static_cast<tools::Long>(m_aConditions.size()) )
755
0
            {
756
0
                m_aConditions.erase( m_aConditions.begin() + nVecPos );
757
0
                ShowConditions();
758
759
0
                if ( bHadFocus && !rBtn.get_sensitive() )
760
0
                {
761
                    // If the button is disabled, focus would normally move to the next control,
762
                    // (left edit of the next row). Move it to left edit of this row instead.
763
764
0
                    mpEdActive = mpLeftEdit[nRow];
765
0
                    mpEdActive->GrabFocus();
766
0
                }
767
0
            }
768
0
        }
769
0
}
770
771
IMPL_LINK_NOARG(ScOptSolverDlg, TargetModifyHdl, formula::RefEdit&, void)
772
0
{
773
    // modify handler for the target edit:
774
    //  select "Value of" if something is input into the edit
775
0
    if ( !m_xEdTargetValue->GetText().isEmpty() )
776
0
        m_xRbValue->set_active(true);
777
0
}
778
779
IMPL_LINK_NOARG(ScOptSolverDlg, CondModifyHdl, formula::RefEdit&, void)
780
0
{
781
    // modify handler for the condition edits, just to enable/disable "delete" buttons
782
0
    ReadConditions();
783
0
    EnableButtons();
784
0
}
785
786
IMPL_LINK_NOARG(ScOptSolverDlg, SelectHdl, weld::ComboBox&, void)
787
0
{
788
    // select handler for operator list boxes, just to enable/disable "delete" buttons
789
0
    ReadConditions();
790
0
    EnableButtons();
791
0
}
792
793
IMPL_LINK_NOARG(ScOptSolverDlg, ScrollHdl, weld::ScrolledWindow&, void)
794
0
{
795
0
    ReadConditions();
796
0
    nScrollPos = m_xScrollBar->vadjustment_get_value();
797
0
    ShowConditions();
798
0
    if( mpEdActive )
799
0
        mpEdActive->SelectAll();
800
0
}
801
802
IMPL_LINK( ScOptSolverDlg, CursorUpHdl, ScCursorRefEdit&, rEdit, void )
803
0
{
804
0
    if ( &rEdit == mpLeftEdit[0] || &rEdit == mpRightEdit[0] )
805
0
    {
806
0
        if ( nScrollPos > 0 )
807
0
        {
808
0
            ReadConditions();
809
0
            --nScrollPos;
810
0
            ShowConditions();
811
0
            if( mpEdActive )
812
0
                mpEdActive->SelectAll();
813
0
        }
814
0
    }
815
0
    else
816
0
    {
817
0
        formula::RefEdit* pFocus = nullptr;
818
0
        for ( sal_uInt16 nRow = 1; nRow < EDIT_ROW_COUNT; ++nRow )      // second row or below: move focus
819
0
        {
820
0
            if ( &rEdit == mpLeftEdit[nRow] )
821
0
                pFocus = mpLeftEdit[nRow-1];
822
0
            else if ( &rEdit == mpRightEdit[nRow] )
823
0
                pFocus = mpRightEdit[nRow-1];
824
0
        }
825
0
        if (pFocus)
826
0
        {
827
0
            mpEdActive = pFocus;
828
0
            pFocus->GrabFocus();
829
0
        }
830
0
    }
831
0
}
832
833
IMPL_LINK( ScOptSolverDlg, CursorDownHdl, ScCursorRefEdit&, rEdit, void )
834
0
{
835
0
    if ( &rEdit == mpLeftEdit[EDIT_ROW_COUNT-1] || &rEdit == mpRightEdit[EDIT_ROW_COUNT-1] )
836
0
    {
837
        //! limit scroll position?
838
0
        ReadConditions();
839
0
        ++nScrollPos;
840
0
        ShowConditions();
841
0
        if( mpEdActive )
842
0
            mpEdActive->SelectAll();
843
0
    }
844
0
    else
845
0
    {
846
0
        formula::RefEdit* pFocus = nullptr;
847
0
        for ( sal_uInt16 nRow = 0; nRow+1 < EDIT_ROW_COUNT; ++nRow )      // before last row: move focus
848
0
        {
849
0
            if ( &rEdit == mpLeftEdit[nRow] )
850
0
                pFocus = mpLeftEdit[nRow+1];
851
0
            else if ( &rEdit == mpRightEdit[nRow] )
852
0
                pFocus = mpRightEdit[nRow+1];
853
0
        }
854
0
        if (pFocus)
855
0
        {
856
0
            mpEdActive = pFocus;
857
0
            pFocus->GrabFocus();
858
0
        }
859
0
    }
860
0
}
861
862
// Converts the position of the operator in the dropdown menu to a ConstraintOperator type
863
sc::ConstraintOperator ScOptSolverDlg::OperatorIndexToConstraintOperator(sal_Int32 nIndex)
864
0
{
865
0
    switch(nIndex)
866
0
    {
867
0
        case 0  : return sc::CO_LESS_EQUAL; break;
868
0
        case 1  : return sc::CO_EQUAL; break;
869
0
        case 2  : return sc::CO_GREATER_EQUAL; break;
870
0
        case 3  : return sc::CO_INTEGER; break;
871
0
        case 4  : return sc::CO_BINARY; break;
872
0
        default : return sc::CO_LESS_EQUAL; break;
873
0
    }
874
0
}
875
876
void ScOptSolverDlg::ShowError( bool bCondition, formula::RefEdit* pFocus )
877
0
{
878
0
    OUString aMessage = bCondition ? maConditionError : maInputError;
879
0
    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
880
0
                                              VclMessageType::Warning, VclButtonsType::Ok,
881
0
                                              aMessage));
882
0
    xBox->run();
883
0
    if (pFocus)
884
0
    {
885
0
        mpEdActive = pFocus;
886
0
        pFocus->GrabFocus();
887
0
    }
888
0
}
889
890
bool ScOptSolverDlg::ParseRef( ScRange& rRange, const OUString& rInput, bool bAllowRange )
891
0
{
892
0
    ScAddress::Details aDetails(mrDoc.GetAddressConvention(), 0, 0);
893
0
    ScRefFlags nFlags = rRange.ParseAny( rInput, mrDoc, aDetails );
894
0
    if ( nFlags & ScRefFlags::VALID )
895
0
    {
896
0
        if ( (nFlags & ScRefFlags::TAB_3D) == ScRefFlags::ZERO)
897
0
            rRange.aStart.SetTab( mnCurTab );
898
0
        if ( (nFlags & ScRefFlags::TAB2_3D) == ScRefFlags::ZERO)
899
0
            rRange.aEnd.SetTab( rRange.aStart.Tab() );
900
0
        return ( bAllowRange || rRange.aStart == rRange.aEnd );
901
0
    }
902
0
    else if ( ScRangeUtil::MakeRangeFromName( rInput, mrDoc, mnCurTab, rRange, RUTL_NAMES, aDetails ) )
903
0
        return ( bAllowRange || rRange.aStart == rRange.aEnd );
904
905
0
    return false;   // not recognized
906
0
}
907
908
bool ScOptSolverDlg::FindTimeout( sal_Int32& rTimeout )
909
0
{
910
0
    bool bFound = false;
911
912
0
    if ( !maProperties.hasElements() )
913
0
        maProperties = ScSolverUtil::GetDefaults( maEngine );   // get property defaults from component
914
915
0
    sal_Int32 nPropCount = maProperties.getLength();
916
0
    for (sal_Int32 nProp=0; nProp<nPropCount && !bFound; ++nProp)
917
0
    {
918
0
        const beans::PropertyValue& rValue = maProperties[nProp];
919
0
        if ( rValue.Name == SC_UNONAME_TIMEOUT )
920
0
            bFound = ( rValue.Value >>= rTimeout );
921
0
    }
922
0
    return bFound;
923
0
}
924
925
OUString ScOptSolverDlg::GetCellStrAddress(css::table::CellAddress aUnoAddress)
926
0
{
927
0
    ScAddress aScAddr;
928
0
    ScUnoConversion::FillScAddress(aScAddr, aUnoAddress);
929
0
    ScRange aRange(aScAddr);
930
0
    return aRange.Format(mrDoc, ScRefFlags::RANGE_ABS);
931
0
}
932
933
bool ScOptSolverDlg::CallSolver()       // return true -> close dialog after calling
934
0
{
935
    // show progress dialog
936
937
0
    auto xProgress = std::make_shared<ScSolverProgressDialog>(m_xDialog.get());
938
0
    sal_Int32 nTimeout = 0;
939
0
    if ( FindTimeout( nTimeout ) )
940
0
        xProgress->SetTimeLimit( nTimeout );
941
0
    else
942
0
        xProgress->HideTimeLimit();
943
944
0
    weld::DialogController::runAsync(xProgress, [](sal_Int32 /*nResult*/){});
945
946
    // try to make sure the progress dialog is painted before continuing
947
0
    Application::Reschedule(true);
948
949
    // collect solver parameters
950
951
0
    ReadConditions();
952
953
0
    rtl::Reference<ScModelObj> xDocument( mpDocShell->GetModel() );
954
955
0
    ScRange aObjRange;
956
0
    if ( !ParseRef( aObjRange, m_xEdObjectiveCell->GetText(), false ) )
957
0
    {
958
0
        ShowError( false, m_xEdObjectiveCell.get() );
959
0
        return false;
960
0
    }
961
0
    table::CellAddress aObjective( aObjRange.aStart.Tab(), aObjRange.aStart.Col(), aObjRange.aStart.Row() );
962
963
    // "changing cells" can be several ranges
964
0
    ScRangeList aVarRanges;
965
0
    if ( !ParseWithNames( aVarRanges, m_xEdVariableCells->GetText(), mrDoc ) )
966
0
    {
967
0
        ShowError( false, m_xEdVariableCells.get() );
968
0
        return false;
969
0
    }
970
0
    uno::Sequence<table::CellAddress> aVariables;
971
0
    sal_Int32 nVarPos = 0;
972
973
0
    for ( size_t nRangePos=0, nRange = aVarRanges.size(); nRangePos < nRange; ++nRangePos )
974
0
    {
975
0
        ScRange aRange( aVarRanges[ nRangePos ] );
976
0
        aRange.PutInOrder();
977
0
        SCTAB nTab = aRange.aStart.Tab();
978
979
        // resolve into single cells
980
981
0
        sal_Int32 nAdd = ( aRange.aEnd.Col() - aRange.aStart.Col() + 1 ) *
982
0
                         ( aRange.aEnd.Row() - aRange.aStart.Row() + 1 );
983
0
        aVariables.realloc( nVarPos + nAdd );
984
0
        auto pVariables = aVariables.getArray();
985
986
0
        for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
987
0
            for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
988
0
                pVariables[nVarPos++] = table::CellAddress( nTab, nCol, nRow );
989
0
    }
990
991
0
    uno::Sequence<sheet::SolverConstraint> aConstraints;
992
0
    sal_Int32 nConstrPos = 0;
993
0
    for ( const auto& rConstr : m_aConditions )
994
0
    {
995
0
        if ( !rConstr.aLeftStr.isEmpty() )
996
0
        {
997
0
            sheet::SolverConstraint aConstraint;
998
            // Order of list box entries must match enum values.
999
            // The enum SolverConstraintOperator starts at zero, whereas ConstraintOperator starts at 1
1000
            // hence we need to subtract -1 here
1001
0
            aConstraint.Operator = static_cast<sheet::SolverConstraintOperator>(rConstr.nOperator - 1);
1002
1003
0
            ScRange aLeftRange;
1004
0
            if ( !ParseRef( aLeftRange, rConstr.aLeftStr, true ) )
1005
0
            {
1006
0
                ShowError( true, nullptr );
1007
0
                return false;
1008
0
            }
1009
1010
0
            bool bIsRange = false;
1011
0
            ScRange aRightRange;
1012
0
            if ( ParseRef( aRightRange, rConstr.aRightStr, true ) )
1013
0
            {
1014
0
                if ( aRightRange.aStart == aRightRange.aEnd )
1015
0
                    aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
1016
0
                                                              aRightRange.aStart.Col(), aRightRange.aStart.Row() );
1017
0
                else if ( aRightRange.aEnd.Col()-aRightRange.aStart.Col() == aLeftRange.aEnd.Col()-aLeftRange.aStart.Col() &&
1018
0
                          aRightRange.aEnd.Row()-aRightRange.aStart.Row() == aLeftRange.aEnd.Row()-aLeftRange.aStart.Row() )
1019
0
                    bIsRange = true;    // same size as "left" range, resolve into single cells
1020
0
                else
1021
0
                {
1022
0
                    ShowError( true, nullptr );
1023
0
                    return false;
1024
0
                }
1025
0
            }
1026
0
            else
1027
0
            {
1028
0
                sal_uInt32 nFormat = 0;     //! explicit language?
1029
0
                double fValue = 0.0;
1030
0
                if ( mrDoc.GetFormatTable()->IsNumberFormat( rConstr.aRightStr, nFormat, fValue ) )
1031
0
                    aConstraint.Right <<= fValue;
1032
0
                else if ( aConstraint.Operator != sheet::SolverConstraintOperator_INTEGER &&
1033
0
                          aConstraint.Operator != sheet::SolverConstraintOperator_BINARY )
1034
0
                {
1035
0
                    ShowError( true, nullptr );
1036
0
                    return false;
1037
0
                }
1038
0
            }
1039
1040
            // resolve into single cells
1041
1042
0
            sal_Int32 nAdd = ( aLeftRange.aEnd.Col() - aLeftRange.aStart.Col() + 1 ) *
1043
0
                             ( aLeftRange.aEnd.Row() - aLeftRange.aStart.Row() + 1 );
1044
0
            aConstraints.realloc( nConstrPos + nAdd );
1045
0
            auto pConstraints = aConstraints.getArray();
1046
1047
0
            for (SCROW nRow = aLeftRange.aStart.Row(); nRow <= aLeftRange.aEnd.Row(); ++nRow)
1048
0
                for (SCCOL nCol = aLeftRange.aStart.Col(); nCol <= aLeftRange.aEnd.Col(); ++nCol)
1049
0
                {
1050
0
                    aConstraint.Left = table::CellAddress( aLeftRange.aStart.Tab(), nCol, nRow );
1051
0
                    if ( bIsRange )
1052
0
                        aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
1053
0
                            aRightRange.aStart.Col() + ( nCol - aLeftRange.aStart.Col() ),
1054
0
                            aRightRange.aStart.Row() + ( nRow - aLeftRange.aStart.Row() ) );
1055
1056
0
                    pConstraints[nConstrPos++] = aConstraint;
1057
0
                }
1058
0
        }
1059
0
    }
1060
1061
0
    bool bMaximize = m_xRbMax->get_active();
1062
0
    if ( m_xRbValue->get_active() )
1063
0
    {
1064
        // handle "value of" with an additional constraint (and then minimize)
1065
1066
0
        sheet::SolverConstraint aConstraint;
1067
0
        aConstraint.Left     = aObjective;
1068
0
        aConstraint.Operator = sheet::SolverConstraintOperator_EQUAL;
1069
1070
0
        OUString aValStr = m_xEdTargetValue->GetText();
1071
0
        ScRange aRightRange;
1072
0
        if ( ParseRef( aRightRange, aValStr, false ) )
1073
0
            aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
1074
0
                                                      aRightRange.aStart.Col(), aRightRange.aStart.Row() );
1075
0
        else
1076
0
        {
1077
0
            sal_uInt32 nFormat = 0;     //! explicit language?
1078
0
            double fValue = 0.0;
1079
0
            if ( mrDoc.GetFormatTable()->IsNumberFormat( aValStr, nFormat, fValue ) )
1080
0
                aConstraint.Right <<= fValue;
1081
0
            else
1082
0
            {
1083
0
                ShowError( false, m_xEdTargetValue.get() );
1084
0
                return false;
1085
0
            }
1086
0
        }
1087
1088
0
        aConstraints.realloc( nConstrPos + 1 );
1089
0
        aConstraints.getArray()[nConstrPos++] = std::move(aConstraint);
1090
0
    }
1091
1092
    // copy old document values
1093
1094
0
    sal_Int32 nVarCount = aVariables.getLength();
1095
0
    uno::Sequence<double> aOldValues( nVarCount );
1096
0
    std::transform(std::cbegin(aVariables), std::cend(aVariables), aOldValues.getArray(),
1097
0
        [this](const table::CellAddress& rVariable) -> double {
1098
0
            ScAddress aCellPos;
1099
0
            ScUnoConversion::FillScAddress( aCellPos, rVariable );
1100
0
            return mrDoc.GetValue( aCellPos );
1101
0
        });
1102
1103
    // create and initialize solver
1104
1105
0
    uno::Reference<sheet::XSolver> xSolver = ScSolverUtil::GetSolver( maEngine );
1106
0
    OSL_ENSURE( xSolver.is(), "can't get solver component" );
1107
0
    if ( !xSolver.is() )
1108
0
        return false;
1109
1110
0
    xSolver->setDocument( xDocument );
1111
0
    xSolver->setObjective( aObjective );
1112
0
    xSolver->setVariables( aVariables );
1113
0
    xSolver->setConstraints( aConstraints );
1114
0
    xSolver->setMaximize( bMaximize );
1115
1116
    // set options
1117
0
    uno::Reference<beans::XPropertySet> xOptProp(xSolver, uno::UNO_QUERY);
1118
0
    if ( xOptProp.is() )
1119
0
    {
1120
0
        for (const beans::PropertyValue& rValue : maProperties)
1121
0
        {
1122
0
            try
1123
0
            {
1124
0
                xOptProp->setPropertyValue( rValue.Name, rValue.Value );
1125
0
            }
1126
0
            catch ( uno::Exception & )
1127
0
            {
1128
0
                OSL_FAIL("Exception in solver option property");
1129
0
            }
1130
0
        }
1131
0
    }
1132
1133
    // tdf#162760 The solver engine may crash unexpectedly, so we need a try...catch here
1134
0
    bool bSuccess(false);
1135
0
    try
1136
0
    {
1137
0
        xSolver->solve();
1138
0
        bSuccess = xSolver->getSuccess();
1139
0
    }
1140
0
    catch (const uno::RuntimeException&)
1141
0
    {
1142
0
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
1143
0
                                                  VclMessageType::Error, VclButtonsType::Ok,
1144
0
                                                  ScResId(STR_SOLVER_ENGINE_ERROR)));
1145
0
        xBox->run();
1146
0
    }
1147
1148
0
    xProgress->response(RET_CLOSE);
1149
1150
0
    bool bClose = false;
1151
0
    bool bRestore = true;   // restore old values unless a solution is accepted
1152
0
    if ( bSuccess )
1153
0
    {
1154
        // put solution into document so it is visible when asking
1155
0
        uno::Sequence<double> aSolution = xSolver->getSolution();
1156
0
        if ( aSolution.getLength() == nVarCount )
1157
0
        {
1158
0
            mpDocShell->LockPaint();
1159
0
            ScDocFunc &rFunc = mpDocShell->GetDocFunc();
1160
0
            for (nVarPos=0; nVarPos<nVarCount; ++nVarPos)
1161
0
            {
1162
0
                ScAddress aCellPos;
1163
0
                ScUnoConversion::FillScAddress(aCellPos, aVariables[nVarPos]);
1164
0
                rFunc.SetValueCell(aCellPos, aSolution[nVarPos], false);
1165
0
            }
1166
0
            mpDocShell->UnlockPaint();
1167
0
        }
1168
        //! else error?
1169
1170
        // take formatted result from document (result value from component is ignored)
1171
0
        OUString aResultStr = mrDoc.GetString(
1172
0
            static_cast<SCCOL>(aObjective.Column), static_cast<SCROW>(aObjective.Row),
1173
0
            static_cast<SCTAB>(aObjective.Sheet));
1174
1175
0
        ScSolverSuccessDialog aDialog(m_xDialog.get(), aResultStr);
1176
0
        if (aDialog.run() == RET_OK)
1177
0
        {
1178
            // keep results and close dialog
1179
0
            bRestore = false;
1180
0
            bClose = true;
1181
0
        }
1182
0
    }
1183
0
    else
1184
0
    {
1185
0
        OUString aError;
1186
0
        uno::Reference<sheet::XSolverDescription> xDesc( xSolver, uno::UNO_QUERY );
1187
0
        if ( xDesc.is() )
1188
0
            aError = xDesc->getStatusDescription();         // error description from component
1189
0
        ScSolverNoSolutionDialog aDialog(m_xDialog.get(), aError);
1190
0
        aDialog.run();
1191
0
    }
1192
1193
0
    if ( bRestore )         // restore old values
1194
0
    {
1195
0
        mpDocShell->LockPaint();
1196
0
        ScDocFunc &rFunc = mpDocShell->GetDocFunc();
1197
0
        for (nVarPos=0; nVarPos<nVarCount; ++nVarPos)
1198
0
        {
1199
0
            ScAddress aCellPos;
1200
0
            ScUnoConversion::FillScAddress( aCellPos, aVariables[nVarPos] );
1201
0
            rFunc.SetValueCell(aCellPos, aOldValues[nVarPos], false);
1202
0
        }
1203
0
        mpDocShell->UnlockPaint();
1204
0
    }
1205
1206
    // Generate sensitivity report if user wants it
1207
0
    uno::Reference<css::beans::XPropertySetInfo> xInfo = xOptProp->getPropertySetInfo();
1208
0
    bool bUserWantsReport = false;
1209
0
    if (xInfo->hasPropertyByName("GenSensitivityReport"))
1210
0
        xOptProp->getPropertyValue("GenSensitivityReport") >>= bUserWantsReport;
1211
1212
0
    if (bSuccess && bUserWantsReport)
1213
0
    {
1214
        // Retrieve the sensitivity analysis report
1215
0
        css::sheet::SensitivityReport aSensitivity;
1216
0
        bool bHasReportObj = xOptProp->getPropertyValue("SensitivityReport") >>= aSensitivity;
1217
1218
0
        if (bHasReportObj && aSensitivity.HasReport)
1219
0
        {
1220
            // Define the Tab name where the sensitivity analysis will be written to
1221
0
            OUString sNewTabName;
1222
0
            SCTAB nNewTab;
1223
0
            mrDoc.GetName(mnCurTab, sNewTabName);
1224
0
            sNewTabName += "_" + ScResId(STR_SENSITIVITY);
1225
            // Check if the new Tab name exists
1226
0
            if (mrDoc.GetTable(sNewTabName, nNewTab))
1227
0
            {
1228
                // Add numbers to the end of the Tab name to make it unique
1229
0
                SCTAB i = 1;
1230
0
                OUString aName;
1231
0
                do
1232
0
                {
1233
0
                    i++;
1234
0
                    aName = sNewTabName + "_" + OUString::number(static_cast<sal_Int32>(i));
1235
0
                }
1236
0
                while(mrDoc.GetTable(aName, nNewTab));
1237
0
                sNewTabName = aName;
1238
0
            }
1239
1240
            // Insert new sheet to the document and start writing the report
1241
0
            ScDocFunc &rFunc = mpDocShell->GetDocFunc();
1242
0
            rFunc.InsertTable(mnCurTab + 1, sNewTabName, false, false);
1243
0
            SCTAB nReportTab;
1244
0
            if (!mrDoc.GetTable(sNewTabName, nReportTab))
1245
0
            {
1246
0
                SAL_WARN("sc", "Could not get the just inserted table!");
1247
0
                return false;
1248
0
            }
1249
1250
            // Used to input data in the new sheet
1251
0
            ScAddress aOutputAddress(0, 0, nReportTab);
1252
0
            ScAddress::Details mAddressDetails(mrDoc, aOutputAddress);
1253
0
            AddressWalkerWriter aOutput(aOutputAddress, mpDocShell, mrDoc,
1254
0
                                        formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_ENGLISH, mAddressDetails.eConv));
1255
0
            aOutput.writeBoldString(ScResId(STR_SENSITIVITY_TITLE));
1256
0
            aOutput.newLine();
1257
0
            aOutput.writeString(ScResId(STR_SOLVER_ENGINE) + " " + maEngine);
1258
0
            aOutput.newLine();
1259
0
            aOutput.newLine();
1260
1261
            // Objective cell section
1262
0
            aOutput.writeBoldString(ScResId(STR_SENSITIVITY_OBJCELL));
1263
0
            aOutput.newLine();
1264
0
            aOutput.formatAsColumnHeader(2);
1265
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_CELL));
1266
0
            aOutput.nextColumn();
1267
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE));
1268
0
            aOutput.newLine();
1269
0
            aOutput.formatTableBottom(2);
1270
0
            aOutput.writeString(GetCellStrAddress(xSolver->getObjective()));
1271
0
            aOutput.nextColumn();
1272
0
            aOutput.writeValue(xSolver->getResultValue());
1273
0
            aOutput.newLine();
1274
0
            aOutput.newLine();
1275
1276
            // Variable cell section
1277
0
            aOutput.writeBoldString(ScResId(STR_SENSITIVITY_VARCELLS));
1278
0
            aOutput.newLine();
1279
0
            aOutput.formatAsColumnHeader(6);
1280
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_CELL));
1281
0
            aOutput.nextColumn();
1282
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE));
1283
0
            aOutput.nextColumn();
1284
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_REDUCED));
1285
0
            aOutput.nextColumn();
1286
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_OBJCOEFF));
1287
0
            aOutput.nextColumn();
1288
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_DECREASE));
1289
0
            aOutput.nextColumn();
1290
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_INCREASE));
1291
0
            aOutput.newLine();
1292
1293
0
            uno::Sequence<double> aSolution = xSolver->getSolution();
1294
0
            uno::Sequence<double> aObjCoefficients = aSensitivity.ObjCoefficients;
1295
0
            uno::Sequence<double> aObjReducedCosts = aSensitivity.ObjReducedCosts;
1296
0
            uno::Sequence<double> aObjAllowableDecreases = aSensitivity.ObjAllowableDecreases;
1297
0
            uno::Sequence<double> aObjAllowableIncreases = aSensitivity.ObjAllowableIncreases;
1298
0
            sal_Int32 nRows = aVariables.getLength();
1299
0
            for (sal_Int32 i = 0; i < nRows; i++)
1300
0
            {
1301
0
                if (i == nRows - 1)
1302
0
                    aOutput.formatTableBottom(6);
1303
0
                aOutput.writeString(GetCellStrAddress(aVariables[i]));
1304
0
                aOutput.nextColumn();
1305
0
                aOutput.writeValue(aSolution[i]);
1306
0
                aOutput.nextColumn();
1307
0
                aOutput.writeValue(aObjReducedCosts[i]);
1308
0
                aOutput.nextColumn();
1309
0
                aOutput.writeValue(aObjCoefficients[i]);
1310
0
                aOutput.nextColumn();
1311
0
                aOutput.writeValue(aObjAllowableDecreases[i]);
1312
0
                aOutput.nextColumn();
1313
0
                aOutput.writeValue(aObjAllowableIncreases[i]);
1314
0
                aOutput.newLine();
1315
0
            }
1316
0
            aOutput.newLine();
1317
1318
            // Constraints section
1319
0
            aOutput.writeBoldString(ScResId(STR_SENSITIVITY_CONSTRAINTS));
1320
0
            aOutput.newLine();
1321
0
            aOutput.formatAsColumnHeader(6);
1322
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_CELL));
1323
0
            aOutput.nextColumn();
1324
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE));
1325
0
            aOutput.nextColumn();
1326
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_SHADOWPRICE));
1327
0
            aOutput.nextColumn();
1328
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_RHS));
1329
0
            aOutput.nextColumn();
1330
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_DECREASE));
1331
0
            aOutput.nextColumn();
1332
0
            aOutput.writeString(ScResId(STR_SENSITIVITY_INCREASE));
1333
0
            aOutput.newLine();
1334
1335
0
            uno::Sequence<double> aConstrValues = aSensitivity.ConstrValues;
1336
0
            uno::Sequence<double> aConstrRHS = aSensitivity.ConstrRHS;
1337
0
            uno::Sequence<double> aConstrShadowPrices = aSensitivity.ConstrShadowPrices;
1338
0
            uno::Sequence<double> aConstrAllowableDecreases = aSensitivity.ConstrAllowableDecreases;
1339
0
            uno::Sequence<double> aConstrAllowableIncreases = aSensitivity.ConstrAllowableIncreases;
1340
0
            nRows = aConstraints.getLength();
1341
0
            for (sal_Int32 i = 0; i < nRows; i++)
1342
0
            {
1343
0
                if (i == nRows - 1)
1344
0
                    aOutput.formatTableBottom(6);
1345
0
                aOutput.writeString(GetCellStrAddress(aConstraints[i].Left));
1346
0
                aOutput.nextColumn();
1347
0
                aOutput.writeValue(aConstrValues[i]);
1348
0
                aOutput.nextColumn();
1349
0
                aOutput.writeValue(aConstrShadowPrices[i]);
1350
0
                aOutput.nextColumn();
1351
0
                aOutput.writeValue(aConstrRHS[i]);
1352
0
                aOutput.nextColumn();
1353
0
                aOutput.writeValue(aConstrAllowableDecreases[i]);
1354
0
                aOutput.nextColumn();
1355
0
                aOutput.writeValue(aConstrAllowableIncreases[i]);
1356
0
                aOutput.newLine();
1357
0
            }
1358
1359
            // Disable grid lines in the sensitivity report
1360
0
            if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
1361
0
            {
1362
0
                ScViewData& rData = pViewSh->GetViewData();
1363
0
                rData.SetTabNo(nReportTab);
1364
0
                rData.SetShowGrid(false);
1365
0
                rData.SetTabNo(mnCurTab);
1366
0
            }
1367
0
        }
1368
0
    }
1369
1370
0
    return bClose;
1371
0
}
1372
1373
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */