1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 ViewInfo.cpp
6 
7 Paul Licameli
8 
9 **********************************************************************/
10 
11 #include "ViewInfo.h"
12 
13 
14 
15 #include <algorithm>
16 #include "XMLAttributeValueView.h"
17 
18 #include "Prefs.h"
19 #include "Project.h"
20 #include "XMLWriter.h"
21 
22 wxDEFINE_EVENT( EVT_SELECTED_REGION_CHANGE, SelectedRegionEvent );
23 
SelectedRegionEvent(wxEventType commandType,NotifyingSelectedRegion * pReg)24 SelectedRegionEvent::SelectedRegionEvent(
25    wxEventType commandType, NotifyingSelectedRegion *pReg )
26 : wxEvent{ 0, commandType }
27 , pRegion{ pReg }
28 {}
29 
Clone() const30 wxEvent *SelectedRegionEvent::Clone() const
31 {
32    return safenew SelectedRegionEvent{ *this };
33 }
34 
35 XMLMethodRegistryBase::Mutators<NotifyingSelectedRegion>
Mutators(const char * legacyT0Name,const char * legacyT1Name)36 NotifyingSelectedRegion::Mutators(
37    const char *legacyT0Name, const char *legacyT1Name)
38 {
39    XMLMethodRegistryBase::Mutators<NotifyingSelectedRegion> results;
40    // Get serialization methods of contained SelectedRegion, and wrap each
41    for (auto &delegate: SelectedRegion::Mutators(legacyT0Name, legacyT1Name)) {
42       results.emplace_back(
43          delegate.first,
44          [fn = std::move(delegate.second)](auto &region, auto value) {
45             fn( region.mRegion, value );
46             region.Notify( true );
47          }
48       );
49    }
50    return results;
51 }
52 
operator =(const SelectedRegion & other)53 NotifyingSelectedRegion& NotifyingSelectedRegion::operator =
54 ( const SelectedRegion &other )
55 {
56    if ( mRegion != other ) {
57       mRegion = other;
58       Notify();
59    }
60    return *this;
61 }
62 
setTimes(double t0,double t1)63 bool NotifyingSelectedRegion::setTimes(double t0, double t1)
64 {
65    bool result = false;
66    if ( mRegion.t0() != t0 || mRegion.t1() != t1 ) {
67       result = mRegion.setTimes( t0, t1 );
68       Notify();
69    }
70    return result;
71 }
72 
setT0(double t,bool maySwap)73 bool NotifyingSelectedRegion::setT0(double t, bool maySwap)
74 {
75    bool result = false;
76    if ( mRegion.t0() != t ) {
77       result = mRegion.setT0( t, maySwap );
78       Notify();
79    }
80    return result;
81 }
82 
setT1(double t,bool maySwap)83 bool NotifyingSelectedRegion::setT1(double t, bool maySwap)
84 {
85    bool result = false;
86    if ( mRegion.t1() != t ) {
87       result = mRegion.setT1( t, maySwap );
88       Notify();
89    }
90    return result;
91 }
92 
collapseToT0()93 void NotifyingSelectedRegion::collapseToT0()
94 {
95    if ( mRegion.t0() !=  mRegion.t1() ) {
96       mRegion.collapseToT0();
97       Notify();
98    }
99 }
100 
collapseToT1()101 void NotifyingSelectedRegion::collapseToT1()
102 {
103    if ( mRegion.t0() !=  mRegion.t1() ) {
104       mRegion.collapseToT1();
105       Notify();
106    }
107 }
108 
move(double delta)109 void NotifyingSelectedRegion::move(double delta)
110 {
111    if (delta != 0) {
112       mRegion.move( delta );
113       Notify();
114    }
115 }
116 
setFrequencies(double f0,double f1)117 bool NotifyingSelectedRegion::setFrequencies(double f0, double f1)
118 {
119    bool result = false;
120    if ( mRegion.f0() != f0 || mRegion.f1() != f1 ) {
121       result = mRegion.setFrequencies( f0, f1 );
122       Notify();
123    }
124    return result;
125 }
126 
setF0(double f,bool maySwap)127 bool NotifyingSelectedRegion::setF0(double f, bool maySwap)
128 {
129    bool result = false;
130    if ( mRegion.f0() != f ) {
131       result = mRegion.setF0( f, maySwap );
132       Notify();
133    }
134    return result;
135 }
136 
setF1(double f,bool maySwap)137 bool NotifyingSelectedRegion::setF1(double f, bool maySwap)
138 {
139    bool result = false;
140    if ( mRegion.f1() != f ) {
141       result = mRegion.setF1( f, maySwap );
142       Notify();
143    }
144    return result;
145 }
146 
Notify(bool delayed)147 void NotifyingSelectedRegion::Notify( bool delayed )
148 {
149    SelectedRegionEvent evt{ EVT_SELECTED_REGION_CHANGE, this };
150    if ( delayed )
151       QueueEvent( evt.Clone() );
152    else
153       ProcessEvent( evt );
154 }
155 
156 wxDEFINE_EVENT( EVT_PLAY_REGION_CHANGE, PlayRegionEvent );
157 
PlayRegionEvent(wxEventType commandType,PlayRegion * pReg)158 PlayRegionEvent::PlayRegionEvent(
159    wxEventType commandType, PlayRegion *pReg )
160 : wxEvent{ 0, commandType }
161 {}
162 
Clone() const163 wxEvent *PlayRegionEvent::Clone() const
164 {
165    return safenew PlayRegionEvent{ *this };
166 }
167 
SetActive(bool active)168 void PlayRegion::SetActive( bool active )
169 {
170    if (mActive != active) {
171       mActive = active;
172       if (mActive) {
173          // Restore values
174          if (mStart != mLastActiveStart || mEnd != mLastActiveEnd) {
175             mStart = mLastActiveStart;
176             mEnd = mLastActiveEnd;
177          }
178       }
179       Notify();
180    }
181 }
182 
SetStart(double start)183 void PlayRegion::SetStart( double start )
184 {
185    if (mStart != start) {
186       if (mActive)
187          mLastActiveStart = start;
188       mStart = start;
189       Notify();
190    }
191 }
192 
SetEnd(double end)193 void PlayRegion::SetEnd( double end )
194 {
195    if (mEnd != end) {
196       if (mActive)
197          mLastActiveEnd = end;
198       mEnd = end;
199       Notify();
200    }
201 }
202 
SetTimes(double start,double end)203 void PlayRegion::SetTimes( double start, double end )
204 {
205    if (mStart != start || mEnd != end) {
206       if (mActive)
207          mLastActiveStart = start, mLastActiveEnd = end;
208       mStart = start, mEnd = end;
209       Notify();
210    }
211 }
212 
SetAllTimes(double start,double end)213 void PlayRegion::SetAllTimes( double start, double end )
214 {
215    SetTimes(start, end);
216    mLastActiveStart = start, mLastActiveEnd = end;
217 }
218 
Clear()219 void PlayRegion::Clear()
220 {
221    SetAllTimes(invalidValue, invalidValue);
222 }
223 
IsClear() const224 bool PlayRegion::IsClear() const
225 {
226    return GetStart() == invalidValue && GetEnd() == invalidValue;
227 }
228 
IsLastActiveRegionClear() const229 bool PlayRegion::IsLastActiveRegionClear() const
230 {
231    return GetLastActiveStart() == invalidValue && GetLastActiveEnd() == invalidValue;
232 }
233 
Order()234 void PlayRegion::Order()
235 {
236    if ( mStart >= 0 && mEnd >= 0 && mStart > mEnd) {
237       std::swap( mStart, mEnd );
238       if (mActive)
239          mLastActiveStart = mStart, mLastActiveEnd = mEnd;
240       Notify();
241    }
242 }
243 
Notify()244 void PlayRegion::Notify()
245 {
246    PlayRegionEvent evt{ EVT_PLAY_REGION_CHANGE, this };
247    ProcessEvent( evt );
248 }
249 
250 const TranslatableString LoopToggleText = XXO("&Loop On/Off");
251 
252 static const AudacityProject::AttachedObjects::RegisteredFactory key{
__anoneaea0bc20202( ) 253    []( AudacityProject &project ) {
254       return std::make_unique<ViewInfo>(0.0, 1.0, ZoomInfo::GetDefaultZoom());
255    }
256 };
257 
Get(AudacityProject & project)258 ViewInfo &ViewInfo::Get( AudacityProject &project )
259 {
260    return project.AttachedObjects::Get< ViewInfo >( key );
261 }
262 
Get(const AudacityProject & project)263 const ViewInfo &ViewInfo::Get( const AudacityProject &project )
264 {
265    return Get( const_cast< AudacityProject & >( project ) );
266 }
267 
ViewInfo(double start,double screenDuration,double pixelsPerSecond)268 ViewInfo::ViewInfo(double start, double screenDuration, double pixelsPerSecond)
269    : ZoomInfo(start, pixelsPerSecond)
270    , selectedRegion()
271    , total(screenDuration)
272    , sbarH(0)
273    , sbarScreen(1)
274    , sbarTotal(1)
275    , sbarScale(1.0)
276    , scrollStep(16)
277    , bUpdateTrackIndicator(true)
278    , bScrollBeyondZero(false)
279 {
280    UpdatePrefs();
281 }
282 
UpdateSelectedPrefs(int id)283 void ViewInfo::UpdateSelectedPrefs( int id )
284 {
285    if (id == UpdateScrollPrefsID())
286       gPrefs->Read(wxT("/GUI/AutoScroll"), &bUpdateTrackIndicator,
287                    true);
288    ZoomInfo::UpdateSelectedPrefs( id );
289 }
290 
UpdatePrefs()291 void ViewInfo::UpdatePrefs()
292 {
293    ZoomInfo::UpdatePrefs();
294 #ifdef EXPERIMENTAL_SCROLLING_LIMITS
295    bScrollBeyondZero = ScrollingPreference.Read();
296 #endif
297    gPrefs->Read(wxT("/GUI/AdjustSelectionEdges"), &bAdjustSelectionEdges,
298       true);
299 
300    UpdateSelectedPrefs( UpdateScrollPrefsID() );
301 }
302 
SetBeforeScreenWidth(wxInt64 beforeWidth,wxInt64 screenWidth,double lowerBoundTime)303 void ViewInfo::SetBeforeScreenWidth(wxInt64 beforeWidth, wxInt64 screenWidth, double lowerBoundTime)
304 {
305    h =
306       std::max(lowerBoundTime,
307          std::min(total - screenWidth / zoom,
308          beforeWidth / zoom));
309 }
310 
WriteXMLAttributes(XMLWriter & xmlFile) const311 void ViewInfo::WriteXMLAttributes(XMLWriter &xmlFile) const
312 // may throw
313 {
314    selectedRegion.WriteXMLAttributes(xmlFile, "sel0", "sel1");
315    xmlFile.WriteAttr(wxT("vpos"), vpos);
316    xmlFile.WriteAttr(wxT("h"), h, 10);
317    xmlFile.WriteAttr(wxT("zoom"), zoom, 10);
318 }
319 
320 //! Construct once at static intialization time to hook project file IO
321 static struct ViewInfo::ProjectFileIORegistration {
322 
323 ProjectFileIORegistry::AttributeReaderEntries entries {
324 [](AudacityProject &project) -> NotifyingSelectedRegion &
__anoneaea0bc20302ViewInfo::ProjectFileIORegistration325 {
326    return ViewInfo::Get(project).selectedRegion;
327 },
328 NotifyingSelectedRegion::Mutators("sel0", "sel1")
329 };
330 
331 ProjectFileIORegistry::AttributeReaderEntries entries2 {
332 // Just a pointer to function, but needing overload resolution as non-const:
333 (ViewInfo& (*)(AudacityProject &)) &ViewInfo::Get,
334 {
__anoneaea0bc20402ViewInfo::ProjectFileIORegistration335    { "vpos", [](auto &viewInfo, auto value){
336       viewInfo.vpos = value.Get(viewInfo.vpos);
337       // Note that (other than in import of old .aup files) there is no other
338       // reassignment of vpos, except in handling the vertical scroll.
339    } },
__anoneaea0bc20502ViewInfo::ProjectFileIORegistration340    { "h", [](auto &viewInfo, auto value){
341       viewInfo.h = value.Get(viewInfo.h);
342    } },
__anoneaea0bc20602ViewInfo::ProjectFileIORegistration343    { "zoom", [](auto &viewInfo, auto value){
344       viewInfo.zoom = value.Get(viewInfo.zoom);
345    } },
346 } };
347 
348 } projectFileIORegistration;
349 
UpdateScrollPrefsID()350 int ViewInfo::UpdateScrollPrefsID()
351 {
352    return 10000;
353 }
354 
355 static ProjectFileIORegistry::AttributeWriterEntry entry {
__anoneaea0bc20702()356 [](const AudacityProject &project, XMLWriter &xmlFile){
357    ViewInfo::Get(project).WriteXMLAttributes(xmlFile);
358 }
359 };
360 
361 BoolSetting ScrollingPreference{ L"/GUI/ScrollBeyondZero", false };
362