/********************************************************************** Audacity: A Digital Audio Editor SpectralSelectionBar.cpp Copyright 2014 Dominic Mazzoni This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. *******************************************************************//** \class SpectralSelectionBar \brief (not quite a Toolbar) at foot of screen for setting and viewing the frequency selection range. *//****************************************************************//** \class SpectralSelectionBarListener \brief A class used to forward events to do with changes in the SpectralSelectionBar. *//*******************************************************************/ #include "../Audacity.h" #include "SpectralSelectionBar.h" #include "SpectralSelectionBarListener.h" #include "../Experimental.h" #include "ToolManager.h" #include // For compilers that support precompilation, includes "wx/wx.h". #include #include // for wxUSE_* macros #ifndef WX_PRECOMP #include #include #include #include #include #include #include #include #include #include #endif #include #include "../Prefs.h" #include "../Project.h" #include "../AllThemeResources.h" #include "../SelectedRegion.h" #include "../ViewInfo.h" #if wxUSE_ACCESSIBILITY #include "../widgets/WindowAccessible.h" #endif #include "../widgets/NumericTextCtrl.h" #ifdef EXPERIMENTAL_SPECTRAL_EDITING IMPLEMENT_CLASS(SpectralSelectionBar, ToolBar); enum { SpectralSelectionBarFirstID = 2750, OnCenterID, OnWidthID, OnLowID, OnHighID, OnChoiceID, }; BEGIN_EVENT_TABLE(SpectralSelectionBar, ToolBar) EVT_SIZE(SpectralSelectionBar::OnSize) EVT_TEXT(OnCenterID, SpectralSelectionBar::OnCtrl) EVT_TEXT(OnWidthID, SpectralSelectionBar::OnCtrl) EVT_TEXT(OnLowID, SpectralSelectionBar::OnCtrl) EVT_TEXT(OnHighID, SpectralSelectionBar::OnCtrl) EVT_CHOICE(OnChoiceID, SpectralSelectionBar::OnChoice) EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, SpectralSelectionBar::OnUpdate) EVT_COMMAND(wxID_ANY, EVT_BANDWIDTHTEXTCTRL_UPDATED, SpectralSelectionBar::OnUpdate) EVT_IDLE( SpectralSelectionBar::OnIdle ) END_EVENT_TABLE() static const wxString preferencePath (wxT("/GUI/Toolbars/SpectralSelection/CenterAndWidthChoice")); SpectralSelectionBar::SpectralSelectionBar( AudacityProject &project ) : ToolBar( project, SpectralSelectionBarID, XO("Spectral Selection"), wxT("SpectralSelection") ) , mListener(NULL), mbCenterAndWidth(true) , mCenter(0.0), mWidth(0.0), mLow(0.0), mHigh(0.0) , mCenterCtrl(NULL), mWidthCtrl(NULL), mLowCtrl(NULL), mHighCtrl(NULL) , mChoice(NULL) { } SpectralSelectionBar::~SpectralSelectionBar() { // Do nothing, sizer deletes the controls } SpectralSelectionBar &SpectralSelectionBar::Get( AudacityProject &project ) { auto &toolManager = ToolManager::Get( project ); return *static_cast( toolManager.GetToolBar(SpectralSelectionBarID) ); } const SpectralSelectionBar &SpectralSelectionBar::Get( const AudacityProject &project ) { return Get( const_cast( project )) ; } void SpectralSelectionBar::Create(wxWindow * parent) { ToolBar::Create(parent); UpdatePrefs(); mHeight = wxWindowBase::GetSizer()->GetSize().GetHeight(); } void SpectralSelectionBar::Populate() { SetBackgroundColour( theTheme.Colour( clrMedium ) ); gPrefs->Read(preferencePath, &mbCenterAndWidth, true); // This will be inherited by all children: SetFont(wxFont( #ifdef __WXMAC__ 12 #else 9 #endif , wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); /* we don't actually need a control yet, but we want to use its methods * to do some look-ups, so we'll have to create one. We can't make the * look-ups static because they depend on translations which are done at * runtime */ auto frequencyFormatName = mListener ? mListener->SSBL_GetFrequencySelectionFormatName() : NumericFormatSymbol{}; auto bandwidthFormatName = mListener ? mListener->SSBL_GetBandwidthSelectionFormatName() : NumericFormatSymbol{}; wxFlexGridSizer *mainSizer; Add((mainSizer = safenew wxFlexGridSizer(1, 1, 1)), 0,wxALIGN_TOP | wxLEFT | wxTOP, 5); // // Top row, choice box // const wxString choices[2] = { _("Center frequency and Width"), _("Low and High Frequencies"), }; mChoice = safenew wxChoice (this, OnChoiceID, wxDefaultPosition, wxDefaultSize, 2, choices, 0, wxDefaultValidator, _("Show")); mChoice->SetSelection(mbCenterAndWidth ? 0 : 1); #if wxUSE_ACCESSIBILITY // so that name can be set on a standard control mChoice->SetAccessible(safenew WindowAccessible(mChoice)); #endif #ifdef __WXGTK__ // Combo boxes are taller on Linux, and if we don't do the following, the selection toolbar will // be three units high. wxSize sz = mChoice->GetBestSize(); sz.SetHeight( sz.y-4); mChoice->SetMinSize( sz ); #endif mainSizer->Add(mChoice, 0, wxALIGN_TOP | wxEXPAND |wxRIGHT, 6); // // Bottom row, split into two columns, each with one control // { auto subSizer = std::make_unique(wxHORIZONTAL); mCenterCtrl = safenew NumericTextCtrl( this, OnCenterID, NumericConverter::FREQUENCY, frequencyFormatName, 0.0, 44100.0, NumericTextCtrl::Options{} .InvalidValue( true, SelectedRegion::UndefinedFrequency ) ); mCenterCtrl->SetName( XO("Center Frequency") ); subSizer->Add(mCenterCtrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); mWidthCtrl = safenew NumericTextCtrl( this, OnWidthID, NumericConverter::BANDWIDTH, bandwidthFormatName, 0.0, 44100.0, NumericTextCtrl::Options{} .InvalidValue( true, -1.0 ) ); mWidthCtrl->SetName( XO("Bandwidth") ); subSizer->Add(mWidthCtrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); mLowCtrl = safenew NumericTextCtrl( this, OnLowID, NumericConverter::FREQUENCY, frequencyFormatName, 0.0, 44100.0, NumericTextCtrl::Options{} .InvalidValue( true, SelectedRegion::UndefinedFrequency ) ); mLowCtrl->SetName( XO("Low Frequency") ); subSizer->Add(mLowCtrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); mHighCtrl = safenew NumericTextCtrl( this, OnHighID, NumericConverter::FREQUENCY, frequencyFormatName, 0.0, 44100.0, NumericTextCtrl::Options{} .InvalidValue( true, SelectedRegion::UndefinedFrequency ) ); mHighCtrl->SetName( XO("High Frequency") ); subSizer->Add(mHighCtrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); mCenterCtrl->Show(mbCenterAndWidth); mWidthCtrl->Show(mbCenterAndWidth); mLowCtrl->Show(!mbCenterAndWidth); mHighCtrl->Show(!mbCenterAndWidth); mainSizer->Add(subSizer.release(), 0, wxALIGN_CENTER_VERTICAL, 0); } mainSizer->Layout(); Layout(); SetMinSize(GetSizer()->GetMinSize()); } void SpectralSelectionBar::UpdatePrefs() { { wxCommandEvent e(EVT_FREQUENCYTEXTCTRL_UPDATED); e.SetInt((mbCenterAndWidth? mCenterCtrl : mLowCtrl)->GetFormatIndex()); OnUpdate(e); } if (mbCenterAndWidth) { wxCommandEvent e(EVT_BANDWIDTHTEXTCTRL_UPDATED); e.SetInt(mWidthCtrl->GetFormatIndex()); OnUpdate(e); } // Set label to pull in language change SetLabel(XO("Spectral Selection")); RegenerateTooltips(); // Give base class a chance ToolBar::UpdatePrefs(); } void SpectralSelectionBar::SetListener(SpectralSelectionBarListener *l) { mListener = l; SetFrequencySelectionFormatName(mListener->SSBL_GetFrequencySelectionFormatName()); SetBandwidthSelectionFormatName(mListener->SSBL_GetBandwidthSelectionFormatName()); }; void SpectralSelectionBar::OnSize(wxSizeEvent &evt) { Refresh(true); evt.Skip(); } void SpectralSelectionBar::ModifySpectralSelection(bool done) { const double nyq = mListener->SSBL_GetRate() / 2.0; double bottom, top; if (mbCenterAndWidth) { mCenter = mCenterCtrl->GetValue(); mWidth = mWidthCtrl->GetValue(); if ((mCenter < 0 || mWidth < 0) && (mLow >= 0 || mHigh >= 0)) // Transition from defined spectral selection to undefined bottom = top = SelectedRegion::UndefinedFrequency; else if (mCenter < 0 && mWidth < 0) bottom = top = SelectedRegion::UndefinedFrequency; else { if (mCenter < 0) { mWidth = log(std::min(nyq, exp(mWidth))); // Choose arbitrary center for the width mCenter = sqrt(nyq); } else if (mWidth < 0) { mCenter = std::max(1.0, std::min(nyq, mCenter)); // Choose arbitrary width for the center const double ratio = std::min(mCenter, nyq / mCenter); mWidth = log(ratio * ratio); } else { mCenter = std::max(1.0, std::min(nyq, mCenter)); double ratio = std::min(mCenter, nyq / mCenter); mWidth = std::min(2 * log(ratio), mWidth); } const double ratio = exp(mWidth / 2); bottom = mCenter / ratio, top = mCenter * ratio; } } else { bottom = mLowCtrl->GetValue(); top = mHighCtrl->GetValue(); if (bottom >= 0) bottom = std::min(nyq, bottom); else bottom = SelectedRegion::UndefinedFrequency; if (top >= 0) top = std::min(nyq, top); else top = SelectedRegion::UndefinedFrequency; // These have to be in the right order. if( bottom > top ){ // Oops. We must fix the order. if( mLowCtrl->HasFocus() ) top = bottom; else bottom = top; } } mLow = bottom; mHigh = top; //SetBounds(); // Notify project and track panel, which may change // the values again, and call back to us in SetFrequencies() mListener->SSBL_ModifySpectralSelection(bottom, top, done); } void SpectralSelectionBar::OnCtrl(wxCommandEvent & event) { ModifySpectralSelection(event.GetInt() != 0); } void SpectralSelectionBar::OnChoice(wxCommandEvent &) { mbCenterAndWidth = (0 == mChoice->GetSelection()); gPrefs->Write(preferencePath, mbCenterAndWidth); gPrefs->Flush(); mCenterCtrl->Show(mbCenterAndWidth); mWidthCtrl->Show(mbCenterAndWidth); mLowCtrl->Show(!mbCenterAndWidth); mHighCtrl->Show(!mbCenterAndWidth); ValuesToControls(); GetSizer()->Layout(); // Required so that the layout does not mess up on Windows when changing the format. wxWindowBase::GetSizer()->SetMinSize(wxSize(0, mHeight)); // so that height of toolbar does not change wxWindowBase::GetSizer()->SetSizeHints(this); Updated(); } void SpectralSelectionBar::OnIdle( wxIdleEvent &evt ) { evt.Skip(); auto &project = mProject; const auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; SetFrequencies( selectedRegion.f0(), selectedRegion.f1() ); } void SpectralSelectionBar::OnUpdate(wxCommandEvent &evt) { int index = evt.GetInt(); wxWindow *w = FindFocus(); bool centerFocus = (w && w == mCenterCtrl); bool widthFocus = (w && w == mWidthCtrl); bool lowFocus = (w && w == mLowCtrl); bool highFocus = (w && w == mHighCtrl); evt.Skip(false); // Save formats before recreating the controls so they resize properly wxEventType type = evt.GetEventType(); if (type == EVT_FREQUENCYTEXTCTRL_UPDATED) { NumericTextCtrl *frequencyCtrl = (mbCenterAndWidth ? mCenterCtrl : mLowCtrl); auto frequencyFormatName = frequencyCtrl->GetBuiltinName(index); if (mListener) mListener->SSBL_SetFrequencySelectionFormatName(frequencyFormatName); } else if (mbCenterAndWidth && type == EVT_BANDWIDTHTEXTCTRL_UPDATED) { auto bandwidthFormatName = mWidthCtrl->GetBuiltinName(index); if (mListener) mListener->SSBL_SetBandwidthSelectionFormatName(bandwidthFormatName); } // ReCreateButtons() will get rid of our sizers and controls // so reset pointers first. mCenterCtrl = mWidthCtrl = NULL; mLowCtrl = mHighCtrl = NULL; ReCreateButtons(); ValuesToControls(); if (centerFocus) { mCenterCtrl->SetFocus(); } else if (widthFocus) { mWidthCtrl->SetFocus(); } else if (lowFocus) { mLowCtrl->SetFocus(); } else if (highFocus) { mHighCtrl->SetFocus(); } Updated(); } void SpectralSelectionBar::ValuesToControls() { if (mbCenterAndWidth) { mCenterCtrl->SetValue(mCenter); mWidthCtrl->SetValue(mWidth); } else { //Bug 1633 //The controls may not be able to show mHigh, e.g. //if set to Hz, and in that case we should either show invalid //or 'do the best we can' and truncate. //If we set bounds we instead clip to the mHigh to mLow, //So no SetBounds, for now. //SetBounds(); mLowCtrl->SetValue(mLow); mHighCtrl->SetValue(mHigh); } } void SpectralSelectionBar::SetBounds() { if (mHigh >= 0) mLowCtrl->SetMaxValue(mHigh); else mLowCtrl->ResetMaxValue(); if (mLow >= 0) mHighCtrl->SetMinValue(mLow); else mHighCtrl->ResetMinValue(); } void SpectralSelectionBar::SetFrequencies(double bottom, double top) { if ( mLow != bottom || mHigh != top ) { mLow = bottom; mHigh = top; if (bottom > 0 && top >= bottom) mWidth = log(top / bottom), mCenter = sqrt(top * bottom); else mWidth = mCenter = -1.0; ValuesToControls(); } } void SpectralSelectionBar::SetFrequencySelectionFormatName(const NumericFormatSymbol & formatName) { NumericTextCtrl *frequencyCtrl = (mbCenterAndWidth ? mCenterCtrl : mLowCtrl); bool changed = frequencyCtrl->SetFormatName(formatName); // Test first whether changed, to avoid infinite recursion from OnUpdate if (changed) { wxCommandEvent e(EVT_FREQUENCYTEXTCTRL_UPDATED); e.SetInt(frequencyCtrl->GetFormatIndex()); OnUpdate(e); } } void SpectralSelectionBar::SetBandwidthSelectionFormatName(const NumericFormatSymbol & formatName) { if (mbCenterAndWidth) { bool changed = mWidthCtrl->SetFormatName(formatName); // Test first whether changed, to avoid infinite recursion from OnUpdate if (changed) { wxCommandEvent e(EVT_BANDWIDTHTEXTCTRL_UPDATED); e.SetInt(mWidthCtrl->GetFormatIndex()); OnUpdate(e); } } } static RegisteredToolbarFactory factory{ SpectralSelectionBarID, []( AudacityProject &project ){ return ToolBar::Holder{ safenew SpectralSelectionBar{ project } }; } }; #endif // #ifdef EXPERIMENTAL_SPECTRAL_EDITING