1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  */
10 
11 #include <svl/undo.hxx>
12 #include <rtl/math.hxx>
13 #include <osl/time.h>
14 
15 #include <rangelst.hxx>
16 #include <docsh.hxx>
17 #include <document.hxx>
18 #include <reffact.hxx>
19 #include <docfunc.hxx>
20 #include <scresid.hxx>
21 #include <strings.hrc>
22 
23 #include <random>
24 
25 #include <RandomNumberGeneratorDialog.hxx>
26 
27 namespace
28 {
29 
30 const sal_Int64 DIST_UNIFORM             = 0;
31 const sal_Int64 DIST_NORMAL              = 1;
32 const sal_Int64 DIST_CAUCHY              = 2;
33 const sal_Int64 DIST_BERNOULLI           = 3;
34 const sal_Int64 DIST_BINOMIAL            = 4;
35 const sal_Int64 DIST_CHI_SQUARED         = 5;
36 const sal_Int64 DIST_GEOMETRIC           = 6;
37 const sal_Int64 DIST_NEGATIVE_BINOMIAL   = 7;
38 const sal_Int64 DIST_UNIFORM_INTEGER     = 8;
39 const sal_Int64 DIST_POISSON             = 9;
40 
41 const sal_Int64 PRECISION   = 10000;
42 const sal_Int64 DIGITS      = 4;
43 
44 }
45 
ScRandomNumberGeneratorDialog(SfxBindings * pSfxBindings,SfxChildWindow * pChildWindow,weld::Window * pParent,ScViewData & rViewData)46 ScRandomNumberGeneratorDialog::ScRandomNumberGeneratorDialog(
47                     SfxBindings* pSfxBindings, SfxChildWindow* pChildWindow,
48                     weld::Window* pParent, ScViewData& rViewData)
49     : ScAnyRefDlgController(pSfxBindings, pChildWindow, pParent,
50                           u"modules/scalc/ui/randomnumbergenerator.ui"_ustr,
51                           u"RandomNumberGeneratorDialog"_ustr)
52     , mrViewData(rViewData)
53     , mrDoc(rViewData.GetDocument())
54     , mbDialogLostFocus(false)
55     , mxInputRangeText(m_xBuilder->weld_label(u"cell-range-label"_ustr))
56     , mxInputRangeEdit(new formula::RefEdit(m_xBuilder->weld_entry(u"cell-range-edit"_ustr)))
57     , mxInputRangeButton(new formula::RefButton(m_xBuilder->weld_button(u"cell-range-button"_ustr)))
58     , mxDistributionCombo(m_xBuilder->weld_combo_box(u"distribution-combo"_ustr))
59     , mxParameter1Text(m_xBuilder->weld_label(u"parameter1-label"_ustr))
60     , mxParameter1Value(m_xBuilder->weld_spin_button(u"parameter1-spin"_ustr))
61     , mxParameter2Text(m_xBuilder->weld_label(u"parameter2-label"_ustr))
62     , mxParameter2Value(m_xBuilder->weld_spin_button(u"parameter2-spin"_ustr))
63     , mxSeed(m_xBuilder->weld_spin_button(u"seed-spin"_ustr))
64     , mxEnableSeed(m_xBuilder->weld_check_button(u"enable-seed-check"_ustr))
65     , mxDecimalPlaces(m_xBuilder->weld_spin_button(u"decimal-places-spin"_ustr))
66     , mxEnableRounding(m_xBuilder->weld_check_button(u"enable-rounding-check"_ustr))
67     , mxButtonApply(m_xBuilder->weld_button(u"apply"_ustr))
68     , mxButtonOk(m_xBuilder->weld_button(u"ok"_ustr))
69     , mxButtonClose(m_xBuilder->weld_button(u"close"_ustr))
70 {
71     mxInputRangeEdit->SetReferences(this, mxInputRangeText.get());
72     mxInputRangeButton->SetReferences(this, mxInputRangeEdit.get());
73 
74     Init();
75     GetRangeFromSelection();
76 }
77 
~ScRandomNumberGeneratorDialog()78 ScRandomNumberGeneratorDialog::~ScRandomNumberGeneratorDialog()
79 {
80 }
81 
Init()82 void ScRandomNumberGeneratorDialog::Init()
83 {
84     mxButtonOk->connect_clicked( LINK( this, ScRandomNumberGeneratorDialog, OkClicked ) );
85     mxButtonClose->connect_clicked( LINK( this, ScRandomNumberGeneratorDialog, CloseClicked ) );
86     mxButtonApply->connect_clicked( LINK( this, ScRandomNumberGeneratorDialog, ApplyClicked ) );
87 
88     mxInputRangeEdit->SetGetFocusHdl(LINK( this, ScRandomNumberGeneratorDialog, GetEditFocusHandler ));
89     mxInputRangeButton->SetGetFocusHdl(LINK( this, ScRandomNumberGeneratorDialog, GetButtonFocusHandler ));
90 
91     mxInputRangeEdit->SetLoseFocusHdl (LINK( this, ScRandomNumberGeneratorDialog, LoseEditFocusHandler ));
92     mxInputRangeButton->SetLoseFocusHdl (LINK( this, ScRandomNumberGeneratorDialog, LoseButtonFocusHandler ));
93 
94     mxInputRangeEdit->SetModifyHdl( LINK( this, ScRandomNumberGeneratorDialog, InputRangeModified ));
95     mxParameter1Value->connect_value_changed( LINK( this, ScRandomNumberGeneratorDialog, Parameter1ValueModified ));
96     mxParameter2Value->connect_value_changed( LINK( this, ScRandomNumberGeneratorDialog, Parameter2ValueModified ));
97 
98     mxDistributionCombo->connect_changed( LINK( this, ScRandomNumberGeneratorDialog, DistributionChanged ));
99 
100     mxEnableSeed->connect_toggled( LINK( this, ScRandomNumberGeneratorDialog, CheckChanged ));
101     mxEnableRounding->connect_toggled( LINK( this, ScRandomNumberGeneratorDialog, CheckChanged ));
102 
103     DistributionChanged(*mxDistributionCombo);
104     CheckChanged(*mxEnableSeed);
105 }
106 
GetRangeFromSelection()107 void ScRandomNumberGeneratorDialog::GetRangeFromSelection()
108 {
109     mrViewData.GetSimpleArea(maInputRange);
110     OUString aCurrentString(maInputRange.Format(mrDoc, ScRefFlags::RANGE_ABS_3D, mrDoc.GetAddressConvention()));
111     mxInputRangeEdit->SetText( aCurrentString );
112 }
113 
SetActive()114 void ScRandomNumberGeneratorDialog::SetActive()
115 {
116     if ( mbDialogLostFocus )
117     {
118         mbDialogLostFocus = false;
119         if( mxInputRangeEdit )
120             mxInputRangeEdit->GrabFocus();
121     }
122     else
123     {
124         m_xDialog->grab_focus();
125     }
126     RefInputDone();
127 }
128 
Close()129 void ScRandomNumberGeneratorDialog::Close()
130 {
131     DoClose( ScRandomNumberGeneratorDialogWrapper::GetChildWindowId() );
132 }
133 
SetReference(const ScRange & rReferenceRange,ScDocument & rDoc)134 void ScRandomNumberGeneratorDialog::SetReference( const ScRange& rReferenceRange, ScDocument& rDoc )
135 {
136     if (!mxInputRangeEdit->GetWidget()->get_sensitive())
137         return;
138 
139     if ( rReferenceRange.aStart != rReferenceRange.aEnd )
140         RefInputStart(mxInputRangeEdit.get());
141 
142     maInputRange = rReferenceRange;
143 
144     OUString aReferenceString(maInputRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention()));
145     mxInputRangeEdit->SetRefString( aReferenceString );
146 
147     mxButtonApply->set_sensitive(true);
148     mxButtonOk->set_sensitive(true);
149 }
150 
SelectGeneratorAndGenerateNumbers()151 void ScRandomNumberGeneratorDialog::SelectGeneratorAndGenerateNumbers()
152 {
153     if (!maInputRange.IsValid())
154         return;
155 
156     sal_Int64 aSelectedId = mxDistributionCombo->get_active_id().toInt64();
157 
158     sal_uInt32 seedValue;
159 
160     if( mxEnableSeed->get_active() )
161     {
162         seedValue = mxSeed->get_value();
163     }
164     else
165     {
166         TimeValue now;
167         osl_getSystemTime(&now);
168         seedValue = now.Nanosec;
169     }
170 
171     std::mt19937 seed(seedValue);
172 
173     sal_Int64 parameterInteger1 = mxParameter1Value->get_value();
174     sal_Int64 parameterInteger2 = mxParameter2Value->get_value();
175 
176     double parameter1 = parameterInteger1 / static_cast<double>(PRECISION);
177     double parameter2 = parameterInteger2 / static_cast<double>(PRECISION);
178 
179     std::optional<sal_Int8> aDecimalPlaces;
180     if (mxEnableRounding->get_active())
181     {
182         aDecimalPlaces = static_cast<sal_Int8>(mxDecimalPlaces->get_value());
183     }
184 
185     switch(aSelectedId)
186     {
187         case DIST_UNIFORM:
188         {
189             std::uniform_real_distribution<> distribution(parameter1, parameter2);
190             auto rng = std::bind(distribution, seed);
191             GenerateNumbers(rng, STR_DISTRIBUTION_UNIFORM_REAL, aDecimalPlaces);
192             break;
193         }
194         case DIST_UNIFORM_INTEGER:
195         {
196             std::uniform_int_distribution<sal_Int64> distribution(parameterInteger1, parameterInteger2);
197             auto rng = std::bind(distribution, seed);
198             GenerateNumbers(rng, STR_DISTRIBUTION_UNIFORM_INTEGER, aDecimalPlaces);
199             break;
200         }
201         case DIST_NORMAL:
202         {
203             std::normal_distribution<> distribution(parameter1, parameter2);
204             auto rng = std::bind(distribution, seed);
205             GenerateNumbers(rng, STR_DISTRIBUTION_NORMAL, aDecimalPlaces);
206             break;
207         }
208         case DIST_CAUCHY:
209         {
210             std::cauchy_distribution<> distribution(parameter1);
211             auto rng = std::bind(distribution, seed);
212             GenerateNumbers(rng, STR_DISTRIBUTION_CAUCHY, aDecimalPlaces);
213             break;
214         }
215         case DIST_BERNOULLI:
216         {
217             std::bernoulli_distribution distribution(parameter1);
218             auto rng = std::bind(distribution, seed);
219             GenerateNumbers(rng, STR_DISTRIBUTION_BERNOULLI, aDecimalPlaces);
220             break;
221         }
222         case DIST_BINOMIAL:
223         {
224             std::binomial_distribution<> distribution(parameterInteger2, parameter1);
225             auto rng = std::bind(distribution, seed);
226             GenerateNumbers(rng, STR_DISTRIBUTION_BINOMIAL, aDecimalPlaces);
227             break;
228         }
229         case DIST_CHI_SQUARED:
230         {
231             std::chi_squared_distribution<> distribution(parameter1);
232             auto rng = std::bind(distribution, seed);
233             GenerateNumbers(rng, STR_DISTRIBUTION_CHI_SQUARED, aDecimalPlaces);
234             break;
235         }
236         case DIST_GEOMETRIC:
237         {
238             std::geometric_distribution<> distribution(parameter1);
239             auto rng = std::bind(distribution, seed);
240             GenerateNumbers(rng, STR_DISTRIBUTION_GEOMETRIC, aDecimalPlaces);
241             break;
242         }
243         case DIST_NEGATIVE_BINOMIAL:
244         {
245             std::negative_binomial_distribution<> distribution(parameterInteger2, parameter1);
246             auto rng = std::bind(distribution, seed);
247             GenerateNumbers(rng, STR_DISTRIBUTION_NEGATIVE_BINOMIAL, aDecimalPlaces);
248             break;
249         }
250         case DIST_POISSON:
251         {
252             std::poisson_distribution<> distribution(parameter1);
253             auto rng = std::bind(distribution, seed);
254             GenerateNumbers(rng, STR_DISTRIBUTION_POISSON, aDecimalPlaces);
255             break;
256         }
257     }
258 }
259 
260 template<class RNG>
GenerateNumbers(RNG & randomGenerator,TranslateId pDistributionStringId,std::optional<sal_Int8> aDecimalPlaces)261 void ScRandomNumberGeneratorDialog::GenerateNumbers(RNG& randomGenerator, TranslateId pDistributionStringId, std::optional<sal_Int8> aDecimalPlaces)
262 {
263     OUString aUndo = ScResId(STR_UNDO_DISTRIBUTION_TEMPLATE);
264     OUString aDistributionName = ScResId(pDistributionStringId);
265     aUndo = aUndo.replaceAll("%1",  aDistributionName);
266 
267     ScDocShell* pDocShell = mrViewData.GetDocShell();
268     SfxUndoManager* pUndoManager = pDocShell->GetUndoManager();
269     pUndoManager->EnterListAction( aUndo, aUndo, 0, mrViewData.GetViewShell()->GetViewShellId() );
270 
271     SCROW nRowStart = maInputRange.aStart.Row();
272     SCROW nRowEnd   = maInputRange.aEnd.Row();
273     SCCOL nColStart = maInputRange.aStart.Col();
274     SCCOL nColEnd   = maInputRange.aEnd.Col();
275     SCTAB nTabStart = maInputRange.aStart.Tab();
276     SCTAB nTabEnd   = maInputRange.aEnd.Tab();
277 
278     std::vector<double> aVals;
279     aVals.reserve(nRowEnd - nRowStart + 1);
280 
281     for (SCROW nTab = nTabStart; nTab <= nTabEnd; ++nTab)
282     {
283         for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
284         {
285             aVals.clear();
286 
287             ScAddress aPos(nCol, nRowStart, nTab);
288             for (SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow)
289             {
290 
291                 if (aDecimalPlaces)
292                     aVals.push_back(rtl::math::round(randomGenerator(), *aDecimalPlaces));
293                 else
294                     aVals.push_back(randomGenerator());
295             }
296 
297             pDocShell->GetDocFunc().SetValueCells(aPos, aVals, true);
298         }
299     }
300 
301     pUndoManager->LeaveListAction();
302 
303     pDocShell->PostPaint( maInputRange, PaintPartFlags::Grid );
304 }
305 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,OkClicked,weld::Button &,void)306 IMPL_LINK_NOARG( ScRandomNumberGeneratorDialog, OkClicked, weld::Button&, void )
307 {
308     ApplyClicked(*mxButtonApply);
309     CloseClicked(*mxButtonClose);
310 }
311 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,ApplyClicked,weld::Button &,void)312 IMPL_LINK_NOARG( ScRandomNumberGeneratorDialog, ApplyClicked, weld::Button&, void )
313 {
314     SelectGeneratorAndGenerateNumbers();
315 }
316 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,CloseClicked,weld::Button &,void)317 IMPL_LINK_NOARG( ScRandomNumberGeneratorDialog, CloseClicked, weld::Button&, void )
318 {
319     response(RET_CLOSE);
320 }
321 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,GetEditFocusHandler,formula::RefEdit &,void)322 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, GetEditFocusHandler, formula::RefEdit&, void)
323 {
324     mxInputRangeEdit->SelectAll();
325 }
326 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,GetButtonFocusHandler,formula::RefButton &,void)327 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, GetButtonFocusHandler, formula::RefButton&, void)
328 {
329     mxInputRangeEdit->SelectAll();
330 }
331 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,LoseEditFocusHandler,formula::RefEdit &,void)332 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, LoseEditFocusHandler, formula::RefEdit&, void)
333 {
334     mbDialogLostFocus = !m_xDialog->has_toplevel_focus();
335 }
336 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,LoseButtonFocusHandler,formula::RefButton &,void)337 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, LoseButtonFocusHandler, formula::RefButton&, void)
338 {
339     mbDialogLostFocus = !m_xDialog->has_toplevel_focus();
340 }
341 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,InputRangeModified,formula::RefEdit &,void)342 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, InputRangeModified, formula::RefEdit&, void)
343 {
344     ScRangeList aRangeList;
345     bool bValid = ParseWithNames( aRangeList, mxInputRangeEdit->GetText(), mrDoc);
346     const ScRange* pRange = (bValid && aRangeList.size() == 1) ? &aRangeList[0] : nullptr;
347     if (pRange)
348     {
349         maInputRange = *pRange;
350         mxButtonApply->set_sensitive(true);
351         mxButtonOk->set_sensitive(true);
352         // Highlight the resulting range.
353         mxInputRangeEdit->StartUpdateData();
354     }
355     else
356     {
357         maInputRange = ScRange( ScAddress::INITIALIZE_INVALID);
358         mxButtonApply->set_sensitive(false);
359         mxButtonOk->set_sensitive(false);
360     }
361 }
362 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,Parameter1ValueModified,weld::SpinButton &,void)363 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, Parameter1ValueModified, weld::SpinButton&, void)
364 {
365     sal_Int64 aSelectedId = mxDistributionCombo->get_active_id().toInt64();
366     if (aSelectedId == DIST_UNIFORM ||
367         aSelectedId == DIST_UNIFORM_INTEGER)
368     {
369         sal_Int64 min = mxParameter1Value->get_value();
370         sal_Int64 max = mxParameter2Value->get_value();
371         if(min > max)
372         {
373             mxParameter2Value->set_value(min);
374         }
375     }
376 }
377 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,Parameter2ValueModified,weld::SpinButton &,void)378 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, Parameter2ValueModified, weld::SpinButton&, void)
379 {
380     sal_Int64 aSelectedId = mxDistributionCombo->get_active_id().toInt64();
381     if (aSelectedId == DIST_UNIFORM ||
382         aSelectedId == DIST_UNIFORM_INTEGER)
383     {
384         sal_Int64 min = mxParameter1Value->get_value();
385         sal_Int64 max = mxParameter2Value->get_value();
386         if(min > max)
387         {
388             mxParameter1Value->set_value(max);
389         }
390     }
391 }
392 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,CheckChanged,weld::Toggleable &,void)393 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, CheckChanged, weld::Toggleable&, void)
394 {
395     mxSeed->set_sensitive(mxEnableSeed->get_active());
396     mxDecimalPlaces->set_sensitive(mxEnableRounding->get_active());
397 }
398 
IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog,DistributionChanged,weld::ComboBox &,void)399 IMPL_LINK_NOARG(ScRandomNumberGeneratorDialog, DistributionChanged, weld::ComboBox&, void)
400 {
401     sal_Int64 aSelectedId = mxDistributionCombo->get_active_id().toInt64();
402 
403     mxParameter1Value->set_range(SAL_MIN_INT32, SAL_MAX_INT32);
404     mxParameter2Value->set_range(SAL_MIN_INT32, SAL_MAX_INT32);
405 
406     mxParameter1Value->set_digits(DIGITS);
407     mxParameter1Value->set_increments(PRECISION, PRECISION * 10);
408 
409     mxParameter2Value->set_digits(DIGITS);
410     mxParameter2Value->set_increments(PRECISION, PRECISION * 10);
411 
412     switch(aSelectedId)
413     {
414         case DIST_UNIFORM:
415         {
416             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_MINIMUM));
417             mxParameter2Text->set_label(ScResId(STR_RNG_PARAMETER_MAXIMUM));
418             mxParameter2Text->show();
419             mxParameter2Value->show();
420             break;
421         }
422         case DIST_UNIFORM_INTEGER:
423         {
424             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_MINIMUM));
425             mxParameter1Value->set_digits(0);
426             mxParameter1Value->set_increments(1, 10);
427 
428             mxParameter2Text->set_label(ScResId(STR_RNG_PARAMETER_MAXIMUM));
429             mxParameter2Value->set_digits(0);
430             mxParameter2Value->set_increments(1, 10);
431 
432             mxParameter2Text->show();
433             mxParameter2Value->show();
434             break;
435         }
436         case DIST_NORMAL:
437         {
438             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_MEAN));
439             mxParameter2Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_DEVIATION));
440             mxParameter2Text->show();
441             mxParameter2Value->show();
442             break;
443         }
444         case DIST_CAUCHY:
445         {
446             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_MEDIAN));
447             mxParameter2Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_SIGMA));
448             mxParameter2Text->show();
449             mxParameter2Value->show();
450             break;
451         }
452         case DIST_BERNOULLI:
453         case DIST_GEOMETRIC:
454         {
455             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_PROBABILITY));
456             mxParameter1Value->set_range(0, PRECISION);
457             mxParameter1Value->set_increments(1000, 10000);
458 
459             mxParameter2Text->hide();
460             mxParameter2Value->hide();
461             break;
462         }
463         case DIST_BINOMIAL:
464         case DIST_NEGATIVE_BINOMIAL:
465         {
466             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_PROBABILITY));
467             mxParameter1Value->set_range(0, PRECISION);
468             mxParameter1Value->set_increments(1000, 10000);
469 
470             mxParameter2Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_NUMBER_OF_TRIALS));
471             mxParameter2Value->set_digits(0);
472             mxParameter2Value->set_increments(1, 10);
473             mxParameter2Value->set_min(0);
474 
475             mxParameter2Text->show();
476             mxParameter2Value->show();
477             break;
478         }
479         case DIST_CHI_SQUARED:
480         {
481             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_STANDARD_NU_VALUE));
482 
483             mxParameter2Text->hide();
484             mxParameter2Value->hide();
485             break;
486         }
487         case DIST_POISSON:
488         {
489             mxParameter1Text->set_label(ScResId(STR_RNG_PARAMETER_MEAN));
490             mxParameter1Value->set_value(PRECISION);
491             mxParameter1Value->set_increments(1000, 10000);
492             mxParameter1Value->set_min(1000);
493             mxParameter2Text->hide();
494             mxParameter2Value->hide();
495             break;
496         }
497     }
498 }
499 
500 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
501