Audacity 3.2.0
SpinControl.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/**********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 @file SpinControl.cpp
7
8 Dmitry Vedenko
9
10 **********************************************************************/
11#include "SpinControl.h"
12
13#include <algorithm>
14
15#include <wx/textctrl.h>
16#include <wx/sizer.h>
17#include <wx/valnum.h>
18// For wxEVT_SPINCTRL
19#include <wx/spinctrl.h>
20
21#ifndef __WXGTK__
22#include <wx/spinbutt.h>
23#else
24#include <wx/button.h>
25#endif
26
27#if wxUSE_ACCESSIBILITY
28#include "WindowAccessible.h"
29
30class SpinControl::SpinControlAx final
31 : public WindowAccessible
32{
33public:
34 explicit SpinControlAx(SpinControl* owner, wxWindow* control)
35 : WindowAccessible { control }
36 , mOwner { owner }
37 {
38 }
39
40 wxAccStatus GetName(int childId, wxString* name) override
41 {
42 if (childId != wxACC_SELF)
43 return wxACC_NOT_IMPLEMENTED;
44
45 *name = mName.StrippedTranslation();
46
47 return wxACC_OK;
48 }
49
51 {
52 mName = std::move(name);
53 }
54
55
56private:
57 SpinControl* mOwner;
59};
60#endif
61
62
64 wxWindow* parent, wxWindowID winid /*= wxID_ANY*/, double value /*= 0.0*/,
65 double min /*= 0.0f*/, double max /*= 100.0*/, double step /*= 1.0*/,
66 bool allowFractional /*= false*/, const wxPoint& pos /*= wxDefaultPosition*/,
67 const wxSize& size /*= wxDefaultSize*/,
68 const TranslatableString& name /*= {}*/)
69 : wxControl(parent, winid, pos, size, wxBORDER_NONE)
70 , mStep(step)
71 , mFractionalAllowed(allowFractional)
72{
73 CreateUI();
74
75 // Call setter explicitly to ensure that all the clamping happens correctly
77 SetMaxValue(max);
78 SetValue(value);
79
81
83}
84
85void SpinControl::SetValue(double value)
86{
87 SetValue(value, true);
88}
89
91{
92 return mValue;
93}
94
95void SpinControl::SetMinValue(double value)
96{
100}
101
103{
104 return mMinValue;
105}
106
107void SpinControl::SetMaxValue(double value)
108{
109 mMaxValue = std::max(value, mMinValue);
112}
113
115{
116 return mMaxValue;
117}
118
119void SpinControl::SetStep(double step)
120{
121 mStep = step;
123}
124
126{
127 return mStep;
128}
129
131{
132 if (mFractionalAllowed == allow)
133 return;
134
135 mFractionalAllowed = allow;
137}
138
140{
141 return mFractionalAllowed;
142}
143
145{
146#if wxUSE_ACCESSIBILITY
147 if (mWindowAccessible == nullptr)
148 {
149 mWindowAccessible = safenew SpinControlAx(this, mTextControl);
150 mTextControl->SetAccessible(mWindowAccessible);
151 }
152
153 mWindowAccessible->SetName(name);
154#endif
155}
156
158{
159}
160
162{
163 mTextControl = safenew wxTextCtrl(this, wxID_ANY);
164#ifndef __WXGTK__
165 const auto editorHeight = mTextControl->GetSize().y;
166#else
167 // GTK requires the buttons to be at least 16x16
168 // TODO: rely on GTK_ICON_SIZE_BUTTON instead of hardcoding the size
169 constexpr auto minGtkSize = 16;
170 const auto editorHeight = std::max(minGtkSize * 2, mTextControl->GetSize().y);
171#endif
172
173 auto boxSizer = safenew wxBoxSizer(wxHORIZONTAL);
174
175 boxSizer->Add(mTextControl, wxSizerFlags().Border(wxALL, 0));
176
177#ifndef __WXGTK__
178 mSpinButton = safenew wxSpinButton(this);
179 mSpinButton->SetMaxSize({ -1, editorHeight });
180
181 // SpinButton is only used to generate the events,
182 // so keep the value between min (0 by default) and max (100 by default)
183 mSpinButton->SetValue(50);
184
185 boxSizer->Add(mSpinButton, wxSizerFlags().Border(wxALL, 0));
186#else
187 auto buttonsSizer = safenew wxBoxSizer(wxVERTICAL);
188
189 const auto buttonSize = wxSize { editorHeight / 2, editorHeight / 2 };
190
191 mUpButton = safenew wxButton(this, wxID_ANY, L"+", wxDefaultPosition, buttonSize);
192 mUpButton->SetMinSize(buttonSize);
193 mUpButton->SetMaxSize(buttonSize);
194 buttonsSizer->Add(mUpButton, wxSizerFlags().Border(wxALL, 0));
195
196 mDownButton = safenew wxButton(this, wxID_ANY, L"-", wxDefaultPosition, buttonSize);
197 mDownButton->SetMinSize(buttonSize);
198 mDownButton->SetMaxSize(buttonSize);
199 buttonsSizer->Add(mDownButton, wxSizerFlags().Border(wxALL, 0));
200
201 boxSizer->Add(buttonsSizer, wxSizerFlags().Border(wxALL, 0));
202#endif
203
204 const auto width = GetSize().x;
205
206 if (width > 0)
207 {
208#ifndef __WXGTK__
209 auto spinWidth = mSpinButton->GetSize().x;
210 const auto editorWidth = std::max(10, width - spinWidth);
211 mTextControl->SetMaxSize({ editorWidth, editorHeight });
212#else
213 const auto editorWidth = std::max(10, width - editorHeight / 2);
214
215 mTextControl->SetMinSize({ editorWidth, editorHeight });
216 mTextControl->SetMaxSize({ editorWidth, editorHeight });
217 mTextControl->SetSize({ editorWidth, editorHeight });
218#endif
219 }
220
221 SetSizerAndFit(boxSizer);
222 Layout();
223
224 Bind(
225 wxEVT_SET_FOCUS,
226 [this](auto& evt)
227 {
228 mTextControl->SetFocus();
229 evt.Skip();
230 });
231
232 Bind(wxEVT_CHAR_HOOK, &SpinControl::OnCharHook, this);
233
234 mTextControl->Bind(
235 wxEVT_KILL_FOCUS,
236 [this](auto& evt)
237 {
239 evt.Skip();
240 });
241
242 mTextControl->Bind(
243 wxEVT_MOUSEWHEEL,
244 [this](auto& evt)
245 {
246 const auto delta = evt.GetWheelDelta();
247 const auto rotation = evt.GetWheelRotation();
248
249 if (rotation >= delta)
250 DoSteps(evt.ShiftDown() ? 10 : 1);
251 else if (rotation <= delta)
252 DoSteps(evt.ShiftDown() ? -10 : -1);
253 });
254
255#ifndef __WXGTK__
256 mSpinButton->Bind(
257 wxEVT_SPIN_UP,
258 [this](auto& evt)
259 {
260 DoSteps(1);
261 evt.Veto();
262 });
263
264 mSpinButton->Bind(
265 wxEVT_SPIN_DOWN,
266 [this](auto& evt)
267 {
268 DoSteps(-1);
269 evt.Veto();
270 });
271#else
272 mUpButton->Bind(
273 wxEVT_BUTTON,
274 [this](auto&)
275 {
276 DoSteps(1);
277 });
278
279 mDownButton->Bind(
280 wxEVT_BUTTON,
281 [this](auto&)
282 {
283 DoSteps(-1);
284 });
285#endif
286}
287
289{
291 {
292 auto validator = wxFloatingPointValidator<ValueType>(
293 mPrecision, nullptr, wxNUM_VAL_NO_TRAILING_ZEROES);
294
295 validator.SetMin(mMinValue);
296 validator.SetMax(mMaxValue);
297
298 mTextControl->SetValidator(validator);
299 }
300 else
301 {
302 auto validator = wxIntegerValidator<int>();
303
304 validator.SetMin(static_cast<int>(std::ceil(mMinValue)));
305 validator.SetMax(static_cast<int>(std::floor(mMaxValue)));
306
307 mTextControl->SetValidator(validator);
308 }
309}
310
312{
313 double value;
314 if (!mTextControl->GetValue().ToDouble(&value))
315 return;
316
317 SetValue(value, false);
318}
319
320void SpinControl::OnCharHook(wxKeyEvent& evt)
321{
322 const auto keyCode = evt.GetKeyCode();
323
324 if (keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP)
325 DoSteps(evt.ShiftDown() ? 10.0 : 1.0);
326 else if (keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP)
327 DoSteps(10.0);
328 else if (keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN)
329 DoSteps(evt.ShiftDown() ? -10.0 : -1.0);
330 else if (keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN)
331 DoSteps(-10.0);
332 else if (keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER)
334 else
335 evt.Skip();
336}
337
338void SpinControl::SetValue(double value, bool silent)
339{
340 value = std::clamp(value, mMinValue, mMaxValue);
341
342 // Should some epsilon be used here?
343 if (value == mValue)
344 return;
345
346 mValue = value;
347 mTextControl->SetValue(wxString::FromDouble(value));
348
349 if (!silent)
351}
352
353void SpinControl::DoSteps(double direction)
354{
355 SetValue(mValue + direction * mStep, false);
356}
357
359{
360 wxCommandEvent e(wxEVT_SPINCTRL, GetId());
361 e.SetEventObject(this);
362 ProcessWindowEvent(e);
363}
int min(int a, int b)
#define safenew
Definition: MemoryX.h:10
wxString name
Definition: TagsEditor.cpp:166
void SetMaxValue(double value)
bool GetFractionalAllowed()
wxButton * mDownButton
Definition: SpinControl.h:86
void UpdatePrefs() override
void DoSteps(double direction)
double GetValue() const
Definition: SpinControl.cpp:90
int mPrecision
Definition: SpinControl.h:77
void NotifyValueChanged()
void CreateUI()
wxTextCtrl * mTextControl
Definition: SpinControl.h:81
void SetStep(double step)
void SetFractionalAllowed(bool allow)
ValueType mMaxValue
Definition: SpinControl.h:75
void CommitTextControlValue()
void OnCharHook(wxKeyEvent &evt)
double GetStep() const
void SetMinValue(double value)
Definition: SpinControl.cpp:95
ValueType mValue
Definition: SpinControl.h:73
ValueType mStep
Definition: SpinControl.h:76
void SetValue(double value)
Definition: SpinControl.cpp:85
SpinControl(wxWindow *parent, wxWindowID winid=wxID_ANY, ValueType value=0.0, ValueType min=0.0f, ValueType max=100.0, ValueType step=1.0, bool allowFractional=false, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, const TranslatableString &name={})
Definition: SpinControl.cpp:63
void SetName(const TranslatableString &name)
bool mFractionalAllowed
Definition: SpinControl.h:79
wxButton * mUpButton
Definition: SpinControl.h:85
void SetupControls()
ValueType mMinValue
Definition: SpinControl.h:74
double GetMaxValue() const
double GetMinValue() const
Holds a msgid for the translation catalog; may also bind format arguments.
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...