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