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