Audacity 3.2.0
SelectionBar.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 SelectionBar.cpp
6
7 Copyright 2005 Dominic Mazzoni
8 2023 Dmitry Vedenko
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15*******************************************************************//*******************************************************************/
22
23
24
25#include "SelectionBar.h"
26
27#include <algorithm>
28
29#include "ToolManager.h"
30
31// For compilers that support precompilation, includes "wx/wx.h".
32#include <wx/wxprec.h>
33
34#include <wx/setup.h> // for wxUSE_* macros
35
36#ifndef WX_PRECOMP
37#include <wx/settings.h>
38#include <wx/sizer.h>
39#include <wx/valtext.h>
40#include <wx/stattext.h>
41#endif
42#include <wx/statline.h>
43#include <wx/menu.h>
44
45
46#include "AudioIO.h"
47#include "AColor.h"
48#include "../KeyboardCapture.h"
49#include "Prefs.h"
50#include "Project.h"
51#include "ProjectAudioIO.h"
53#include "ProjectRate.h"
56#include "ViewInfo.h"
57#include "AllThemeResources.h"
58#include "ImageManipulation.h"
59#include "../widgets/AButton.h"
60#include "../widgets/auStaticText.h"
61#include "../widgets/BasicMenu.h"
62#include "../widgets/NumericTextCtrl.h"
65
66IntSetting SelectionToolbarMode { "/SelectionToolbarMode", 0 };
67
68namespace
69{
71{
72 return static_cast<SelectionBar::SelectionMode>(
73 std::clamp(SelectionToolbarMode.Read(), 0, 3));
74}
75
77{
78 SelectionToolbarMode.Write(static_cast<int>(selectionMode));
79 gPrefs->Flush();
80}
81
82/* i18n-hint noun */
84/* i18n-hint noun */
86/* i18n-hint noun */
88/* i18n-hint noun */
90
91std::pair<const TranslatableString&, const TranslatableString&> ModeNames[] = {
96};
97
98std::unordered_map<TranslatableString, wxWindowID> WindowIDs {
99 { StartTimeText, 2705 },
100 { LengthTimeText, 2706 },
101 { CenterTimeText, 2707 },
102 { EndTimeText, 2708 }
103};
104
110};
111}
112
114
115BEGIN_EVENT_TABLE(SelectionBar, ToolBar)
116 EVT_SIZE(SelectionBar::OnSize)
117
118 EVT_IDLE( SelectionBar::OnIdle )
119
120 EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, SelectionBar::OnUpdate)
121 EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, SelectionBar::OnCaptureKey)
123
125{
126 return wxT("Selection");
127}
128
130: ToolBar(project, XO("Selection"), ID()),
131 mRate(0.0),
132 mStart(0.0), mEnd(0.0), mLength(0.0), mCenter(0.0),
133 mFormatsSubscription{ ProjectNumericFormats::Get(project).Subscribe(
134 *this, &SelectionBar::OnFormatsChanged) }
135{
136 // Make sure we have a valid rate as the NumericTextCtrl()s
137 // created in Populate()
138 // depend on it. Otherwise, division-by-zero floating point exceptions
139 // will occur.
140 // Refer to bug #462 for a scenario where the division-by-zero causes
141 // Audacity to fail.
142 // We expect mRate to be set from the project later.
144
145 // Selection mode of 0 means showing 'start' and 'end' only.
147}
148
150{
151}
152
154{
155 return true;
156}
157
159{
160 return BotDockID;
161}
162
164{
165 auto &toolManager = ToolManager::Get( project );
166 return *static_cast<SelectionBar*>(toolManager.GetToolBar(ID()));
167}
168
170{
171 return Get( const_cast<AudacityProject&>( project )) ;
172}
173
174void SelectionBar::Create(wxWindow * parent)
175{
176 ToolBar::Create(parent);
177 UpdatePrefs();
178}
179
181{
182 wxImage up = theTheme.Image(bmpRecoloredUpSmall);
183 up.Rescale(23, 23, wxIMAGE_QUALITY_HIGH);
184 wxImage down = theTheme.Image(bmpRecoloredDownSmall);
185 down.Rescale(23, 23, wxIMAGE_QUALITY_HIGH);
186 wxImage hiliteUp = theTheme.Image(bmpRecoloredUpHiliteSmall);
187 hiliteUp.Rescale(23, 23, wxIMAGE_QUALITY_HIGH);
188 wxImage hiliteDown = theTheme.Image(bmpRecoloredHiliteSmall);
189 hiliteDown.Rescale(23, 23, wxIMAGE_QUALITY_HIGH);
190
191 auto btn = safenew AButton(
192 this, wxID_ANY, wxDefaultPosition, wxSize { 23, 23 }, up, hiliteUp, down,
193 hiliteDown, up, false);
194
196 btn->SetIcon(theTheme.Image(bmpCogwheel));
197 btn->SetLabel({});
198 btn->SetName(XO("Selection Toolbar Setup").Translation());
199
200 return btn;
201}
202
204 const TranslatableString & Title, wxSizer * pSizer ){
205 const auto translated = Title.Translation();
206 auStaticText * pTitle = safenew auStaticText(this, translated );
207 pTitle->SetBackgroundColour( theTheme.Colour( clrMedium ));
208 pTitle->SetForegroundColour( theTheme.Colour( clrTrackPanelText ) );
209 pSizer->Add( pTitle, 0, wxEXPAND | wxRIGHT, 5 );
210}
211
212
213void SelectionBar::AddTime(int id, wxSizer * pSizer)
214{
215 auto &formats = ProjectNumericFormats::Get(mProject);
216 auto formatName = formats.GetSelectionFormat();
218 this, id, NumericConverterType_TIME(), formatName, 0.0);
219
220 pCtrl->Bind(
221 wxEVT_TEXT,
222 [this, id](auto& evt) { ModifySelection(id, evt.GetInt() != 0); });
223
224 pSizer->Add(pCtrl, 0, wxALIGN_TOP | wxRIGHT, 5);
225
226 mTimeControls[id] = pCtrl;
227
228 mFormatChangedToFitValueSubscription[id] = pCtrl->Subscribe(
229 [this, id](const auto& msg)
230 {
231 auto altCtrl = mTimeControls[id == 0 ? 1 : 0];
232 if (altCtrl != nullptr)
233 altCtrl->UpdateFormatToFit(msg.value);
234
236 });
237}
238
240{
241 auto setupBtn = MakeSetupButton();
242
243 auto showMenu = [this, setupBtn]()
244 {
245 static const wxString choices[4] = {
246 _("Start and End of Selection"),
247 _("Start and Length of Selection"),
248 _("Length and End of Selection"),
249 _("Length and Center of Selection"),
250 };
251
252 wxMenu menu;
253 int selectionMode {};
254 for (auto& choice : choices)
255 {
256 auto subMenu = menu.AppendRadioItem(wxID_ANY, choice);
257
258 if (static_cast<SelectionMode>(selectionMode) == mSelectionMode)
259 subMenu->Check();
260
261 menu.Bind(
262 wxEVT_MENU,
263 [this, selectionMode](auto& evt)
264 {
265 SetSelectionMode(static_cast<SelectionMode>(selectionMode));
267 },
268 subMenu->GetId());
269
270 ++selectionMode;
271 }
272
273 menu.Bind(wxEVT_MENU_CLOSE, [setupBtn](auto&) { setupBtn->PopUp(); });
274
275 BasicMenu::Handle { &menu }.Popup(wxWidgetsWindowPlacement { setupBtn });
276 };
277
278 setupBtn->Bind(wxEVT_BUTTON, [this, showMenu](auto&) { showMenu(); });
279 pSizer->Add(setupBtn, 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, 5);
280
281 mSetupButton = setupBtn;
282}
283
285{
286 SetBackgroundColour( theTheme.Colour( clrMedium ) );
287
288 mTimeControls[0] = mTimeControls[1] = {};
289
290 // Outer sizer has space top and left.
291 // Inner sizers have space on right only.
292 // This choice makes for a nice border and internal spacing and places clear responsibility
293 // on each sizer as to what spacings it creates.
294 wxFlexGridSizer *mainSizer = safenew wxFlexGridSizer(2, 1, 1);
295 Add(mainSizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
296
297 AddTitle(XO("Selection"), mainSizer);
298 AddTime(0, mainSizer);
299 AddSelectionSetupButton(mainSizer);
300 AddTime(1, mainSizer);
301
302 mSetupButton->MoveBeforeInTabOrder(mTimeControls[0]);
303
304 // Update the selection mode before the layout
306 mainSizer->Layout();
308 Layout();
309
310 CallAfter([this]{
311 auto &formats = ProjectNumericFormats::Get(mProject);
312 SetSelectionFormat(formats.GetSelectionFormat());
313 });
314
315}
316
318{
319 // The project rate is no longer driven from here.
320 // When preferences change, the Project learns about it too.
321 // If necessary we can drive the SelectionBar mRate via the Project
322 // calling our SetRate().
323 // As of 13-Sep-2018, changes to the sample rate pref will only affect
324 // creation of new projects, not the sample rate in existing ones.
325
326 // This will only change the selection mode during a "Reset Configuration"
327 // action since the read value will be the same during a normal preferences
328 // update.
330
331 // This will only change the time format during a "Reset Configuration"
332 // action since the read value will be the same during a normal preferences
333 // update.
334 wxCommandEvent e;
338 gPrefs->Read(wxT("/SelectionFormat"), wxT(""))).Internal());
339 OnUpdate(e);
340
341 // Set label to pull in language change
342 SetLabel(XO("Selection"));
343
345 // Give base class a chance
347}
348
350{
351}
352
353void SelectionBar::OnSize(wxSizeEvent &evt)
354{
355 Refresh( true );
356
357 evt.Skip();
358}
359
360// When a control value is changed, this function is called.
361// It determines the values for the other controls.
362void SelectionBar::ModifySelection(int driver, bool done)
363{
364 // Only update a value if user typed something in.
365 // The reason is the controls may be less accurate than
366 // the values.
367 double start = mStart;
368 double end = mEnd;
369 double center = mCenter;
370 double length = mLength;
371
372 if (driver == 0)
373 {
374 if (
377 start = mTimeControls[0]->GetValue();
378 else if (
381 length = mTimeControls[0]->GetValue();
382 else
383 wxFAIL_MSG("Unexpected selection mode");
384 }
385 else if (driver == 1)
386 {
387 if (
390 end = mTimeControls[1]->GetValue();
392 length = mTimeControls[1]->GetValue();
394 center = mTimeControls[1]->GetValue();
395 else
396 wxFAIL_MSG("Unexpected selection mode");
397 }
398 else
399 {
400 wxFAIL_MSG("Illegal selection driver");
401 }
402
403 switch (mSelectionMode)
404 {
406 if (driver == 0 && end < start)
407 end = start;
408 else if (driver == 1 && start > end)
409 start = end;
410 break;
412 // Nothing can go wrong here
413 end = start + length;
414 break;
416 start = end - length;
417
418 if (start < 0)
419 {
420 // Length is set by user
421 if (driver == 0)
422 end -= start;
423
424 start = 0;
425 }
426 break;
428 start = center - length / 2.0;
429
430 if (start < 0)
431 {
432 // Length is set by user
433 if (driver == 0)
434 center = length / 2.0;
435 else
436 // Center is set by user
437 length = center * 2.0;
438
439 start = 0;
440 }
441
442 end = center + length / 2.0;
443 break;
444 default:
445 break;
446 }
447
448 // Places the start-end markers on the track panel.
450 manager.ModifySelection(start, end, done);
451}
452
453// Called when one of the format drop downs is changed.
454void SelectionBar::OnUpdate(wxCommandEvent &evt)
455{
456 evt.Skip(false);
457
458 wxWindow *w = FindFocus();
459
460 auto focusedCtrlIt =
461 std::find(mTimeControls.begin(), mTimeControls.end(), w);
462
463 const auto focusedCtrlIdx =
464 focusedCtrlIt != mTimeControls.end() ?
465 std::distance(mTimeControls.begin(), focusedCtrlIt) :
466 -1;
467
468 auto format = evt.GetString();
469
470 // Save format name before recreating the controls so they resize properly
471 if (mTimeControls.front())
472 {
473 auto &formats = ProjectNumericFormats::Get(mProject);
474 formats.SetSelectionFormat(format);
475 // Then my Subscription is called
476 }
477
478 // ReCreateButtons() will get rid of our sizers and controls
479 // so reset pointers first.
480 std::fill(mTimeControls.begin(), mTimeControls.end(), nullptr);
481
483
485
487
488 if (focusedCtrlIdx >= 0 && mTimeControls[focusedCtrlIdx])
489 mTimeControls[focusedCtrlIdx]->SetFocus();
490
492
493 Updated();
494}
495
496void SelectionBar::OnIdle( wxIdleEvent &evt )
497{
498 evt.Skip();
499 const auto& selectedRegion = ViewInfo::Get(mProject).selectedRegion;
500
501 SetTimes(selectedRegion.t0(), selectedRegion.t1());
502}
503
505{
506 // We just changed the mode. Remember it.
508
510}
511
512// We used to have 8 modes which showed different combinations of the
513// length, start, end, center controls.
514// Mode 7 for example showed all four at the same time.
516{
517 mSelectionMode = mode;
518
519 // Update names and WindowIds for the screen readers
520 auto& modeName = ModeNames[static_cast<size_t>(mode)];
521
522 if (mTimeControls[0]) {
523 mTimeControls[0]->SetName(modeName.first);
524 mTimeControls[0]->SetId(WindowIDs.at(modeName.first));
525 }
526
527 if (mTimeControls[1]) {
528 mTimeControls[1]->SetName(modeName.second);
529 mTimeControls[1]->SetId(WindowIDs.at(modeName.second));
530 }
531
532 UpdateTimeControlsFormat(mTimeControls[0]->GetFormatName());
533
535}
536
538{
539 const double valuePairs[4][2] = {
540 { mStart, mEnd },
541 { mStart, mLength },
542 { mLength, mEnd },
543 { mLength, mCenter } };
544
545 const auto value = valuePairs[static_cast<size_t>(mSelectionMode)];
546
547 size_t i = 0;
548 for (auto ctrl : mTimeControls)
549 {
550 if (ctrl != nullptr)
551 ctrl->SetValue(value[i]);
552
553 i++;
554 }
555}
556
557// A time has been set. Update the control values.
558void SelectionBar::SetTimes(double start, double end)
559{
560 if ( start != mStart || end != mEnd
562 ) {
563 mStart = start;
564 mEnd = end;
565 mLength = end-start;
566 mCenter = (end+start)/2.0;
568
570 }
571}
572
574{
575 if (mTimeControls.front() == nullptr)
576 return;
577
578 const bool changed = mTimeControls.front()->SetFormatName(format);
579
580 // Test first whether changed, to avoid infinite recursion from OnUpdate
581 if ( changed ) {
582 wxCommandEvent e;
583 e.SetString(format.GET());
584 OnUpdate(e);
585 }
586}
587
589{
590 auto &formats = ProjectNumericFormats::Get(mProject);
591 switch (evt.type) {
593 return SetSelectionFormat(formats.GetSelectionFormat());
594 default:
595 break;
596 }
597}
598
599void SelectionBar::OnFocus(wxFocusEvent &event)
600{
601 KeyboardCapture::OnFocus( *this, event );
602}
603
604void SelectionBar::OnCaptureKey(wxCommandEvent &event)
605{
606 wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
607 wxWindow *w = FindFocus();
608 int keyCode = kevent->GetKeyCode();
609
610 // Convert numeric keypad entries.
611 if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9)) {
612 keyCode -= WXK_NUMPAD0 - '0';
613 }
614
615 if (keyCode >= '0' && keyCode <= '9') {
616 return;
617 }
618
619 event.Skip();
620}
621
623{
624 for (size_t controlIndex = 0; controlIndex < mTimeControls.size();
625 ++controlIndex)
626 {
627 auto ctrl = mTimeControls[controlIndex];
628
629 if (ctrl == nullptr)
630 continue;
631
632 const auto type =
633 TimeConverterType[static_cast<size_t>(mSelectionMode)][controlIndex];
634
635 ctrl->SetTypeAndFormatName(
636 type, type != NumericConverterType_DURATION() ?
637 format :
639 }
640}
641
643{
644 wxSize sz = GetMinSize();
645 sz.SetWidth(10);
646 SetMinSize(sz);
647 Fit();
648 Layout();
649 Updated();
650}
651
655};
656
657namespace {
659 /* i18n-hint: Clicking this menu item shows the toolbar
660 for selecting a time range of audio */
661 SelectionBar::ID(), wxT("ShowSelectionTB"), XXO("&Selection Toolbar")
662};
663}
664
wxImage(22, 22)
wxT("CloseDown"))
@ Internal
Indicates internal failure from Audacity.
END_EVENT_TABLE()
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:89
#define safenew
Definition: MemoryX.h:10
const NumericConverterType & NumericConverterType_DURATION()
const NumericConverterType & NumericConverterType_TIME()
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
an object holding per-project preferred sample rate
static const AttachedProjectObjects::RegisteredFactory manager
IMPLEMENT_CLASS(SelectionBar, ToolBar)
static RegisteredToolbarFactory factory
IntSetting SelectionToolbarMode
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
int id
A wxButton with mouse-over behaviour.
Definition: AButton.h:104
void SetButtonType(Type type)
Definition: AButton.cpp:148
@ FrameButton
Definition: AButton.h:114
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
static FormatterContext ProjectContext(const AudacityProject &project)
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
Specialization of Setting for int.
Definition: Prefs.h:356
static ProjectNumericFormats & Get(AudacityProject &project)
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
static ProjectSelectionManager & Get(AudacityProject &project)
(not quite a Toolbar) at foot of screen for setting and viewing the selection range.
Definition: SelectionBar.h:37
void OnIdle(wxIdleEvent &evt)
void AddTitle(const TranslatableString &Title, wxSizer *pSizer)
void ModifySelection(int driver, bool done=false)
void AddSelectionSetupButton(wxSizer *pSizer)
void SetSelectionMode(SelectionMode mode)
AButton * mSetupButton
Definition: SelectionBar.h:104
virtual ~SelectionBar()
void SetSelectionFormat(const NumericFormatID &format)
void SelectionModeUpdated()
void AddTime(int id, wxSizer *pSizer)
void RegenerateTooltips() override
void SetTimes(double start, double end)
double mLength
Definition: SelectionBar.h:98
Observer::Subscription mFormatChangedToFitValueSubscription[2]
Definition: SelectionBar.h:106
static Identifier ID()
void Create(wxWindow *parent) override
double mRate
Definition: SelectionBar.h:97
void OnFocus(wxFocusEvent &event)
void OnSize(wxSizeEvent &evt)
void OnUpdate(wxCommandEvent &evt)
void OnFormatsChanged(struct ProjectNumericFormatsEvent)
bool ShownByDefault() const override
Whether the toolbar should be shown by default. Default implementation returns true.
void FitToTimeControls()
static SelectionBar & Get(AudacityProject &project)
void UpdateTimeControlsFormat(const NumericFormatID &format)
double mCenter
Definition: SelectionBar.h:98
SelectionMode mLastSelectionMode
Definition: SelectionBar.h:101
void ValuesToControls()
void Populate() override
SelectionMode mSelectionMode
Definition: SelectionBar.h:100
void OnCaptureKey(wxCommandEvent &event)
SelectionBar(AudacityProject &project)
DockID DefaultDockID() const override
Which dock the toolbar defaults into. Default implementation chooses the top dock.
double mStart
Definition: SelectionBar.h:98
void UpdatePrefs() override
std::array< NumericTextCtrl *, 2 > mTimeControls
Definition: SelectionBar.h:103
AButton * MakeSetupButton()
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:259
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
wxColour & Colour(int iIndex)
wxImage & Image(int iIndex)
Works with ToolManager and ToolDock to provide a dockable window in which buttons can be placed.
Definition: ToolBar.h:73
AudacityProject & mProject
Definition: ToolBar.h:247
DockID
Identifies one of the docking areas for toolbars.
Definition: ToolBar.h:91
@ BotDockID
Definition: ToolBar.h:93
void Add(wxWindow *window, int proportion=0, int flag=wxALIGN_TOP, int border=0, wxObject *userData=NULL)
Definition: ToolBar.cpp:709
virtual void ReCreateButtons()
Definition: ToolBar.cpp:533
void SetLabel(const wxString &label) override
Definition: ToolBar.cpp:408
void UpdatePrefs() override
Definition: ToolBar.cpp:622
void Updated()
Definition: ToolBar.cpp:684
virtual void Create(wxWindow *parent)
Definition: ToolBar.cpp:492
wxWindowPtr< ToolBar > Holder
Definition: ToolBar.h:77
static ToolManager & Get(AudacityProject &project)
Holds a msgid for the translation catalog; may also bind format arguments.
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
is like wxStaticText, except it can be themed. wxStaticText can't be.
Definition: auStaticText.h:20
virtual bool Flush() noexcept=0
virtual bool Read(const wxString &key, bool *value) const =0
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:383
void OnFocus(wxWindow &window, wxFocusEvent &event)
a function useful to implement a focus event handler The window releases the keyboard if the event is...
NUMERIC_FORMATS_API NumericFormatSymbol Lookup(const FormatterContext &context, const NumericConverterType &type, const NumericFormatID &formatIdentifier)
Looks up the format, returns Default for the type if the format is not registered.
NUMERIC_FORMATS_API NumericFormatID GetBestDurationFormat(const NumericFormatID &timeFormat)
Return the best duration format for the given time format. Currently is an identity function.
const TranslatableString CenterTimeText
SelectionBar::SelectionMode ReadSelectionMode()
const TranslatableString LengthTimeText
AttachedToolBarMenuItem sAttachment
std::pair< const TranslatableString &, const TranslatableString & > ModeNames[]
const NumericConverterType TimeConverterType[][2]
void UpdateSelectionMode(SelectionBar::SelectionMode selectionMode)
const TranslatableString EndTimeText
std::unordered_map< TranslatableString, wxWindowID > WindowIDs
const TranslatableString StartTimeText
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
enum ProjectNumericFormatsEvent::Type type
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.