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
156#ifdef EXPERIMENTAL_DA
157 false
158#else
159 true
160#endif
161 ;
162}
163
165{
166 return BotDockID;
167}
168
170{
171 auto &toolManager = ToolManager::Get( project );
172 return *static_cast<SelectionBar*>(toolManager.GetToolBar(ID()));
173}
174
176{
177 return Get( const_cast<AudacityProject&>( project )) ;
178}
179
180void SelectionBar::Create(wxWindow * parent)
181{
182 ToolBar::Create(parent);
183 UpdatePrefs();
184}
185
187{
188 wxImage up = theTheme.Image(bmpRecoloredUpSmall);
189 up.Rescale(20, 20, wxIMAGE_QUALITY_HIGH);
190 wxImage down = theTheme.Image(bmpRecoloredDownSmall);
191 down.Rescale(20, 20, wxIMAGE_QUALITY_HIGH);
192 wxImage hiliteUp = theTheme.Image(bmpRecoloredUpHiliteSmall);
193 hiliteUp.Rescale(20, 20, wxIMAGE_QUALITY_HIGH);
194 wxImage hiliteDown = theTheme.Image(bmpRecoloredHiliteSmall);
195 hiliteDown.Rescale(20, 20, wxIMAGE_QUALITY_HIGH);
196
197 auto btn = safenew AButton(
198 this, wxID_ANY, wxDefaultPosition, wxSize { 20, 20 }, up, hiliteUp, down,
199 hiliteDown, up, false);
200
202 btn->SetIcon(theTheme.Image(bmpCogwheel));
203 btn->SetLabel({});
204 btn->SetName(XO("Selection Toolbar Setup").Translation());
205
206 return btn;
207}
208
210 const TranslatableString & Title, wxSizer * pSizer ){
211 const auto translated = Title.Translation();
212 auStaticText * pTitle = safenew auStaticText(this, translated );
213 pTitle->SetBackgroundColour( theTheme.Colour( clrMedium ));
214 pTitle->SetForegroundColour( theTheme.Colour( clrTrackPanelText ) );
215 pSizer->Add( pTitle, 0, wxEXPAND | wxRIGHT, 5 );
216}
217
218
219void SelectionBar::AddTime(int id, wxSizer * pSizer)
220{
221 auto &formats = ProjectNumericFormats::Get(mProject);
222 auto formatName = formats.GetSelectionFormat();
224 this, id, NumericConverterType_TIME(), formatName, 0.0);
225
226 pCtrl->Bind(
227 wxEVT_TEXT,
228 [this, id](auto& evt) { ModifySelection(id, evt.GetInt() != 0); });
229
230 pSizer->Add(pCtrl, 0, wxALIGN_TOP | wxRIGHT, 5);
231
232 mTimeControls[id] = pCtrl;
233
234 mFormatChangedToFitValueSubscription[id] = pCtrl->Subscribe(
235 [this, id](const auto& msg)
236 {
237 auto altCtrl = mTimeControls[id == 0 ? 1 : 0];
238 if (altCtrl != nullptr)
239 altCtrl->UpdateFormatToFit(msg.value);
240
242 });
243}
244
246{
247 auto setupBtn = MakeSetupButton();
248
249 auto showMenu = [this, setupBtn]()
250 {
251 static const wxString choices[4] = {
252 _("Start and End of Selection"),
253 _("Start and Length of Selection"),
254 _("Length and End of Selection"),
255 _("Length and Center of Selection"),
256 };
257
258 wxMenu menu;
259 int selectionMode {};
260 for (auto& choice : choices)
261 {
262 auto subMenu = menu.AppendRadioItem(wxID_ANY, choice);
263
264 if (static_cast<SelectionMode>(selectionMode) == mSelectionMode)
265 subMenu->Check();
266
267 menu.Bind(
268 wxEVT_MENU,
269 [this, selectionMode](auto& evt)
270 {
271 SetSelectionMode(static_cast<SelectionMode>(selectionMode));
273 },
274 subMenu->GetId());
275
276 ++selectionMode;
277 }
278
279 menu.Bind(wxEVT_MENU_CLOSE, [setupBtn](auto&) { setupBtn->PopUp(); });
280
281 BasicMenu::Handle { &menu }.Popup(wxWidgetsWindowPlacement { setupBtn });
282 };
283
284 setupBtn->Bind(wxEVT_BUTTON, [this, showMenu](auto&) { showMenu(); });
285 pSizer->Add(setupBtn, 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, 5);
286
287 mSetupButton = setupBtn;
288}
289
291{
292 SetBackgroundColour( theTheme.Colour( clrMedium ) );
293
294 mTimeControls[0] = mTimeControls[1] = {};
295
296 // Outer sizer has space top and left.
297 // Inner sizers have space on right only.
298 // This choice makes for a nice border and internal spacing and places clear responsibility
299 // on each sizer as to what spacings it creates.
300 wxFlexGridSizer *mainSizer = safenew wxFlexGridSizer(2, 1, 1);
301 Add(mainSizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
302
303 AddTitle(XO("Selection"), mainSizer);
304 AddTime(0, mainSizer);
305 AddSelectionSetupButton(mainSizer);
306 AddTime(1, mainSizer);
307
308 mSetupButton->MoveBeforeInTabOrder(mTimeControls[0]);
309
310 // Update the selection mode before the layout
312 mainSizer->Layout();
314 Layout();
315
316 CallAfter([this]{
317 auto &formats = ProjectNumericFormats::Get(mProject);
318 SetSelectionFormat(formats.GetSelectionFormat());
319 });
320
321}
322
324{
325 // The project rate is no longer driven from here.
326 // When preferences change, the Project learns about it too.
327 // If necessary we can drive the SelectionBar mRate via the Project
328 // calling our SetRate().
329 // As of 13-Sep-2018, changes to the sample rate pref will only affect
330 // creation of new projects, not the sample rate in existing ones.
331
332 // This will only change the selection mode during a "Reset Configuration"
333 // action since the read value will be the same during a normal preferences
334 // update.
336
337 // This will only change the time format during a "Reset Configuration"
338 // action since the read value will be the same during a normal preferences
339 // update.
340 wxCommandEvent e;
344 gPrefs->Read(wxT("/SelectionFormat"), wxT(""))).Internal());
345 OnUpdate(e);
346
347 // Set label to pull in language change
348 SetLabel(XO("Selection"));
349
351 // Give base class a chance
353}
354
356{
357}
358
359void SelectionBar::OnSize(wxSizeEvent &evt)
360{
361 Refresh( true );
362
363 evt.Skip();
364}
365
366// When a control value is changed, this function is called.
367// It determines the values for the other controls.
368void SelectionBar::ModifySelection(int driver, bool done)
369{
370 // Only update a value if user typed something in.
371 // The reason is the controls may be less accurate than
372 // the values.
373 double start = mStart;
374 double end = mEnd;
375 double center = mCenter;
376 double length = mLength;
377
378 if (driver == 0)
379 {
380 if (
383 start = mTimeControls[0]->GetValue();
384 else if (
387 length = mTimeControls[0]->GetValue();
388 else
389 wxFAIL_MSG("Unexpected selection mode");
390 }
391 else if (driver == 1)
392 {
393 if (
396 end = mTimeControls[1]->GetValue();
398 length = mTimeControls[1]->GetValue();
400 center = mTimeControls[1]->GetValue();
401 else
402 wxFAIL_MSG("Unexpected selection mode");
403 }
404 else
405 {
406 wxFAIL_MSG("Illegal selection driver");
407 }
408
409 switch (mSelectionMode)
410 {
412 if (driver == 0 && end < start)
413 end = start;
414 else if (driver == 1 && start > end)
415 start = end;
416 break;
418 // Nothing can go wrong here
419 end = start + length;
420 break;
422 start = end - length;
423
424 if (start < 0)
425 {
426 // Length is set by user
427 if (driver == 0)
428 end -= start;
429
430 start = 0;
431 }
432 break;
434 start = center - length / 2.0;
435
436 if (start < 0)
437 {
438 // Length is set by user
439 if (driver == 0)
440 center = length / 2.0;
441 else
442 // Center is set by user
443 length = center * 2.0;
444
445 start = 0;
446 }
447
448 end = center + length / 2.0;
449 break;
450 default:
451 break;
452 }
453
454 // Places the start-end markers on the track panel.
456 manager.ModifySelection(start, end, done);
457}
458
459// Called when one of the format drop downs is changed.
460void SelectionBar::OnUpdate(wxCommandEvent &evt)
461{
462 evt.Skip(false);
463
464 wxWindow *w = FindFocus();
465
466 auto focusedCtrlIt =
467 std::find(mTimeControls.begin(), mTimeControls.end(), w);
468
469 const auto focusedCtrlIdx =
470 focusedCtrlIt != mTimeControls.end() ?
471 std::distance(mTimeControls.begin(), focusedCtrlIt) :
472 -1;
473
474 auto format = evt.GetString();
475
476 // Save format name before recreating the controls so they resize properly
477 if (mTimeControls.front())
478 {
479 auto &formats = ProjectNumericFormats::Get(mProject);
480 formats.SetSelectionFormat(format);
481 // Then my Subscription is called
482 }
483
484 // ReCreateButtons() will get rid of our sizers and controls
485 // so reset pointers first.
486 std::fill(mTimeControls.begin(), mTimeControls.end(), nullptr);
487
489
491
493
494 if (focusedCtrlIdx >= 0 && mTimeControls[focusedCtrlIdx])
495 mTimeControls[focusedCtrlIdx]->SetFocus();
496
498
499 Updated();
500}
501
502void SelectionBar::OnIdle( wxIdleEvent &evt )
503{
504 evt.Skip();
505 const auto& selectedRegion = ViewInfo::Get(mProject).selectedRegion;
506
507 SetTimes(selectedRegion.t0(), selectedRegion.t1());
508}
509
511{
512 // We just changed the mode. Remember it.
514
516}
517
518// We used to have 8 modes which showed different combinations of the
519// length, start, end, center controls.
520// Mode 7 for example showed all four at the same time.
522{
523 mSelectionMode = mode;
524
525 // Update names and WindowIds for the screen readers
526 auto& modeName = ModeNames[static_cast<size_t>(mode)];
527
528 if (mTimeControls[0]) {
529 mTimeControls[0]->SetName(modeName.first);
530 mTimeControls[0]->SetId(WindowIDs.at(modeName.first));
531 }
532
533 if (mTimeControls[1]) {
534 mTimeControls[1]->SetName(modeName.second);
535 mTimeControls[1]->SetId(WindowIDs.at(modeName.second));
536 }
537
538 UpdateTimeControlsFormat(mTimeControls[0]->GetFormatName());
539
541}
542
544{
545 const double valuePairs[4][2] = {
546 { mStart, mEnd },
547 { mStart, mLength },
548 { mLength, mEnd },
549 { mLength, mCenter } };
550
551 const auto value = valuePairs[static_cast<size_t>(mSelectionMode)];
552
553 size_t i = 0;
554 for (auto ctrl : mTimeControls)
555 {
556 if (ctrl != nullptr)
557 ctrl->SetValue(value[i]);
558
559 i++;
560 }
561}
562
563// A time has been set. Update the control values.
564void SelectionBar::SetTimes(double start, double end)
565{
566 if ( start != mStart || end != mEnd
568 ) {
569 mStart = start;
570 mEnd = end;
571 mLength = end-start;
572 mCenter = (end+start)/2.0;
574
576 }
577}
578
580{
581 if (mTimeControls.front() == nullptr)
582 return;
583
584 const bool changed = mTimeControls.front()->SetFormatName(format);
585
586 // Test first whether changed, to avoid infinite recursion from OnUpdate
587 if ( changed ) {
588 wxCommandEvent e;
589 e.SetString(format.GET());
590 OnUpdate(e);
591 }
592}
593
595{
596 auto &formats = ProjectNumericFormats::Get(mProject);
597 switch (evt.type) {
599 return SetSelectionFormat(formats.GetSelectionFormat());
600 default:
601 break;
602 }
603}
604
605void SelectionBar::OnFocus(wxFocusEvent &event)
606{
607 KeyboardCapture::OnFocus( *this, event );
608}
609
610void SelectionBar::OnCaptureKey(wxCommandEvent &event)
611{
612 wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
613 wxWindow *w = FindFocus();
614 int keyCode = kevent->GetKeyCode();
615
616 // Convert numeric keypad entries.
617 if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9)) {
618 keyCode -= WXK_NUMPAD0 - '0';
619 }
620
621 if (keyCode >= '0' && keyCode <= '9') {
622 return;
623 }
624
625 event.Skip();
626}
627
629{
630 for (size_t controlIndex = 0; controlIndex < mTimeControls.size();
631 ++controlIndex)
632 {
633 auto ctrl = mTimeControls[controlIndex];
634
635 if (ctrl == nullptr)
636 continue;
637
638 const auto type =
639 TimeConverterType[static_cast<size_t>(mSelectionMode)][controlIndex];
640
641 ctrl->SetTypeAndFormatName(
642 type, type != NumericConverterType_DURATION() ?
643 format :
645 }
646}
647
649{
650 wxSize sz = GetMinSize();
651 sz.SetWidth(10);
652 SetMinSize(sz);
653 Fit();
654 Layout();
655 Updated();
656}
657
661};
662
663namespace {
665 /* i18n-hint: Clicking this menu item shows the toolbar
666 for selecting a time range of audio */
667 SelectionBar::ID(), wxT("ShowSelectionTB"), XXO("&Selection Toolbar")
668};
669}
670
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:147
@ 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:354
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:257
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:205
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:74
AudacityProject & mProject
Definition: ToolBar.h:248
DockID
Identifies one of the docking areas for toolbars.
Definition: ToolBar.h:92
@ BotDockID
Definition: ToolBar.h:94
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:78
static ToolManager & Get(AudacityProject &project)
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
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:208
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:196
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:373
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.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
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
enum ProjectNumericFormatsEvent::Type type
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.