diff --git a/src/LabelTrack.h b/src/LabelTrack.h index 63b3621b4..b1a037017 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -21,6 +21,7 @@ class wxTextFile; class AudacityProject; class DirManager; +class NotifyingSelectedRegion; class TimeWarper; struct LabelTrackHit; diff --git a/src/ProjectSelectionManager.cpp b/src/ProjectSelectionManager.cpp index 49c68980a..581146c49 100644 --- a/src/ProjectSelectionManager.cpp +++ b/src/ProjectSelectionManager.cpp @@ -58,7 +58,7 @@ bool ProjectSelectionManager::SnapSelection() auto snapTo = settings.GetSnapTo(); if (snapTo != SNAP_OFF) { auto &viewInfo = ViewInfo::Get( project ); - SelectedRegion &selectedRegion = viewInfo.selectedRegion; + auto &selectedRegion = viewInfo.selectedRegion; NumericConverter nc(NumericConverter::TIME, settings.GetSelectionFormat(), 0, settings.GetRate()); const bool nearest = (snapTo == SNAP_NEAREST); diff --git a/src/TrackArtist.h b/src/TrackArtist.h index 50705a6f0..50e97a3b3 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -128,7 +128,7 @@ public: bool findNotesQuantizeOld; #endif - SelectedRegion *pSelectedRegion{}; + const SelectedRegion *pSelectedRegion{}; ZoomInfo *pZoomInfo{}; bool drawEnvelope{ false }; diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index d72c363bc..770db6eac 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -851,7 +851,8 @@ void TrackPanel::DrawTracks(wxDC * dc) const wxRect clip = GetRect(); - mTrackArtist->pSelectedRegion = &mViewInfo->selectedRegion; + const SelectedRegion &sr = mViewInfo->selectedRegion; + mTrackArtist->pSelectedRegion = &sr; mTrackArtist->pZoomInfo = mViewInfo; TrackPanelDrawingContext context { *dc, Target(), mLastMouseState, mTrackArtist.get() diff --git a/src/ViewInfo.cpp b/src/ViewInfo.cpp index 7d9cfb55c..e9597ea54 100644 --- a/src/ViewInfo.cpp +++ b/src/ViewInfo.cpp @@ -21,6 +21,119 @@ Paul Licameli #include "prefs/TracksBehaviorsPrefs.h" #include "xml/XMLWriter.h" +wxDEFINE_EVENT( EVT_SELECTED_REGION_CHANGE, SelectedRegionEvent ); + +SelectedRegionEvent::SelectedRegionEvent( + wxEventType commandType, NotifyingSelectedRegion *pReg ) +: wxEvent{ 0, commandType } +, pRegion{ pReg } +{} + +wxEvent *SelectedRegionEvent::Clone() const +{ + return safenew SelectedRegionEvent{ *this }; +} + +NotifyingSelectedRegion& NotifyingSelectedRegion::operator = +( const SelectedRegion &other ) +{ + if ( mRegion != other ) { + mRegion = other; + Notify(); + } + return *this; +} + +bool NotifyingSelectedRegion::setTimes(double t0, double t1) +{ + bool result = false; + if ( mRegion.t0() != t0 || mRegion.t1() != t1 ) { + result = mRegion.setTimes( t0, t1 ); + Notify(); + } + return result; +} + +bool NotifyingSelectedRegion::setT0(double t, bool maySwap) +{ + bool result = false; + if ( mRegion.t0() != t ) { + result = mRegion.setT0( t, maySwap ); + Notify(); + } + return result; +} + +bool NotifyingSelectedRegion::setT1(double t, bool maySwap) +{ + bool result = false; + if ( mRegion.t1() != t ) { + result = mRegion.setT1( t, maySwap ); + Notify(); + } + return result; +} + +void NotifyingSelectedRegion::collapseToT0() +{ + if ( mRegion.t0() != mRegion.t1() ) { + mRegion.collapseToT0(); + Notify(); + } +} + +void NotifyingSelectedRegion::collapseToT1() +{ + if ( mRegion.t0() != mRegion.t1() ) { + mRegion.collapseToT1(); + Notify(); + } +} + +void NotifyingSelectedRegion::move(double delta) +{ + if (delta != 0) { + mRegion.move( delta ); + Notify(); + } +} + +bool NotifyingSelectedRegion::setFrequencies(double f0, double f1) +{ + bool result = false; + if ( mRegion.f0() != f0 || mRegion.f1() != f1 ) { + result = mRegion.setFrequencies( f0, f1 ); + Notify(); + } + return result; +} + +bool NotifyingSelectedRegion::setF0(double f, bool maySwap) +{ + bool result = false; + if ( mRegion.f0() != f ) { + result = mRegion.setF0( f, maySwap ); + Notify(); + } + return result; +} + +bool NotifyingSelectedRegion::setF1(double f, bool maySwap) +{ + bool result = false; + if ( mRegion.f1() != f ) { + result = mRegion.setF1( f, maySwap ); + Notify(); + } + return result; +} + +void NotifyingSelectedRegion::Notify() +{ + SelectedRegionEvent evt{ EVT_SELECTED_REGION_CHANGE, this }; + ProcessEvent( evt ); +} + static const AudacityProject::AttachedObjects::RegisteredFactory key{ []( AudacityProject &project ) { auto result = diff --git a/src/ViewInfo.h b/src/ViewInfo.h index 4b5dab681..ca34c8205 100644 --- a/src/ViewInfo.h +++ b/src/ViewInfo.h @@ -14,11 +14,92 @@ #include #include #include // inherit wxEvtHandler +#include // member variable #include "SelectedRegion.h" #include "MemoryX.h" #include "ZoomInfo.h" // to inherit +class NotifyingSelectedRegion; + +struct SelectedRegionEvent : public wxEvent +{ + SelectedRegionEvent( wxEventType commandType, + NotifyingSelectedRegion *pRegion ); + + wxEvent *Clone() const override; + + wxWeakRef< NotifyingSelectedRegion > pRegion; +}; + +// To do: distinguish time changes from frequency changes perhaps? +wxDECLARE_EXPORTED_EVENT( AUDACITY_DLL_API, + EVT_SELECTED_REGION_CHANGE, SelectedRegionEvent ); + +// This heavyweight wrapper of the SelectedRegion structure emits events +// on mutating operations, that other classes can listen for. +class NotifyingSelectedRegion : public wxEvtHandler +{ +public: + // Expose SelectedRegion's const accessors + double t0 () const { return mRegion.t0(); } + double t1 () const { return mRegion.t1(); } + double f0 () const { return mRegion.f0(); } + double f1 () const { return mRegion.f1(); } + double fc () const { return mRegion.fc(); } + bool isPoint() const { return mRegion.isPoint(); } + double duration() const { return mRegion.duration(); } + + // Writing and reading of persistent fields -- the read is mutating but + // does not emit events + void WriteXMLAttributes + (XMLWriter &xmlFile, + const wxChar *legacyT0Name, const wxChar *legacyT1Name) const + { mRegion.WriteXMLAttributes(xmlFile, legacyT0Name, legacyT1Name); } + + bool HandleXMLAttribute + (const wxChar *attr, const wxChar *value, + const wxChar *legacyT0Name, const wxChar *legacyT1Name) + { return mRegion.HandleXMLAttribute( + attr, value, legacyT0Name, legacyT1Name ); } + + // const-only access allows assignment from this into a SelectedRegion + // or otherwise passing it into a function taking const SelectedRegion& + operator const SelectedRegion & () const { return mRegion; } + + // These are the event-emitting operations + NotifyingSelectedRegion& operator = ( const SelectedRegion &other ); + + // Returns true iff the bounds got swapped + bool setTimes(double t0, double t1); + + // Returns true iff the bounds got swapped + bool setT0(double t, bool maySwap = true); + + // Returns true iff the bounds got swapped + bool setT1(double t, bool maySwap = true); + + void collapseToT0(); + + void collapseToT1(); + + void move(double delta); + + // Returns true iff the bounds got swapped + bool setFrequencies(double f0, double f1); + + // Returns true iff the bounds got swapped + bool setF0(double f, bool maySwap = true); + + // Returns true iff the bounds got swapped + bool setF1(double f, bool maySwap = true); + +private: + void Notify(); + + SelectedRegion mRegion; +}; + // See big pictorial comment in TrackPanel.cpp for explanation of these numbers enum : int { // constants related to y coordinates in the track panel @@ -117,7 +198,7 @@ public: // Current selection - SelectedRegion selectedRegion; + NotifyingSelectedRegion selectedRegion; PlayRegion playRegion; // Scroll info diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index b53a81fdd..a3100d22b 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -1171,14 +1171,14 @@ bool Effect::DoEffect(wxWindow *parent, double projectRate, TrackList *list, TrackFactory *factory, - SelectedRegion *selectedRegion, + NotifyingSelectedRegion &selectedRegion, bool shouldPrompt /* = true */) { - wxASSERT(selectedRegion->duration() >= 0.0); + wxASSERT(selectedRegion.duration() >= 0.0); mOutputTracks.reset(); - mpSelectedRegion = selectedRegion; + mpSelectedRegion = &selectedRegion; mFactory = factory; mProjectRate = projectRate; mTracks = list; @@ -1218,8 +1218,8 @@ bool Effect::DoEffect(wxWindow *parent, newTrack->SetSelected(true); } - mT0 = selectedRegion->t0(); - mT1 = selectedRegion->t1(); + mT0 = selectedRegion.t0(); + mT1 = selectedRegion.t1(); if (mT1 > mT0) { // there is a selection: let's fit in there... @@ -1237,8 +1237,8 @@ bool Effect::DoEffect(wxWindow *parent, : NumericConverter::DefaultSelectionFormat(); #ifdef EXPERIMENTAL_SPECTRAL_EDITING - mF0 = selectedRegion->f0(); - mF1 = selectedRegion->f1(); + mF0 = selectedRegion.f0(); + mF1 = selectedRegion.f1(); wxArrayString Names; if( mF0 != SelectedRegion::UndefinedFrequency ) Names.push_back(wxT("control-f0")); @@ -1280,7 +1280,7 @@ bool Effect::DoEffect(wxWindow *parent, if (returnVal && (mT1 >= mT0 )) { - selectedRegion->setTimes(mT0, mT1); + selectedRegion.setTimes(mT0, mT1); } success = returnVal; @@ -1289,10 +1289,11 @@ bool Effect::DoEffect(wxWindow *parent, bool Effect::Delegate( Effect &delegate, wxWindow *parent, bool shouldPrompt) { - SelectedRegion region{ mT0, mT1 }; + NotifyingSelectedRegion region; + region.setTimes( mT0, mT1 ); return delegate.DoEffect( parent, mProjectRate, mTracks, mFactory, - ®ion, shouldPrompt ); + region, shouldPrompt ); } // All legacy effects should have this overridden diff --git a/src/effects/Effect.h b/src/effects/Effect.h index 92cbd0baa..d4a2e9db8 100644 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -43,6 +43,7 @@ class AudacityCommand; class AudacityProject; class LabelTrack; +class NotifyingSelectedRegion; class ProgressDialog; class SelectedRegion; class EffectUIHost; @@ -257,7 +258,7 @@ class AUDACITY_DLL_API Effect /* not final */ : public wxEvtHandler, // have the "selected" flag set to true, which is consistent with // Audacity's standard UI. /* not virtual */ bool DoEffect(wxWindow *parent, double projectRate, TrackList *list, - TrackFactory *factory, SelectedRegion *selectedRegion, + TrackFactory *factory, NotifyingSelectedRegion &selectedRegion, bool shouldPrompt = true); bool Delegate( Effect &delegate, wxWindow *parent, bool shouldPrompt); @@ -459,7 +460,7 @@ protected: double mProjectRate; // Sample rate of the project - NEW tracks should // be created with this rate... double mSampleRate; - SelectedRegion *mpSelectedRegion{}; + wxWeakRef mpSelectedRegion{}; TrackFactory *mFactory; const TrackList *inputTracks() const { return mTracks; } std::shared_ptr mOutputTracks; // used only if CopyInputTracks() is called. diff --git a/src/effects/EffectManager.cpp b/src/effects/EffectManager.cpp index 107cef40b..881e57ee9 100644 --- a/src/effects/EffectManager.cpp +++ b/src/effects/EffectManager.cpp @@ -166,7 +166,7 @@ void EffectManager::UnregisterEffect(const PluginID & ID) EffectManager & em = EffectManager::Get(); success = em.DoEffect(ID, &window, rate, - &tracks, &trackFactory, &selectedRegion, + &tracks, &trackFactory, selectedRegion, (flags & EffectManager::kConfigured) == 0); if (!success) @@ -239,7 +239,7 @@ bool EffectManager::DoEffect(const PluginID & ID, double projectRate, TrackList *list, TrackFactory *factory, - SelectedRegion *selectedRegion, + NotifyingSelectedRegion &selectedRegion, bool shouldPrompt /* = true */) { diff --git a/src/effects/EffectManager.h b/src/effects/EffectManager.h index 584e94068..60e0e9ffc 100644 --- a/src/effects/EffectManager.h +++ b/src/effects/EffectManager.h @@ -41,6 +41,8 @@ class EffectRack; class AudacityCommand; +class NotifyingSelectedRegion; + class AUDACITY_DLL_API EffectManager { public: @@ -88,7 +90,7 @@ public: double projectRate, TrackList *list, TrackFactory *factory, - SelectedRegion *selectedRegion, + NotifyingSelectedRegion &selectedRegion, bool shouldPrompt = true); wxString GetEffectFamilyName(const PluginID & ID); diff --git a/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp b/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp index 950b92b67..cc7717b33 100644 --- a/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp +++ b/src/tracks/labeltrack/ui/LabelGlyphHandle.cpp @@ -124,7 +124,7 @@ LabelGlyphHandle::~LabelGlyphHandle() void LabelGlyphHandle::HandleGlyphClick (LabelTrackHit &hit, const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *WXUNUSED(newSel)) + NotifyingSelectedRegion &WXUNUSED(newSel)) { if (evt.ButtonDown()) { @@ -188,7 +188,7 @@ UIHandle::Result LabelGlyphHandle::Click auto &viewInfo = ViewInfo::Get( *pProject ); HandleGlyphClick( - *mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion); + *mpHit, event, mRect, viewInfo, viewInfo.selectedRegion); if (! mpHit->mIsAdjustingLabel ) { @@ -278,7 +278,7 @@ bool LabelGlyphHandle::HandleGlyphDragRelease (AudacityProject &project, LabelTrackHit &hit, const wxMouseEvent & evt, wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *newSel) + NotifyingSelectedRegion &newSel) { const auto pTrack = mpLT; const auto &mLabels = pTrack->GetLabels(); @@ -340,7 +340,7 @@ bool LabelGlyphHandle::HandleGlyphDragRelease auto selIndex = view.GetSelectedIndex( project ); //Set the selection region to be equal to //the NEW size of the label. - *newSel = mLabels[ selIndex ].selectedRegion; + newSel = mLabels[ selIndex ].selectedRegion; } pTrack->SortLabels(); } @@ -356,7 +356,7 @@ UIHandle::Result LabelGlyphHandle::Drag const wxMouseEvent &event = evt.event; auto &viewInfo = ViewInfo::Get( *pProject ); HandleGlyphDragRelease( - *pProject, *mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion); + *pProject, *mpHit, event, mRect, viewInfo, viewInfo.selectedRegion); // Refresh all so that the change of selection is redrawn in all tracks return result | RefreshCode::RefreshAll | RefreshCode::DrawOverlays; @@ -377,7 +377,7 @@ UIHandle::Result LabelGlyphHandle::Release const wxMouseEvent &event = evt.event; auto &viewInfo = ViewInfo::Get( *pProject ); if (HandleGlyphDragRelease( - *pProject, *mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion)) { + *pProject, *mpHit, event, mRect, viewInfo, viewInfo.selectedRegion)) { ProjectHistory::Get( *pProject ).PushState(_("Modified Label"), _("Label Edit"), UndoPush::CONSOLIDATE); diff --git a/src/tracks/labeltrack/ui/LabelGlyphHandle.h b/src/tracks/labeltrack/ui/LabelGlyphHandle.h index 91be73ec2..93e501e95 100644 --- a/src/tracks/labeltrack/ui/LabelGlyphHandle.h +++ b/src/tracks/labeltrack/ui/LabelGlyphHandle.h @@ -16,7 +16,7 @@ Paul Licameli split from TrackPanel.cpp class wxMouseState; class LabelTrack; class LabelTrackEvent; -class SelectedRegion; +class NotifyingSelectedRegion; class ZoomInfo; /// mEdge: @@ -92,12 +92,12 @@ private: void HandleGlyphClick (LabelTrackHit &hit, const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *newSel); + NotifyingSelectedRegion &newSel); bool HandleGlyphDragRelease (AudacityProject &project, LabelTrackHit &hit, const wxMouseEvent & evt, wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *newSel); + NotifyingSelectedRegion &newSel); void MayAdjustLabel ( LabelTrackHit &hit, diff --git a/src/tracks/labeltrack/ui/LabelTextHandle.cpp b/src/tracks/labeltrack/ui/LabelTextHandle.cpp index f3d52474f..832bb0d18 100644 --- a/src/tracks/labeltrack/ui/LabelTextHandle.cpp +++ b/src/tracks/labeltrack/ui/LabelTextHandle.cpp @@ -78,7 +78,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject & , const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *newSel) + NotifyingSelectedRegion &newSel) { auto pTrack = mpLT.lock(); if (!pTrack) @@ -94,7 +94,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject & if ( selIndex != -1 ) { const auto &mLabels = pTrack->GetLabels(); const auto &labelStruct = mLabels[ selIndex ]; - *newSel = labelStruct.selectedRegion; + newSel = labelStruct.selectedRegion; if (evt.LeftDown()) { // Find the NEW drag end @@ -135,7 +135,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject & if (!LabelTrackView::OverTextBox(&labelStruct, evt.m_x, evt.m_y)) view.SetSelectedIndex( -1 ); double t = zoomInfo.PositionToTime(evt.m_x, r.x); - *newSel = SelectedRegion(t, t); + newSel = SelectedRegion(t, t); } #endif } @@ -143,7 +143,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject & if (evt.MiddleDown()) { // Paste text, making a NEW label if none is selected. wxTheClipboard->UsePrimarySelection(true); - view.PasteSelectedText(project, newSel->t0(), newSel->t1()); + view.PasteSelectedText(project, newSel.t0(), newSel.t1()); wxTheClipboard->UsePrimarySelection(false); } #endif @@ -169,7 +169,7 @@ UIHandle::Result LabelTextHandle::Click mSelectedRegion = viewInfo.selectedRegion; HandleTextClick( *pProject, - event, evt.rect, viewInfo, &viewInfo.selectedRegion ); + event, evt.rect, viewInfo, viewInfo.selectedRegion ); { // IF the user clicked a label, THEN select all other tracks by Label diff --git a/src/tracks/labeltrack/ui/LabelTextHandle.h b/src/tracks/labeltrack/ui/LabelTextHandle.h index d87e6d6af..6446ee16d 100644 --- a/src/tracks/labeltrack/ui/LabelTextHandle.h +++ b/src/tracks/labeltrack/ui/LabelTextHandle.h @@ -16,6 +16,7 @@ Paul Licameli split from TrackPanel.cpp class wxMouseState; class LabelTrack; +class NotifyingSelectedRegion; class SelectionStateChanger; class ZoomInfo; @@ -59,7 +60,7 @@ private: void HandleTextClick (AudacityProject &project, const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo, - SelectedRegion *newSel); + NotifyingSelectedRegion &newSel); void HandleTextDragRelease( AudacityProject &project, const wxMouseEvent & evt); diff --git a/src/tracks/labeltrack/ui/LabelTrackView.cpp b/src/tracks/labeltrack/ui/LabelTrackView.cpp index 1af764dcc..cea361733 100644 --- a/src/tracks/labeltrack/ui/LabelTrackView.cpp +++ b/src/tracks/labeltrack/ui/LabelTrackView.cpp @@ -1336,7 +1336,7 @@ unsigned LabelTrackView::Char( /// KeyEvent is called for every keypress when over the label track. bool LabelTrackView::DoKeyDown( - AudacityProject &project, SelectedRegion &newSel, wxKeyEvent & event) + AudacityProject &project, NotifyingSelectedRegion &newSel, wxKeyEvent & event) { // Only track true changes to the label bool updated = false; @@ -1564,7 +1564,7 @@ bool LabelTrackView::DoKeyDown( /// OnChar is called for incoming characters -- that's any keypress not handled /// by OnKeyDown. bool LabelTrackView::DoChar( - AudacityProject &project, SelectedRegion &WXUNUSED(newSel), + AudacityProject &project, NotifyingSelectedRegion &WXUNUSED(newSel), wxKeyEvent & event) { // Check for modifiers and only allow shift. diff --git a/src/tracks/labeltrack/ui/LabelTrackView.h b/src/tracks/labeltrack/ui/LabelTrackView.h index ad85b58ba..75c90217d 100644 --- a/src/tracks/labeltrack/ui/LabelTrackView.h +++ b/src/tracks/labeltrack/ui/LabelTrackView.h @@ -20,6 +20,7 @@ class LabelStruct; class LabelTrack; struct LabelTrackEvent; struct LabelTrackHit; +class NotifyingSelectedRegion; class SelectedRegion; struct TrackPanelDrawingContext; class ZoomInfo; @@ -54,9 +55,9 @@ public: bool DoCaptureKey( AudacityProject &project, wxKeyEvent &event ); bool DoKeyDown( - AudacityProject &project, SelectedRegion &sel, wxKeyEvent & event); + AudacityProject &project, NotifyingSelectedRegion &sel, wxKeyEvent & event); bool DoChar( - AudacityProject &project, SelectedRegion &sel, wxKeyEvent & event); + AudacityProject &project, NotifyingSelectedRegion &sel, wxKeyEvent & event); //This returns the index of the label we just added. int AddLabel(const SelectedRegion ®ion,