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 <memory>
21 #include <solveroptions.hxx>
22 #include <global.hxx>
23 #include <miscuno.hxx>
24 #include <solverutil.hxx>
25
26 #include <rtl/math.hxx>
27 #include <unotools/collatorwrapper.hxx>
28 #include <unotools/localedatawrapper.hxx>
29 #include <osl/diagnose.h>
30
31 #include <algorithm>
32
33 #include <com/sun/star/sheet/XSolver.hpp>
34 #include <com/sun/star/sheet/XSolverDescription.hpp>
35 #include <com/sun/star/beans/PropertyValue.hpp>
36
37 using namespace com::sun::star;
38
39 namespace {
40
41 /// Helper for sorting properties
42 struct ScSolverOptionsEntry
43 {
44 sal_Int32 nPosition;
45 OUString aDescription;
46
ScSolverOptionsEntry__anon24fcc6cc0111::ScSolverOptionsEntry47 ScSolverOptionsEntry() : nPosition(0) {}
48
operator <__anon24fcc6cc0111::ScSolverOptionsEntry49 bool operator< (const ScSolverOptionsEntry& rOther) const
50 {
51 return (ScGlobal::GetCollator()->compareString( aDescription, rOther.aDescription ) < 0);
52 }
53 };
54
55 }
56
ScSolverOptionsDialog(weld::Window * pParent,const uno::Sequence<OUString> & rImplNames,const uno::Sequence<OUString> & rDescriptions,const OUString & rEngine,const uno::Sequence<beans::PropertyValue> & rProperties)57 ScSolverOptionsDialog::ScSolverOptionsDialog(weld::Window* pParent,
58 const uno::Sequence<OUString>& rImplNames,
59 const uno::Sequence<OUString>& rDescriptions,
60 const OUString& rEngine,
61 const uno::Sequence<beans::PropertyValue>& rProperties )
62 : GenericDialogController(pParent, "modules/scalc/ui/solveroptionsdialog.ui", "SolverOptionsDialog")
63 , maImplNames(rImplNames)
64 , maEngine(rEngine)
65 , maProperties(rProperties)
66 , m_xLbEngine(m_xBuilder->weld_combo_box("engine"))
67 , m_xLbSettings(m_xBuilder->weld_tree_view("settings"))
68 , m_xBtnEdit(m_xBuilder->weld_button("edit"))
69 {
70 m_xLbSettings->set_size_request(m_xLbSettings->get_approximate_digit_width() * 32,
71 m_xLbSettings->get_height_rows(6));
72
73 m_xLbSettings->enable_toggle_buttons(weld::ColumnToggleType::Check);
74
75 m_xLbEngine->connect_changed( LINK( this, ScSolverOptionsDialog, EngineSelectHdl ) );
76
77 m_xBtnEdit->connect_clicked( LINK( this, ScSolverOptionsDialog, ButtonHdl ) );
78
79 m_xLbSettings->connect_changed( LINK( this, ScSolverOptionsDialog, SettingsSelHdl ) );
80 m_xLbSettings->connect_row_activated( LINK( this, ScSolverOptionsDialog, SettingsDoubleClickHdl ) );
81
82 sal_Int32 nSelect = -1;
83 sal_Int32 nImplCount = maImplNames.getLength();
84 for (sal_Int32 nImpl=0; nImpl<nImplCount; ++nImpl)
85 {
86 OUString aImplName( maImplNames[nImpl] );
87 OUString aDescription( rDescriptions[nImpl] ); // user-visible descriptions in list box
88 m_xLbEngine->append_text(aDescription);
89 if ( aImplName == maEngine )
90 nSelect = nImpl;
91 }
92 if ( nSelect < 0 ) // no (valid) engine given
93 {
94 if ( nImplCount > 0 )
95 {
96 maEngine = maImplNames[0]; // use first implementation
97 nSelect = 0;
98 }
99 else
100 maEngine.clear();
101 maProperties.realloc(0); // don't use options from different engine
102 }
103 if ( nSelect >= 0 ) // select in list box
104 m_xLbEngine->set_active(nSelect);
105
106 if ( !maProperties.hasElements() )
107 ReadFromComponent(); // fill maProperties from component (using maEngine)
108 FillListBox(); // using maProperties
109 }
110
~ScSolverOptionsDialog()111 ScSolverOptionsDialog::~ScSolverOptionsDialog()
112 {
113 if (m_xIntDialog)
114 m_xIntDialog->response(RET_CANCEL);
115 assert(!m_xIntDialog);
116 if (m_xValDialog)
117 m_xValDialog->response(RET_CANCEL);
118 assert(!m_xValDialog);
119 }
120
GetProperties()121 const uno::Sequence<beans::PropertyValue>& ScSolverOptionsDialog::GetProperties()
122 {
123 // update maProperties from list box content
124 // order of entries in list box and maProperties is the same
125 sal_Int32 nEntryCount = maProperties.getLength();
126 if (nEntryCount == m_xLbSettings->n_children())
127 {
128 for (sal_Int32 nEntryPos=0; nEntryPos<nEntryCount; ++nEntryPos)
129 {
130 uno::Any& rValue = maProperties[nEntryPos].Value;
131 if (ScSolverOptionsString* pStringItem = reinterpret_cast<ScSolverOptionsString*>(m_xLbSettings->get_id(nEntryPos).toInt64()))
132 {
133 if (pStringItem->IsDouble())
134 rValue <<= pStringItem->GetDoubleValue();
135 else
136 rValue <<= pStringItem->GetIntValue();
137 }
138 else
139 rValue <<= m_xLbSettings->get_toggle(nEntryPos) == TRISTATE_TRUE;
140 }
141 }
142 else
143 {
144 OSL_FAIL( "wrong count" );
145 }
146
147 return maProperties;
148 }
149
FillListBox()150 void ScSolverOptionsDialog::FillListBox()
151 {
152 // get property descriptions, sort by them
153
154 uno::Reference<sheet::XSolverDescription> xDesc( ScSolverUtil::GetSolver( maEngine ), uno::UNO_QUERY );
155 sal_Int32 nCount = maProperties.getLength();
156 std::vector<ScSolverOptionsEntry> aDescriptions( nCount );
157 for (sal_Int32 nPos=0; nPos<nCount; nPos++)
158 {
159 OUString aPropName( maProperties[nPos].Name );
160 OUString aVisName;
161 if ( xDesc.is() )
162 aVisName = xDesc->getPropertyDescription( aPropName );
163 if ( aVisName.isEmpty() )
164 aVisName = aPropName;
165 aDescriptions[nPos].nPosition = nPos;
166 aDescriptions[nPos].aDescription = aVisName;
167 }
168 std::sort( aDescriptions.begin(), aDescriptions.end() );
169
170 // also update maProperties to the order of descriptions
171
172 uno::Sequence<beans::PropertyValue> aNewSeq;
173 aNewSeq.realloc( nCount );
174 std::transform(aDescriptions.begin(), aDescriptions.end(), aNewSeq.begin(),
175 [this](const ScSolverOptionsEntry& rDescr) -> beans::PropertyValue { return maProperties[ rDescr.nPosition ]; });
176 maProperties = aNewSeq;
177
178 // fill the list box
179
180 m_xLbSettings->freeze();
181 m_xLbSettings->clear();
182
183 for (sal_Int32 nPos=0; nPos<nCount; nPos++)
184 {
185 OUString aVisName = aDescriptions[nPos].aDescription;
186
187 uno::Any aValue = maProperties[nPos].Value;
188 uno::TypeClass eClass = aValue.getValueTypeClass();
189
190 m_xLbSettings->append();
191
192 if ( eClass == uno::TypeClass_BOOLEAN )
193 {
194 // check box entry
195 m_xLbSettings->set_toggle(nPos, ScUnoHelpFunctions::GetBoolFromAny(aValue) ? TRISTATE_TRUE : TRISTATE_FALSE);
196 m_xLbSettings->set_text(nPos, aVisName, 0);
197 }
198 else
199 {
200 // value entry
201 m_xLbSettings->set_text(nPos, aVisName, 0);
202 m_aOptions.emplace_back(new ScSolverOptionsString(aVisName));
203 if (eClass == uno::TypeClass_DOUBLE)
204 {
205 double fDoubleValue = 0.0;
206 if (aValue >>= fDoubleValue)
207 m_aOptions.back()->SetDoubleValue(fDoubleValue);
208
209 OUString sTxt = aVisName + ": ";
210 sTxt += rtl::math::doubleToUString(fDoubleValue,
211 rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
212 ScGlobal::getLocaleDataPtr()->getNumDecimalSep()[0], true );
213
214 m_xLbSettings->set_text(nPos, sTxt, 0);
215 }
216 else
217 {
218 sal_Int32 nIntValue = 0;
219 if (aValue >>= nIntValue)
220 m_aOptions.back()->SetIntValue(nIntValue);
221
222 OUString sTxt = aVisName + ": " + OUString::number(nIntValue);
223
224 m_xLbSettings->set_text(nPos, sTxt, 0);
225 }
226 m_xLbSettings->set_id(nPos, OUString::number(reinterpret_cast<sal_Int64>(m_aOptions.back().get())));
227 }
228 }
229
230 m_xLbSettings->thaw();
231 }
232
ReadFromComponent()233 void ScSolverOptionsDialog::ReadFromComponent()
234 {
235 maProperties = ScSolverUtil::GetDefaults( maEngine );
236 }
237
EditOption()238 void ScSolverOptionsDialog::EditOption()
239 {
240 int nEntry = m_xLbSettings->get_selected_index();
241 if (nEntry == -1)
242 return;
243 ScSolverOptionsString* pStringItem = reinterpret_cast<ScSolverOptionsString*>(m_xLbSettings->get_id(nEntry).toInt64());
244 if (!pStringItem)
245 return;
246
247 if (pStringItem->IsDouble())
248 {
249 m_xValDialog = std::make_shared<ScSolverValueDialog>(m_xDialog.get());
250 m_xValDialog->SetOptionName(pStringItem->GetText());
251 if (maProperties[nEntry].Name == "DECR")
252 m_xValDialog->SetMax(1.0);
253 else if (maProperties[nEntry].Name == "DEFactorMax")
254 m_xValDialog->SetMax(1.2);
255 else if (maProperties[nEntry].Name == "DEFactorMin")
256 m_xValDialog->SetMax(1.2);
257 else if (maProperties[nEntry].Name == "PSCL")
258 m_xValDialog->SetMax(0.005);
259 m_xValDialog->SetValue(pStringItem->GetDoubleValue());
260 weld::DialogController::runAsync(m_xValDialog, [nEntry, pStringItem, this](sal_Int32 nResult){
261 if (nResult == RET_OK)
262 {
263 pStringItem->SetDoubleValue(m_xValDialog->GetValue());
264
265 OUString sTxt(pStringItem->GetText() + ": ");
266 sTxt += rtl::math::doubleToUString(pStringItem->GetDoubleValue(),
267 rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
268 ScGlobal::getLocaleDataPtr()->getNumDecimalSep()[0], true );
269
270 m_xLbSettings->set_text(nEntry, sTxt, 0);
271 }
272 m_xValDialog.reset();
273 });
274 }
275 else
276 {
277 m_xIntDialog = std::make_shared<ScSolverIntegerDialog>(m_xDialog.get());
278 m_xIntDialog->SetOptionName( pStringItem->GetText() );
279 if (maProperties[nEntry].Name == "EpsilonLevel")
280 m_xIntDialog->SetMax(3);
281 else if (maProperties[nEntry].Name == "Algorithm")
282 m_xIntDialog->SetMax(1);
283 m_xIntDialog->SetValue( pStringItem->GetIntValue() );
284 weld::DialogController::runAsync(m_xIntDialog, [nEntry, pStringItem, this](sal_Int32 nResult){
285 if (nResult == RET_OK)
286 {
287 pStringItem->SetIntValue(m_xIntDialog->GetValue());
288
289 OUString sTxt(pStringItem->GetText() + ": ");
290 sTxt += OUString::number(pStringItem->GetIntValue());
291
292 m_xLbSettings->set_text(nEntry, sTxt, 0);
293 }
294 m_xIntDialog.reset();
295 });
296 }
297 }
298
IMPL_LINK(ScSolverOptionsDialog,ButtonHdl,weld::Button &,rBtn,void)299 IMPL_LINK( ScSolverOptionsDialog, ButtonHdl, weld::Button&, rBtn, void )
300 {
301 if (&rBtn == m_xBtnEdit.get())
302 EditOption();
303 }
304
IMPL_LINK_NOARG(ScSolverOptionsDialog,SettingsDoubleClickHdl,weld::TreeView &,bool)305 IMPL_LINK_NOARG(ScSolverOptionsDialog, SettingsDoubleClickHdl, weld::TreeView&, bool)
306 {
307 EditOption();
308 return true;
309 }
310
IMPL_LINK_NOARG(ScSolverOptionsDialog,EngineSelectHdl,weld::ComboBox &,void)311 IMPL_LINK_NOARG(ScSolverOptionsDialog, EngineSelectHdl, weld::ComboBox&, void)
312 {
313 const sal_Int32 nSelectPos = m_xLbEngine->get_active();
314 if ( nSelectPos < maImplNames.getLength() )
315 {
316 OUString aNewEngine( maImplNames[nSelectPos] );
317 if ( aNewEngine != maEngine )
318 {
319 maEngine = aNewEngine;
320 ReadFromComponent(); // fill maProperties from component (using maEngine)
321 FillListBox(); // using maProperties
322 }
323 }
324 }
325
IMPL_LINK_NOARG(ScSolverOptionsDialog,SettingsSelHdl,weld::TreeView &,void)326 IMPL_LINK_NOARG(ScSolverOptionsDialog, SettingsSelHdl, weld::TreeView&, void)
327 {
328 bool bCheckbox = false;
329
330 int nEntry = m_xLbSettings->get_selected_index();
331 if (nEntry != -1)
332 {
333 ScSolverOptionsString* pStringItem = reinterpret_cast<ScSolverOptionsString*>(m_xLbSettings->get_id(nEntry).toInt64());
334 if (!pStringItem)
335 bCheckbox = true;
336 }
337
338 m_xBtnEdit->set_sensitive(!bCheckbox);
339 }
340
ScSolverIntegerDialog(weld::Window * pParent)341 ScSolverIntegerDialog::ScSolverIntegerDialog(weld::Window * pParent)
342 : GenericDialogController(pParent, "modules/scalc/ui/integerdialog.ui", "IntegerDialog")
343 , m_xFrame(m_xBuilder->weld_frame("frame"))
344 , m_xNfValue(m_xBuilder->weld_spin_button("value"))
345 {
346 }
347
~ScSolverIntegerDialog()348 ScSolverIntegerDialog::~ScSolverIntegerDialog()
349 {
350 }
351
SetOptionName(const OUString & rName)352 void ScSolverIntegerDialog::SetOptionName( const OUString& rName )
353 {
354 m_xFrame->set_label(rName);
355 }
356
SetValue(sal_Int32 nValue)357 void ScSolverIntegerDialog::SetValue( sal_Int32 nValue )
358 {
359 m_xNfValue->set_value( nValue );
360 }
361
SetMax(sal_Int32 nMax)362 void ScSolverIntegerDialog::SetMax( sal_Int32 nMax )
363 {
364 m_xNfValue->set_range(0, nMax);
365 }
366
GetValue() const367 sal_Int32 ScSolverIntegerDialog::GetValue() const
368 {
369 return m_xNfValue->get_value();
370 }
371
ScSolverValueDialog(weld::Window * pParent)372 ScSolverValueDialog::ScSolverValueDialog(weld::Window* pParent)
373 : GenericDialogController(pParent, "modules/scalc/ui/doubledialog.ui", "DoubleDialog")
374 , m_xFrame(m_xBuilder->weld_frame("frame"))
375 , m_xEdValue(m_xBuilder->weld_entry("value"))
376 {
377 ::rtl::math::setNan(&m_fMaxValue);
378 }
379
~ScSolverValueDialog()380 ScSolverValueDialog::~ScSolverValueDialog()
381 {
382 }
383
SetOptionName(const OUString & rName)384 void ScSolverValueDialog::SetOptionName( const OUString& rName )
385 {
386 m_xFrame->set_label(rName);
387 }
388
SetValue(double fValue)389 void ScSolverValueDialog::SetValue( double fValue )
390 {
391 m_xEdValue->set_text( rtl::math::doubleToUString( fValue,
392 rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
393 ScGlobal::getLocaleDataPtr()->getNumDecimalSep()[0], true ) );
394 }
395
SetMax(double fMax)396 void ScSolverValueDialog::SetMax(double fMax)
397 {
398 m_fMaxValue = fMax;
399 }
400
GetValue() const401 double ScSolverValueDialog::GetValue() const
402 {
403 OUString aInput = m_xEdValue->get_text();
404
405 rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
406 sal_Int32 nParseEnd = 0;
407 double fValue = ScGlobal::getLocaleDataPtr()->stringToDouble( aInput, true, &eStatus, &nParseEnd);
408 /* TODO: shouldn't there be some error checking? */
409 if (!std::isnan(m_fMaxValue) && fValue > m_fMaxValue)
410 fValue = m_fMaxValue;
411 return fValue;
412 }
413
414 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
415