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
