1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
11 #include <svl/undo.hxx>
12 #include <comphelper/random.hxx>
13 #include <rangelst.hxx>
15 #include <document.hxx>
16 #include <reffact.hxx>
17 #include <docfunc.hxx>
18 #include <SamplingDialog.hxx>
19 #include <scresid.hxx>
20 #include <strings.hrc>
22 ScSamplingDialog::ScSamplingDialog(SfxBindings
* pSfxBindings
, SfxChildWindow
* pChildWindow
,
23 weld::Window
* pParent
, ScViewData
& rViewData
)
24 : ScAnyRefDlgController(pSfxBindings
, pChildWindow
, pParent
,
25 u
"modules/scalc/ui/samplingdialog.ui"_ustr
, u
"SamplingDialog"_ustr
)
26 , mpActiveEdit(nullptr)
27 , mViewData(rViewData
)
28 , mDocument(rViewData
.GetDocument())
29 , mInputRange(ScAddress::INITIALIZE_INVALID
)
30 , mAddressDetails(mDocument
.GetAddressConvention(), 0, 0)
31 , mOutputAddress(ScAddress::INITIALIZE_INVALID
)
32 , mCurrentAddress(rViewData
.GetCurX(), rViewData
.GetCurY(), rViewData
.GetTabNo())
33 , mnLastSampleSizeValue(1)
34 , mnLastPeriodValue(1)
35 , mDialogLostFocus(false)
36 , mxInputRangeLabel(m_xBuilder
->weld_label(u
"input-range-label"_ustr
))
37 , mxInputRangeEdit(new formula::RefEdit(m_xBuilder
->weld_entry(u
"input-range-edit"_ustr
)))
38 , mxInputRangeButton(new formula::RefButton(m_xBuilder
->weld_button(u
"input-range-button"_ustr
)))
39 , mxOutputRangeLabel(m_xBuilder
->weld_label(u
"output-range-label"_ustr
))
40 , mxOutputRangeEdit(new formula::RefEdit(m_xBuilder
->weld_entry(u
"output-range-edit"_ustr
)))
41 , mxOutputRangeButton(new formula::RefButton(m_xBuilder
->weld_button(u
"output-range-button"_ustr
)))
42 , mxSampleSize(m_xBuilder
->weld_spin_button(u
"sample-size-spin"_ustr
))
43 , mxPeriod(m_xBuilder
->weld_spin_button(u
"period-spin"_ustr
))
44 , mxRandomMethodRadio(m_xBuilder
->weld_radio_button(u
"random-method-radio"_ustr
))
45 , mxWithReplacement(m_xBuilder
->weld_check_button(u
"with-replacement"_ustr
))
46 , mxKeepOrder(m_xBuilder
->weld_check_button(u
"keep-order"_ustr
))
47 , mxPeriodicMethodRadio(m_xBuilder
->weld_radio_button(u
"periodic-method-radio"_ustr
))
48 , mxButtonOk(m_xBuilder
->weld_button(u
"ok"_ustr
))
49 , mxButtonCancel(m_xBuilder
->weld_button(u
"cancel"_ustr
))
51 mxInputRangeEdit
->SetReferences(this, mxInputRangeLabel
.get());
52 mxInputRangeButton
->SetReferences(this, mxInputRangeEdit
.get());
54 mxOutputRangeEdit
->SetReferences(this, mxOutputRangeLabel
.get());
55 mxOutputRangeButton
->SetReferences(this, mxOutputRangeEdit
.get());
58 GetRangeFromSelection();
61 ScSamplingDialog::~ScSamplingDialog()
65 void ScSamplingDialog::Init()
67 mxButtonCancel
->connect_clicked( LINK( this, ScSamplingDialog
, ButtonClicked
) );
68 mxButtonOk
->connect_clicked( LINK( this, ScSamplingDialog
, ButtonClicked
) );
69 mxButtonOk
->set_sensitive(false);
71 Link
<formula::RefEdit
&,void> aEditLink
= LINK( this, ScSamplingDialog
, GetEditFocusHandler
);
72 mxInputRangeEdit
->SetGetFocusHdl( aEditLink
);
73 mxOutputRangeEdit
->SetGetFocusHdl( aEditLink
);
74 Link
<formula::RefButton
&,void> aButtonLink
= LINK( this, ScSamplingDialog
, GetButtonFocusHandler
);
75 mxInputRangeButton
->SetGetFocusHdl( aButtonLink
);
76 mxOutputRangeButton
->SetGetFocusHdl( aButtonLink
);
78 aEditLink
= LINK( this, ScSamplingDialog
, LoseEditFocusHandler
);
79 mxInputRangeEdit
->SetLoseFocusHdl( aEditLink
);
80 mxOutputRangeEdit
->SetLoseFocusHdl( aEditLink
);
81 aButtonLink
= LINK( this, ScSamplingDialog
, LoseButtonFocusHandler
);
82 mxInputRangeButton
->SetLoseFocusHdl( aButtonLink
);
83 mxOutputRangeButton
->SetLoseFocusHdl( aButtonLink
);
85 Link
<formula::RefEdit
&,void> aLink2
= LINK( this, ScSamplingDialog
, RefInputModifyHandler
);
86 mxInputRangeEdit
->SetModifyHdl( aLink2
);
87 mxOutputRangeEdit
->SetModifyHdl( aLink2
);
89 mxSampleSize
->connect_value_changed( LINK( this, ScSamplingDialog
, SamplingSizeValueModified
));
90 mxSampleSize
->set_range(1, SAL_MAX_INT32
);
91 mxPeriod
->connect_value_changed( LINK( this, ScSamplingDialog
, PeriodValueModified
));
92 mxPeriod
->set_range(1, SAL_MAX_INT32
);
94 mxPeriodicMethodRadio
->connect_toggled( LINK( this, ScSamplingDialog
, ToggleSamplingMethod
) );
95 mxRandomMethodRadio
->connect_toggled( LINK( this, ScSamplingDialog
, ToggleSamplingMethod
) );
97 mxWithReplacement
->connect_toggled( LINK( this, ScSamplingDialog
, CheckHdl
));
98 mxKeepOrder
->connect_toggled( LINK( this, ScSamplingDialog
, CheckHdl
));
100 mxOutputRangeEdit
->GrabFocus();
101 mxPeriodicMethodRadio
->set_active(true);
103 ToggleSamplingMethod();
106 void ScSamplingDialog::GetRangeFromSelection()
108 mViewData
.GetSimpleArea(mInputRange
);
109 OUString
aCurrentString(mInputRange
.Format(mDocument
, ScRefFlags::RANGE_ABS_3D
, mAddressDetails
));
110 mxInputRangeEdit
->SetText(aCurrentString
);
113 void ScSamplingDialog::SetActive()
115 if ( mDialogLostFocus
)
117 mDialogLostFocus
= false;
119 mpActiveEdit
->GrabFocus();
123 m_xDialog
->grab_focus();
128 void ScSamplingDialog::Close()
130 DoClose( ScSamplingDialogWrapper::GetChildWindowId() );
133 void ScSamplingDialog::SetReference( const ScRange
& rReferenceRange
, ScDocument
& rDocument
)
137 if ( rReferenceRange
.aStart
!= rReferenceRange
.aEnd
)
138 RefInputStart( mpActiveEdit
);
140 OUString aReferenceString
;
142 if ( mpActiveEdit
== mxInputRangeEdit
.get() )
144 mInputRange
= rReferenceRange
;
145 aReferenceString
= mInputRange
.Format(rDocument
, ScRefFlags::RANGE_ABS_3D
, mAddressDetails
);
146 mxInputRangeEdit
->SetRefString( aReferenceString
);
148 LimitSampleSizeAndPeriod();
150 else if ( mpActiveEdit
== mxOutputRangeEdit
.get() )
152 mOutputAddress
= rReferenceRange
.aStart
;
154 ScRefFlags nFormat
= ( mOutputAddress
.Tab() == mCurrentAddress
.Tab() ) ?
155 ScRefFlags::ADDR_ABS
:
156 ScRefFlags::ADDR_ABS_3D
;
157 aReferenceString
= mOutputAddress
.Format(nFormat
, &rDocument
, rDocument
.GetAddressConvention());
158 mxOutputRangeEdit
->SetRefString( aReferenceString
);
160 // Change sampling size according to output range selection
161 sal_Int64 aSelectedSampleSize
= rReferenceRange
.aEnd
.Row() - rReferenceRange
.aStart
.Row() + 1;
162 if (aSelectedSampleSize
> 1)
163 mxSampleSize
->set_value(aSelectedSampleSize
);
164 SamplingSizeValueModified(*mxSampleSize
);
168 // Enable OK if both, input range and output address are set.
169 // Disable if at least one is invalid.
170 mxButtonOk
->set_sensitive(mInputRange
.IsValid() && mOutputAddress
.IsValid());
173 ScRange
ScSamplingDialog::PerformPeriodicSampling(ScDocShell
* pDocShell
)
175 ScAddress aStart
= mInputRange
.aStart
;
176 ScAddress aEnd
= mInputRange
.aEnd
;
178 SCTAB outTab
= mOutputAddress
.Tab();
179 SCROW outRow
= mOutputAddress
.Row();
181 sal_Int64 aPeriod
= mxPeriod
->get_value();
183 for (SCROW inTab
= aStart
.Tab(); inTab
<= aEnd
.Tab(); inTab
++)
185 SCCOL outCol
= mOutputAddress
.Col();
186 for (SCCOL inCol
= aStart
.Col(); inCol
<= aEnd
.Col(); inCol
++)
189 outRow
= mOutputAddress
.Row();
190 for (SCROW inRow
= aStart
.Row(); inRow
<= aEnd
.Row(); inRow
++)
192 assert(aPeriod
&& "div-by-zero");
193 if (i
% aPeriod
== aPeriod
- 1 ) // Sample the last of period
195 double aValue
= mDocument
.GetValue(ScAddress(inCol
, inRow
, inTab
));
196 pDocShell
->GetDocFunc().SetValueCell(ScAddress(outCol
, outRow
, outTab
), aValue
, true);
206 return ScRange(mOutputAddress
, ScAddress(outTab
, outRow
, outTab
) );
209 ScRange
ScSamplingDialog::PerformRandomSampling(ScDocShell
* pDocShell
)
211 ScAddress aStart
= mInputRange
.aStart
;
212 ScAddress aEnd
= mInputRange
.aEnd
;
214 SCTAB outTab
= mOutputAddress
.Tab();
215 SCROW outRow
= mOutputAddress
.Row();
217 const sal_Int64 nSampleSize
= mxSampleSize
->get_value();
219 // This implementation groups by columns. Other options could be grouping
221 const sal_Int64 nPopulationSize
= aEnd
.Row() - aStart
.Row() + 1;
223 const bool bWithReplacement
= mxWithReplacement
->get_sensitive() && mxWithReplacement
->get_active();
225 // WOR (WithOutReplacement) can't draw more than population. Catch that in
227 assert( bWithReplacement
|| nSampleSize
<= nPopulationSize
);
228 if (!bWithReplacement
&& nSampleSize
> nPopulationSize
)
229 // Would enter an endless loop below, bail out.
230 return ScRange( mOutputAddress
);
232 for (SCROW inTab
= aStart
.Tab(); inTab
<= aEnd
.Tab(); inTab
++)
234 SCCOL outCol
= mOutputAddress
.Col();
235 for (SCCOL inCol
= aStart
.Col(); inCol
<= aEnd
.Col(); inCol
++)
237 outRow
= mOutputAddress
.Row();
238 std::vector
<bool> vUsed( nPopulationSize
, false);
240 while ((outRow
- mOutputAddress
.Row()) < nSampleSize
)
242 // [a,b] *both* inclusive
243 SCROW nRandom
= comphelper::rng::uniform_int_distribution( aStart
.Row(), aEnd
.Row());
245 if (!bWithReplacement
)
247 nRandom
-= aStart
.Row();
250 // Find a nearest one, preferring forwards.
251 // Again: it's essential that the loop is entered only
252 // if nSampleSize<=nPopulationSize, which is checked
254 SCROW nBack
= nRandom
;
255 SCROW nForw
= nRandom
;
258 if (nForw
< nPopulationSize
- 1 && !vUsed
[++nForw
])
263 if (nBack
> 0 && !vUsed
[--nBack
])
271 vUsed
[nRandom
] = true;
272 nRandom
+= aStart
.Row();
275 const double fValue
= mDocument
.GetValue( ScAddress(inCol
, nRandom
, inTab
) );
276 pDocShell
->GetDocFunc().SetValueCell(ScAddress(outCol
, outRow
, outTab
), fValue
, true);
284 return ScRange(mOutputAddress
, ScAddress(outTab
, outRow
, outTab
) );
287 ScRange
ScSamplingDialog::PerformRandomSamplingKeepOrder(ScDocShell
* pDocShell
)
289 ScAddress aStart
= mInputRange
.aStart
;
290 ScAddress aEnd
= mInputRange
.aEnd
;
292 SCTAB outTab
= mOutputAddress
.Tab();
293 SCROW outRow
= mOutputAddress
.Row();
297 sal_Int64 aSampleSize
= mxSampleSize
->get_value();
299 for (SCROW inTab
= aStart
.Tab(); inTab
<= aEnd
.Tab(); inTab
++)
301 SCCOL outCol
= mOutputAddress
.Col();
302 for (SCCOL inCol
= aStart
.Col(); inCol
<= aEnd
.Col(); inCol
++)
304 SCROW aPopulationSize
= (aEnd
.Row() - aStart
.Row()) + 1;
306 outRow
= mOutputAddress
.Row();
307 inRow
= aStart
.Row();
309 while ((outRow
- mOutputAddress
.Row()) < aSampleSize
)
311 double aRandomValue
= comphelper::rng::uniform_real_distribution();
313 if ( (aPopulationSize
- (inRow
- aStart
.Row())) * aRandomValue
>= aSampleSize
- (outRow
- mOutputAddress
.Row()) )
319 double aValue
= mDocument
.GetValue( ScAddress(inCol
, inRow
, inTab
) );
320 pDocShell
->GetDocFunc().SetValueCell(ScAddress(outCol
, outRow
, outTab
), aValue
, true);
330 return ScRange(mOutputAddress
, ScAddress(outTab
, outRow
, outTab
) );
333 void ScSamplingDialog::PerformSampling()
335 OUString
aUndo(ScResId(STR_SAMPLING_UNDO_NAME
));
336 ScDocShell
* pDocShell
= mViewData
.GetDocShell();
337 SfxUndoManager
* pUndoManager
= pDocShell
->GetUndoManager();
339 ScRange aModifiedRange
;
341 pUndoManager
->EnterListAction( aUndo
, aUndo
, 0, mViewData
.GetViewShell()->GetViewShellId() );
343 if (mxRandomMethodRadio
->get_active())
345 if (mxKeepOrder
->get_sensitive() && mxKeepOrder
->get_active())
346 aModifiedRange
= PerformRandomSamplingKeepOrder(pDocShell
);
348 aModifiedRange
= PerformRandomSampling(pDocShell
);
350 else if (mxPeriodicMethodRadio
->get_active())
352 aModifiedRange
= PerformPeriodicSampling(pDocShell
);
355 pUndoManager
->LeaveListAction();
356 pDocShell
->PostPaint(aModifiedRange
, PaintPartFlags::Grid
);
359 sal_Int64
ScSamplingDialog::GetPopulationSize() const
361 return mInputRange
.IsValid() ? mInputRange
.aEnd
.Row() - mInputRange
.aStart
.Row() + 1 : 0;
364 void ScSamplingDialog::LimitSampleSizeAndPeriod()
366 // Limit sample size (for WOR methods) and period if population is smaller
367 // than last known value. When enlarging the input population range the
368 // values will be adjusted up to the last known value again.
369 const sal_Int64 nPopulationSize
= GetPopulationSize();
370 if (nPopulationSize
<= mnLastSampleSizeValue
&& !mxWithReplacement
->get_active())
371 mxSampleSize
->set_value( nPopulationSize
);
372 if (nPopulationSize
<= mnLastPeriodValue
)
373 mxPeriod
->set_value( nPopulationSize
);
376 IMPL_LINK_NOARG(ScSamplingDialog
, SamplingSizeValueModified
, weld::SpinButton
&, void)
378 if (!mxWithReplacement
->get_active())
380 // For all WOR methods limit sample size to population size.
381 const sal_Int64 nPopulationSize
= GetPopulationSize();
382 if (mxSampleSize
->get_value() > nPopulationSize
)
383 mxSampleSize
->set_value(nPopulationSize
);
385 mnLastSampleSizeValue
= mxSampleSize
->get_value();
388 IMPL_LINK_NOARG(ScSamplingDialog
, PeriodValueModified
, weld::SpinButton
&, void)
390 // Limit period to population size.
391 const sal_Int64 nPopulationSize
= GetPopulationSize();
392 if (mxPeriod
->get_value() > nPopulationSize
)
393 mxPeriod
->set_value(nPopulationSize
);
394 mnLastPeriodValue
= mxPeriod
->get_value();
397 IMPL_LINK( ScSamplingDialog
, GetEditFocusHandler
, formula::RefEdit
&, rCtrl
, void )
399 if (&rCtrl
== mxInputRangeEdit
.get())
400 mpActiveEdit
= mxInputRangeEdit
.get();
401 else if (&rCtrl
== mxOutputRangeEdit
.get())
402 mpActiveEdit
= mxOutputRangeEdit
.get();
404 mpActiveEdit
= nullptr;
407 mpActiveEdit
->SelectAll();
410 IMPL_LINK(ScSamplingDialog
, GetButtonFocusHandler
, formula::RefButton
&, rCtrl
, void)
412 if (&rCtrl
== mxInputRangeButton
.get())
413 mpActiveEdit
= mxInputRangeEdit
.get();
414 else if (&rCtrl
== mxOutputRangeButton
.get())
415 mpActiveEdit
= mxOutputRangeEdit
.get();
417 mpActiveEdit
= nullptr;
420 mpActiveEdit
->SelectAll();
424 IMPL_LINK(ScSamplingDialog
, ButtonClicked
, weld::Button
&, rButton
, void)
426 if (&rButton
== mxButtonOk
.get())
432 response(RET_CANCEL
);
435 IMPL_LINK_NOARG(ScSamplingDialog
, LoseEditFocusHandler
, formula::RefEdit
&, void)
437 mDialogLostFocus
= !m_xDialog
->has_toplevel_focus();
440 IMPL_LINK_NOARG(ScSamplingDialog
, LoseButtonFocusHandler
, formula::RefButton
&, void)
442 mDialogLostFocus
= !m_xDialog
->has_toplevel_focus();
445 IMPL_LINK_NOARG(ScSamplingDialog
, ToggleSamplingMethod
, weld::Toggleable
&, void)
447 ToggleSamplingMethod();
450 void ScSamplingDialog::ToggleSamplingMethod()
452 if (mxRandomMethodRadio
->get_active())
454 mxPeriod
->set_sensitive(false);
455 mxSampleSize
->set_sensitive(true);
456 mxWithReplacement
->set_sensitive(true);
457 mxKeepOrder
->set_sensitive(true);
459 else if (mxPeriodicMethodRadio
->get_active())
461 // WOR keeping order.
462 mxPeriod
->set_sensitive(true);
463 mxSampleSize
->set_sensitive(false);
464 mxWithReplacement
->set_active(false);
465 mxWithReplacement
->set_sensitive(false);
466 mxKeepOrder
->set_active(true);
467 mxKeepOrder
->set_sensitive(false);
471 IMPL_LINK(ScSamplingDialog
, CheckHdl
, weld::Toggleable
&, rBtn
, void)
473 // Keep both checkboxes enabled so user can easily switch between the three
474 // possible combinations (one or the other or none), just uncheck the other
475 // one if one is checked. Otherwise the other checkbox would had to be
476 // disabled until user unchecks the enabled one again, which would force
477 // user to two clicks to switch.
478 if (&rBtn
== mxWithReplacement
.get())
480 if (mxWithReplacement
->get_active())
482 // For WR can't keep order.
483 mxKeepOrder
->set_active(false);
487 // For WOR limit sample size to population size.
488 SamplingSizeValueModified(*mxSampleSize
);
491 else if (&rBtn
== mxKeepOrder
.get())
493 if (mxKeepOrder
->get_active())
495 // Keep order is always WOR.
496 mxWithReplacement
->set_active(false);
497 SamplingSizeValueModified(*mxSampleSize
);
502 IMPL_LINK_NOARG(ScSamplingDialog
, RefInputModifyHandler
, formula::RefEdit
&, void)
506 if ( mpActiveEdit
== mxInputRangeEdit
.get() )
508 ScRangeList aRangeList
;
509 bool bValid
= ParseWithNames( aRangeList
, mxInputRangeEdit
->GetText(), mDocument
);
510 const ScRange
* pRange
= (bValid
&& aRangeList
.size() == 1) ? &aRangeList
[0] : nullptr;
513 mInputRange
= *pRange
;
514 // Highlight the resulting range.
515 mxInputRangeEdit
->StartUpdateData();
517 LimitSampleSizeAndPeriod();
521 mInputRange
= ScRange( ScAddress::INITIALIZE_INVALID
);
524 else if ( mpActiveEdit
== mxOutputRangeEdit
.get() )
526 ScRangeList aRangeList
;
527 bool bValid
= ParseWithNames( aRangeList
, mxOutputRangeEdit
->GetText(), mDocument
);
528 const ScRange
* pRange
= (bValid
&& aRangeList
.size() == 1) ? &aRangeList
[0] : nullptr;
531 mOutputAddress
= pRange
->aStart
;
533 // Crop output range to top left address for Edit field.
534 if (pRange
->aStart
!= pRange
->aEnd
)
536 ScRefFlags nFormat
= ( mOutputAddress
.Tab() == mCurrentAddress
.Tab() ) ?
537 ScRefFlags::ADDR_ABS
:
538 ScRefFlags::ADDR_ABS_3D
;
539 OUString aReferenceString
= mOutputAddress
.Format(nFormat
, &mDocument
, mDocument
.GetAddressConvention());
540 mxOutputRangeEdit
->SetRefString( aReferenceString
);
543 // Change sampling size according to output range selection
544 sal_Int64 aSelectedSampleSize
= pRange
->aEnd
.Row() - pRange
->aStart
.Row() + 1;
545 if (aSelectedSampleSize
> 1)
546 mxSampleSize
->set_value(aSelectedSampleSize
);
547 SamplingSizeValueModified(*mxSampleSize
);
549 // Highlight the resulting range.
550 mxOutputRangeEdit
->StartUpdateData();
554 mOutputAddress
= ScAddress( ScAddress::INITIALIZE_INVALID
);
559 // Enable OK if both, input range and output address are set.
560 mxButtonOk
->set_sensitive(mInputRange
.IsValid() && mOutputAddress
.IsValid());
563 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */