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