Audacity  3.0.3
LabelTrack.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  LabelTrack.cpp
6 
7  Dominic Mazzoni
8  James Crook
9  Jun Wan
10 
11 *******************************************************************//****************************************************************//*******************************************************************/
30 
31 
32 #include "LabelTrack.h"
33 
34 #include "tracks/ui/TrackView.h"
36 
37 
38 
39 #include <stdio.h>
40 #include <algorithm>
41 #include <limits.h>
42 #include <float.h>
43 
44 #include <wx/log.h>
45 #include <wx/tokenzr.h>
46 
47 #include "Prefs.h"
48 #include "Project.h"
50 
51 #include "effects/TimeWarper.h"
53 
54 wxDEFINE_EVENT(EVT_LABELTRACK_ADDITION, LabelTrackEvent);
55 wxDEFINE_EVENT(EVT_LABELTRACK_DELETION, LabelTrackEvent);
56 wxDEFINE_EVENT(EVT_LABELTRACK_PERMUTED, LabelTrackEvent);
57 wxDEFINE_EVENT(EVT_LABELTRACK_SELECTION, LabelTrackEvent);
58 
60  wxT( "labeltrack" ),
61  []( AudacityProject &project ){
62  auto &tracks = TrackList::Get( project );
63  auto result = tracks.Add(std::make_shared<LabelTrack>());
64  TrackView::Get( *result );
65  TrackControls::Get( *result );
66  return result;
67  }
68 };
69 
71  Track(),
72  mClipLen(0.0),
73  miLastLabel(-1)
74 {
75  SetDefaultName(_("Label Track"));
77 }
78 
80  Track(orig),
81  mClipLen(0.0)
82 {
83  for (auto &original: orig.mLabels) {
84  LabelStruct l { original.selectedRegion, original.title };
85  mLabels.push_back(l);
86  }
87 }
88 
90 {
91  auto pNewTrack = std::make_shared<LabelTrack>();
92  pNewTrack->Paste(0.0, this);
93  return pNewTrack;
94 }
95 
96 template<typename IntervalType>
97 static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
98 {
99  return {
100  label.getT0(), label.getT1(),
101  std::make_unique<LabelTrack::IntervalData>( index ) };
102 }
103 
104 auto LabelTrack::MakeInterval( size_t index ) const -> ConstInterval
105 {
106  return DoMakeInterval<ConstInterval>(mLabels[index], index);
107 }
108 
109 auto LabelTrack::MakeInterval( size_t index ) -> Interval
110 {
111  return DoMakeInterval<Interval>(mLabels[index], index);
112 }
113 
114 template< typename Container, typename LabelTrack >
115 static Container DoMakeIntervals(LabelTrack &track)
116 {
117  Container result;
118  for (size_t ii = 0, nn = track.GetNumLabels(); ii < nn; ++ii)
119  result.emplace_back( track.MakeInterval( ii ) );
120  return result;
121 }
122 
124 {
125  return DoMakeIntervals<ConstIntervals>(*this);
126 }
127 
129 {
130  return DoMakeIntervals<Intervals>(*this);
131 }
132 
133 void LabelTrack::SetLabel( size_t iLabel, const LabelStruct &newLabel )
134 {
135  if( iLabel >= mLabels.size() ) {
136  wxASSERT( false );
137  mLabels.resize( iLabel + 1 );
138  }
139  mLabels[ iLabel ] = newLabel;
140 }
141 
142 LabelTrack::~LabelTrack()
143 {
144 }
145 
146 void LabelTrack::SetOffset(double dOffset)
147 {
148  for (auto &labelStruct: mLabels)
149  labelStruct.selectedRegion.move(dOffset);
150 }
151 
152 void LabelTrack::Clear(double b, double e)
153 {
154  // May DELETE labels, so use subscripts to iterate
155  for (size_t i = 0; i < mLabels.size(); ++i) {
156  auto &labelStruct = mLabels[i];
157  LabelStruct::TimeRelations relation =
158  labelStruct.RegionRelation(b, e, this);
159  if (relation == LabelStruct::BEFORE_LABEL)
160  labelStruct.selectedRegion.move(- (e-b));
161  else if (relation == LabelStruct::SURROUNDS_LABEL) {
162  DeleteLabel( i );
163  --i;
164  }
165  else if (relation == LabelStruct::ENDS_IN_LABEL)
166  labelStruct.selectedRegion.setTimes(
167  b,
168  labelStruct.getT1() - (e - b));
169  else if (relation == LabelStruct::BEGINS_IN_LABEL)
170  labelStruct.selectedRegion.setT1(b);
171  else if (relation == LabelStruct::WITHIN_LABEL)
172  labelStruct.selectedRegion.moveT1( - (e-b));
173  }
174 }
175 
176 #if 0
177 //used when we want to use clear only on the labels
178 bool LabelTrack::SplitDelete(double b, double e)
179 {
180  // May DELETE labels, so use subscripts to iterate
181  for (size_t i = 0, len = mLabels.size(); i < len; ++i) {
182  auto &labelStruct = mLabels[i];
183  LabelStruct::TimeRelations relation =
184  labelStruct.RegionRelation(b, e, this);
185  if (relation == LabelStruct::SURROUNDS_LABEL) {
186  DeleteLabel(i);
187  --i;
188  }
189  else if (relation == LabelStruct::WITHIN_LABEL)
190  labelStruct.selectedRegion.moveT1( - (e-b));
191  else if (relation == LabelStruct::ENDS_IN_LABEL)
192  labelStruct.selectedRegion.setT0(e);
193  else if (relation == LabelStruct::BEGINS_IN_LABEL)
194  labelStruct.selectedRegion.setT1(b);
195  }
196 
197  return true;
198 }
199 #endif
200 
201 void LabelTrack::ShiftLabelsOnInsert(double length, double pt)
202 {
203  for (auto &labelStruct: mLabels) {
204  LabelStruct::TimeRelations relation =
205  labelStruct.RegionRelation(pt, pt, this);
206 
207  if (relation == LabelStruct::BEFORE_LABEL)
208  labelStruct.selectedRegion.move(length);
209  else if (relation == LabelStruct::WITHIN_LABEL)
210  labelStruct.selectedRegion.moveT1(length);
211  }
212 }
213 
214 void LabelTrack::ChangeLabelsOnReverse(double b, double e)
215 {
216  for (auto &labelStruct: mLabels) {
217  if (labelStruct.RegionRelation(b, e, this) ==
219  {
220  double aux = b + (e - labelStruct.getT1());
221  labelStruct.selectedRegion.setTimes(
222  aux,
223  e - (labelStruct.getT0() - b));
224  }
225  }
226  SortLabels();
227 }
228 
229 void LabelTrack::ScaleLabels(double b, double e, double change)
230 {
231  for (auto &labelStruct: mLabels) {
232  labelStruct.selectedRegion.setTimes(
233  AdjustTimeStampOnScale(labelStruct.getT0(), b, e, change),
234  AdjustTimeStampOnScale(labelStruct.getT1(), b, e, change));
235  }
236 }
237 
238 double LabelTrack::AdjustTimeStampOnScale(double t, double b, double e, double change)
239 {
240 //t is the time stamp we'll be changing
241 //b and e are the selection start and end
242 
243  if (t < b){
244  return t;
245  }else if (t > e){
246  double shift = (e-b)*change - (e-b);
247  return t + shift;
248  }else{
249  double shift = (t-b)*change - (t-b);
250  return t + shift;
251  }
252 }
253 
254 // Move the labels in the track according to the given TimeWarper.
255 // (If necessary this could be optimised by ignoring labels that occur before a
256 // specified time, as in most cases they don't need to move.)
257 void LabelTrack::WarpLabels(const TimeWarper &warper) {
258  for (auto &labelStruct: mLabels) {
259  labelStruct.selectedRegion.setTimes(
260  warper.Warp(labelStruct.getT0()),
261  warper.Warp(labelStruct.getT1()));
262  }
263 
264  // This should not be needed, assuming the warper is nondecreasing, but
265  // let's not assume too much.
266  SortLabels();
267 }
268 
270  const wxString& aTitle)
271 : selectedRegion(region)
272 , title(aTitle)
273 {
274  updated = false;
275  width = 0;
276  x = 0;
277  x1 = 0;
278  xText = 0;
279  y = 0;
280 }
281 
283  double t0, double t1,
284  const wxString& aTitle)
285 : selectedRegion(region)
286 , title(aTitle)
287 {
288  // Overwrite the times
289  selectedRegion.setTimes(t0, t1);
290 
291  updated = false;
292  width = 0;
293  x = 0;
294  x1 = 0;
295  xText = 0;
296  y = 0;
297 }
298 
300 {
301  bool selected = GetSelected();
302  Track::SetSelected( s );
303  if ( selected != GetSelected() ) {
304  LabelTrackEvent evt{
305  EVT_LABELTRACK_SELECTION, SharedPointer<LabelTrack>(), {}, -1, -1
306  };
307  ProcessEvent( evt );
308  }
309 }
310 
311 double LabelTrack::GetOffset() const
312 {
313  return mOffset;
314 }
315 
317 {
318  if (mLabels.empty())
319  return 0.0;
320  else
321  return mLabels[0].getT0();
322 }
323 
325 {
326  //we need to scan through all the labels, because the last
327  //label might not have the right-most end (if there is overlap).
328  if (mLabels.empty())
329  return 0.0;
330 
331  double end = 0.0;
332  for (auto &labelStruct: mLabels) {
333  const double t1 = labelStruct.getT1();
334  if(t1 > end)
335  end = t1;
336  }
337  return end;
338 }
339 
341 {
342  return std::make_shared<LabelTrack>( *this );
343 }
344 
345 // Adjust label's left or right boundary, depending which is requested.
346 // Return true iff the label flipped.
347 bool LabelStruct::AdjustEdge( int iEdge, double fNewTime)
348 {
349  updated = true;
350  if( iEdge < 0 )
351  return selectedRegion.setT0(fNewTime);
352  else
353  return selectedRegion.setT1(fNewTime);
354 }
355 
356 // We're moving the label. Adjust both left and right edge.
357 void LabelStruct::MoveLabel( int iEdge, double fNewTime)
358 {
359  double fTimeSpan = getDuration();
360 
361  if( iEdge < 0 )
362  {
363  selectedRegion.setTimes(fNewTime, fNewTime+fTimeSpan);
364  }
365  else
366  {
367  selectedRegion.setTimes(fNewTime-fTimeSpan, fNewTime);
368  }
369  updated = true;
370 }
371 
372 LabelStruct LabelStruct::Import(wxTextFile &file, int &index)
373 {
374  SelectedRegion sr;
375  wxString title;
376  static const wxString continuation{ wxT("\\") };
377 
378  wxString firstLine = file.GetLine(index++);
379 
380  {
381  // Assume tab is an impossible character within the exported text
382  // of the label, so can be only a delimiter. But other white space may
383  // be part of the label text.
384  wxStringTokenizer toker { firstLine, wxT("\t") };
385 
386  //get the timepoint of the left edge of the label.
387  auto token = toker.GetNextToken();
388 
389  double t0;
390  if (!Internat::CompatibleToDouble(token, &t0))
391  throw BadFormatException{};
392 
393  token = toker.GetNextToken();
394 
395  double t1;
396  if (!Internat::CompatibleToDouble(token, &t1))
397  //s1 is not a number.
398  t1 = t0; //This is a one-sided label; t1 == t0.
399  else
400  token = toker.GetNextToken();
401 
402  sr.setTimes( t0, t1 );
403 
404  title = token;
405  }
406 
407  // Newer selection fields are written on additional lines beginning with
408  // '\' which is an impossible numerical character that older versions of
409  // audacity will ignore. Test for the presence of such a line and then
410  // parse it if we can.
411 
412  // There may also be additional continuation lines from future formats that
413  // we ignore.
414 
415  // Advance index over all continuation lines first, before we might throw
416  // any exceptions.
417  int index2 = index;
418  while (index < (int)file.GetLineCount() &&
419  file.GetLine(index).StartsWith(continuation))
420  ++index;
421 
422  if (index2 < index) {
423  wxStringTokenizer toker { file.GetLine(index2++), wxT("\t") };
424  auto token = toker.GetNextToken();
425  if (token != continuation)
426  throw BadFormatException{};
427 
428  token = toker.GetNextToken();
429  double f0;
430  if (!Internat::CompatibleToDouble(token, &f0))
431  throw BadFormatException{};
432 
433  token = toker.GetNextToken();
434  double f1;
435  if (!Internat::CompatibleToDouble(token, &f1))
436  throw BadFormatException{};
437 
438  sr.setFrequencies(f0, f1);
439  }
440 
441  return LabelStruct{ sr, title };
442 }
443 
444 void LabelStruct::Export(wxTextFile &file) const
445 {
446  file.AddLine(wxString::Format(wxT("%s\t%s\t%s"),
447  Internat::ToString(getT0(), FLT_DIG),
448  Internat::ToString(getT1(), FLT_DIG),
449  title
450  ));
451 
452  // Do we need more lines?
453  auto f0 = selectedRegion.f0();
454  auto f1 = selectedRegion.f1();
458  return;
459 
460  // Write a \ character at the start of a second line,
461  // so that earlier versions of Audacity ignore it.
462  file.AddLine(wxString::Format(wxT("\\\t%s\t%s"),
463  Internat::ToString(f0, FLT_DIG),
464  Internat::ToString(f1, FLT_DIG)
465  ));
466 
467  // Additional lines in future formats should also start with '\'.
468 }
469 
471  double reg_t0, double reg_t1, const LabelTrack * WXUNUSED(parent)) const
472 -> TimeRelations
473 {
474  bool retainLabels = false;
475 
476  wxASSERT(reg_t0 <= reg_t1);
477  gPrefs->Read(wxT("/GUI/RetainLabels"), &retainLabels);
478 
479  if(retainLabels) {
480 
481  // Desired behavior for edge cases: The length of the selection is smaller
482  // than the length of the label if the selection is within the label or
483  // matching exactly a (region) label.
484 
485  if (reg_t0 < getT0() && reg_t1 > getT1())
486  return SURROUNDS_LABEL;
487  else if (reg_t1 < getT0())
488  return BEFORE_LABEL;
489  else if (reg_t0 > getT1())
490  return AFTER_LABEL;
491 
492  else if (reg_t0 >= getT0() && reg_t0 <= getT1() &&
493  reg_t1 >= getT0() && reg_t1 <= getT1())
494  return WITHIN_LABEL;
495 
496  else if (reg_t0 >= getT0() && reg_t0 <= getT1())
497  return BEGINS_IN_LABEL;
498  else
499  return ENDS_IN_LABEL;
500 
501  } else {
502 
503  // AWD: Desired behavior for edge cases: point labels bordered by the
504  // selection are included within it. Region labels are included in the
505  // selection to the extent that the selection covers them; specifically,
506  // they're not included at all if the selection borders them, and they're
507  // fully included if the selection covers them fully, even if it just
508  // borders their endpoints. This is just one of many possible schemes.
509 
510  // The first test catches bordered point-labels and selected-through
511  // region-labels; move it to third and selection edges become inclusive
512  // WRT point-labels.
513  if (reg_t0 <= getT0() && reg_t1 >= getT1())
514  return SURROUNDS_LABEL;
515  else if (reg_t1 <= getT0())
516  return BEFORE_LABEL;
517  else if (reg_t0 >= getT1())
518  return AFTER_LABEL;
519 
520  // At this point, all point labels should have returned.
521 
522  else if (reg_t0 > getT0() && reg_t0 < getT1() &&
523  reg_t1 > getT0() && reg_t1 < getT1())
524  return WITHIN_LABEL;
525 
526  // Knowing that none of the other relations match simplifies remaining
527  // tests
528  else if (reg_t0 > getT0() && reg_t0 < getT1())
529  return BEGINS_IN_LABEL;
530  else
531  return ENDS_IN_LABEL;
532 
533  }
534 }
535 
537 void LabelTrack::Export(wxTextFile & f) const
538 {
539  // PRL: to do: export other selection fields
540  for (auto &labelStruct: mLabels)
541  labelStruct.Export(f);
542 }
543 
545 void LabelTrack::Import(wxTextFile & in)
546 {
547  int lines = in.GetLineCount();
548 
549  mLabels.clear();
550  mLabels.reserve(lines);
551 
552  //Currently, we expect a tag file to have two values and a label
553  //on each line. If the second token is not a number, we treat
554  //it as a single-value label.
555  bool error = false;
556  for (int index = 0; index < lines;) {
557  try {
558  // Let LabelStruct::Import advance index
559  LabelStruct l { LabelStruct::Import(in, index) };
560  mLabels.push_back(l);
561  }
562  catch(const LabelStruct::BadFormatException&) { error = true; }
563  }
564  if (error)
565  ::AudacityMessageBox( XO("One or more saved labels could not be read.") );
566  SortLabels();
567 }
568 
569 bool LabelTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
570 {
571  if (!wxStrcmp(tag, wxT("label"))) {
572 
573  SelectedRegion selectedRegion;
574  wxString title;
575 
576  // loop through attrs, which is a null-terminated list of
577  // attribute-value pairs
578  while(*attrs) {
579  const wxChar *attr = *attrs++;
580  const wxChar *value = *attrs++;
581 
582  if (!value)
583  break;
584 
585  const wxString strValue = value;
586  // Bug 1905 was about long label strings.
587  if (!XMLValueChecker::IsGoodLongString(strValue))
588  {
589  return false;
590  }
591 
592  if (selectedRegion.HandleXMLAttribute(attr, value, wxT("t"), wxT("t1")))
593  ;
594  else if (!wxStrcmp(attr, wxT("title")))
595  title = strValue;
596 
597  } // while
598 
599  // Handle files created by Audacity 1.1. Labels in Audacity 1.1
600  // did not have separate start- and end-times.
601  // PRL: this superfluous now, given class SelectedRegion's internal
602  // consistency guarantees
603  //if (selectedRegion.t1() < 0)
604  // selectedRegion.collapseToT0();
605 
606  LabelStruct l { selectedRegion, title };
607  mLabels.push_back(l);
608 
609  return true;
610  }
611  else if (!wxStrcmp(tag, wxT("labeltrack"))) {
612  long nValue = -1;
613  while (*attrs) {
614  const wxChar *attr = *attrs++;
615  const wxChar *value = *attrs++;
616 
617  if (!value)
618  return true;
619 
620  const wxString strValue = value;
621  if (this->Track::HandleCommonXMLAttribute(attr, strValue))
622  ;
623  else if (!wxStrcmp(attr, wxT("numlabels")) &&
624  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
625  {
626  if (nValue < 0)
627  {
628  wxLogWarning(wxT("Project shows negative number of labels: %d"), nValue);
629  return false;
630  }
631  mLabels.clear();
632  mLabels.reserve(nValue);
633  }
634  }
635 
636  return true;
637  }
638 
639  return false;
640 }
641 
643 {
644  if (!wxStrcmp(tag, wxT("label")))
645  return this;
646  else
647  return NULL;
648 }
649 
650 void LabelTrack::WriteXML(XMLWriter &xmlFile) const
651 // may throw
652 {
653  int len = mLabels.size();
654 
655  xmlFile.StartTag(wxT("labeltrack"));
656  this->Track::WriteCommonXMLAttributes( xmlFile );
657  xmlFile.WriteAttr(wxT("numlabels"), len);
658 
659  for (auto &labelStruct: mLabels) {
660  xmlFile.StartTag(wxT("label"));
661  labelStruct.getSelectedRegion()
662  .WriteXMLAttributes(xmlFile, wxT("t"), wxT("t1"));
663  // PRL: to do: write other selection fields
664  xmlFile.WriteAttr(wxT("title"), labelStruct.title);
665  xmlFile.EndTag(wxT("label"));
666  }
667 
668  xmlFile.EndTag(wxT("labeltrack"));
669 }
670 
671 Track::Holder LabelTrack::Cut(double t0, double t1)
672 {
673  auto tmp = Copy(t0, t1);
674 
675  Clear(t0, t1);
676 
677  return tmp;
678 }
679 
680 #if 0
681 Track::Holder LabelTrack::SplitCut(double t0, double t1)
682 {
683  // SplitCut() == Copy() + SplitDelete()
684 
685  Track::Holder tmp = Copy(t0, t1);
686 
687  if (!SplitDelete(t0, t1))
688  return {};
689 
690  return tmp;
691 }
692 #endif
693 
694 Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
695 {
696  auto tmp = std::make_shared<LabelTrack>();
697  const auto lt = static_cast<LabelTrack*>(tmp.get());
698 
699  for (auto &labelStruct: mLabels) {
700  LabelStruct::TimeRelations relation =
701  labelStruct.RegionRelation(t0, t1, this);
702  if (relation == LabelStruct::SURROUNDS_LABEL) {
703  LabelStruct l {
704  labelStruct.selectedRegion,
705  labelStruct.getT0() - t0,
706  labelStruct.getT1() - t0,
707  labelStruct.title
708  };
709  lt->mLabels.push_back(l);
710  }
711  else if (relation == LabelStruct::WITHIN_LABEL) {
712  LabelStruct l {
713  labelStruct.selectedRegion,
714  0,
715  t1-t0,
716  labelStruct.title
717  };
718  lt->mLabels.push_back(l);
719  }
720  else if (relation == LabelStruct::BEGINS_IN_LABEL) {
721  LabelStruct l {
722  labelStruct.selectedRegion,
723  0,
724  labelStruct.getT1() - t0,
725  labelStruct.title
726  };
727  lt->mLabels.push_back(l);
728  }
729  else if (relation == LabelStruct::ENDS_IN_LABEL) {
730  LabelStruct l {
731  labelStruct.selectedRegion,
732  labelStruct.getT0() - t0,
733  t1 - t0,
734  labelStruct.title
735  };
736  lt->mLabels.push_back(l);
737  }
738  }
739  lt->mClipLen = (t1 - t0);
740 
741  return tmp;
742 }
743 
744 
745 bool LabelTrack::PasteOver(double t, const Track * src)
746 {
747  auto result = src->TypeSwitch< bool >( [&](const LabelTrack *sl) {
748  int len = mLabels.size();
749  int pos = 0;
750 
751  while (pos < len && mLabels[pos].getT0() < t)
752  pos++;
753 
754  for (auto &labelStruct: sl->mLabels) {
755  LabelStruct l {
756  labelStruct.selectedRegion,
757  labelStruct.getT0() + t,
758  labelStruct.getT1() + t,
759  labelStruct.title
760  };
761  mLabels.insert(mLabels.begin() + pos++, l);
762  }
763 
764  return true;
765  } );
766 
767  if (! result )
768  // THROW_INCONSISTENCY_EXCEPTION; // ?
769  (void)0;// intentionally do nothing
770 
771  return result;
772 }
773 
774 void LabelTrack::Paste(double t, const Track *src)
775 {
776  bool bOk = src->TypeSwitch< bool >( [&](const LabelTrack *lt) {
777  double shiftAmt = lt->mClipLen > 0.0 ? lt->mClipLen : lt->GetEndTime();
778 
779  ShiftLabelsOnInsert(shiftAmt, t);
780  PasteOver(t, src);
781 
782  return true;
783  } );
784 
785  if ( !bOk )
786  // THROW_INCONSISTENCY_EXCEPTION; // ?
787  (void)0;// intentionally do nothing
788 }
789 
790 // This repeats the labels in a time interval a specified number of times.
791 bool LabelTrack::Repeat(double t0, double t1, int n)
792 {
793  // Sanity-check the arguments
794  if (n < 0 || t1 < t0)
795  return false;
796 
797  double tLen = t1 - t0;
798 
799  // Insert space for the repetitions
800  ShiftLabelsOnInsert(tLen * n, t1);
801 
802  // mLabels may resize as we iterate, so use subscripting
803  for (unsigned int i = 0; i < mLabels.size(); ++i)
804  {
805  LabelStruct::TimeRelations relation =
806  mLabels[i].RegionRelation(t0, t1, this);
807  if (relation == LabelStruct::SURROUNDS_LABEL)
808  {
809  // Label is completely inside the selection; duplicate it in each
810  // repeat interval
811  unsigned int pos = i; // running label insertion position in mLabels
812 
813  for (int j = 1; j <= n; j++)
814  {
815  const LabelStruct &label = mLabels[i];
816  LabelStruct l {
817  label.selectedRegion,
818  label.getT0() + j * tLen,
819  label.getT1() + j * tLen,
820  label.title
821  };
822 
823  // Figure out where to insert
824  while (pos < mLabels.size() &&
825  mLabels[pos].getT0() < l.getT0())
826  pos++;
827  mLabels.insert(mLabels.begin() + pos, l);
828  }
829  }
830  else if (relation == LabelStruct::BEGINS_IN_LABEL)
831  {
832  // Label ends inside the selection; ShiftLabelsOnInsert() hasn't touched
833  // it, and we need to extend it through to the last repeat interval
834  mLabels[i].selectedRegion.moveT1(n * tLen);
835  }
836 
837  // Other cases have already been handled by ShiftLabelsOnInsert()
838  }
839 
840  return true;
841 }
842 
843 void LabelTrack::SyncLockAdjust(double oldT1, double newT1)
844 {
845  if (newT1 > oldT1) {
846  // Insert space within the track
847 
848  if (oldT1 > GetEndTime())
849  return;
850 
851  //Clear(oldT1, newT1);
852  ShiftLabelsOnInsert(newT1 - oldT1, oldT1);
853  }
854  else if (newT1 < oldT1) {
855  // Remove from the track
856  Clear(newT1, oldT1);
857  }
858 }
859 
860 
861 void LabelTrack::Silence(double t0, double t1)
862 {
863  int len = mLabels.size();
864 
865  // mLabels may resize as we iterate, so use subscripting
866  for (int i = 0; i < len; ++i) {
867  LabelStruct::TimeRelations relation =
868  mLabels[i].RegionRelation(t0, t1, this);
869  if (relation == LabelStruct::WITHIN_LABEL)
870  {
871  // Split label around the selection
872  const LabelStruct &label = mLabels[i];
873  LabelStruct l {
874  label.selectedRegion,
875  t1,
876  label.getT1(),
877  label.title
878  };
879 
880  mLabels[i].selectedRegion.setT1(t0);
881 
882  // This might not be the right place to insert, but we sort at the end
883  ++i;
884  mLabels.insert(mLabels.begin() + i, l);
885  }
886  else if (relation == LabelStruct::ENDS_IN_LABEL)
887  {
888  // Beginning of label to selection end
889  mLabels[i].selectedRegion.setT0(t1);
890  }
891  else if (relation == LabelStruct::BEGINS_IN_LABEL)
892  {
893  // End of label to selection beginning
894  mLabels[i].selectedRegion.setT1(t0);
895  }
896  else if (relation == LabelStruct::SURROUNDS_LABEL)
897  {
898  DeleteLabel( i );
899  len--;
900  i--;
901  }
902  }
903 
904  SortLabels();
905 }
906 
907 void LabelTrack::InsertSilence(double t, double len)
908 {
909  for (auto &labelStruct: mLabels) {
910  double t0 = labelStruct.getT0();
911  double t1 = labelStruct.getT1();
912  if (t0 >= t)
913  t0 += len;
914 
915  if (t1 >= t)
916  t1 += len;
917  labelStruct.selectedRegion.setTimes(t0, t1);
918  }
919 }
920 
922 {
923  return mLabels.size();
924 }
925 
926 const LabelStruct *LabelTrack::GetLabel(int index) const
927 {
928  return &mLabels[index];
929 }
930 
931 int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
932  const wxString &title)
933 {
934  LabelStruct l { selectedRegion, title };
935 
936  int len = mLabels.size();
937  int pos = 0;
938 
939  while (pos < len && mLabels[pos].getT0() < selectedRegion.t0())
940  pos++;
941 
942  mLabels.insert(mLabels.begin() + pos, l);
943 
944  LabelTrackEvent evt{
945  EVT_LABELTRACK_ADDITION, SharedPointer<LabelTrack>(), title, -1, pos
946  };
947  ProcessEvent( evt );
948 
949  return pos;
950 }
951 
952 void LabelTrack::DeleteLabel(int index)
953 {
954  wxASSERT((index < (int)mLabels.size()));
955  auto iter = mLabels.begin() + index;
956  const auto title = iter->title;
957  mLabels.erase(iter);
958 
959  LabelTrackEvent evt{
960  EVT_LABELTRACK_DELETION, SharedPointer<LabelTrack>(), title, index, -1
961  };
962  ProcessEvent( evt );
963 }
964 
970 {
971  const auto begin = mLabels.begin();
972  const auto nn = (int)mLabels.size();
973  int i = 1;
974  while (true)
975  {
976  // Find the next disorder
977  while (i < nn && mLabels[i - 1].getT0() <= mLabels[i].getT0())
978  ++i;
979  if (i >= nn)
980  break;
981 
982  // Where must element i sink to? At most i - 1, maybe less
983  int j = i - 2;
984  while( (j >= 0) && (mLabels[j].getT0() > mLabels[i].getT0()) )
985  --j;
986  ++j;
987 
988  // Now fix the disorder
989  std::rotate(
990  begin + j,
991  begin + i,
992  begin + i + 1
993  );
994 
995  // Let listeners update their stored indices
996  LabelTrackEvent evt{
997  EVT_LABELTRACK_PERMUTED, SharedPointer<LabelTrack>(),
998  mLabels[j].title, i, j
999  };
1000  ProcessEvent( evt );
1001  }
1002 }
1003 
1004 wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
1005 {
1006  bool firstLabel = true;
1007  wxString retVal;
1008 
1009  for (auto &labelStruct: mLabels) {
1010  if (labelStruct.getT0() >= t0 &&
1011  labelStruct.getT1() <= t1)
1012  {
1013  if (!firstLabel)
1014  retVal += '\t';
1015  firstLabel = false;
1016  retVal += labelStruct.title;
1017  }
1018  }
1019 
1020  return retVal;
1021 }
1022 
1024 {
1025  int i = -1;
1026 
1027  if (!mLabels.empty()) {
1028  int len = (int) mLabels.size();
1029  if (miLastLabel >= 0 && miLastLabel + 1 < len
1030  && currentRegion.t0() == mLabels[miLastLabel].getT0()
1031  && currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
1032  i = miLastLabel + 1;
1033  }
1034  else {
1035  i = 0;
1036  if (currentRegion.t0() < mLabels[len - 1].getT0()) {
1037  while (i < len &&
1038  mLabels[i].getT0() <= currentRegion.t0()) {
1039  i++;
1040  }
1041  }
1042  }
1043  }
1044 
1045  miLastLabel = i;
1046  return i;
1047 }
1048 
1049  int LabelTrack::FindPrevLabel(const SelectedRegion& currentRegion)
1050 {
1051  int i = -1;
1052 
1053  if (!mLabels.empty()) {
1054  int len = (int) mLabels.size();
1055  if (miLastLabel > 0 && miLastLabel < len
1056  && currentRegion.t0() == mLabels[miLastLabel].getT0()
1057  && currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
1058  i = miLastLabel - 1;
1059  }
1060  else {
1061  i = len - 1;
1062  if (currentRegion.t0() > mLabels[0].getT0()) {
1063  while (i >=0 &&
1064  mLabels[i].getT0() >= currentRegion.t0()) {
1065  i--;
1066  }
1067  }
1068  }
1069  }
1070 
1071  miLastLabel = i;
1072  return i;
1073 }
XMLWriter
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:23
LabelStruct::x1
int x1
Pixel position of left hand glyph.
Definition: LabelTrack.h:76
LabelStruct::getDuration
double getDuration() const
Definition: LabelTrack.h:39
LabelTrack::SetSelected
void SetSelected(bool s) override
Definition: LabelTrack.cpp:299
LabelTrack::Import
void Import(wxTextFile &f)
Import labels, handling files with or without end-times.
Definition: LabelTrack.cpp:545
LabelTrack::SetLabel
void SetLabel(size_t iLabel, const LabelStruct &newLabel)
Definition: LabelTrack.cpp:133
Track::GetDefaultName
wxString GetDefaultName() const
Definition: Track.h:428
Track::mOffset
double mOffset
Definition: Track.h:408
Track::WriteCommonXMLAttributes
void WriteCommonXMLAttributes(XMLWriter &xmlFile, bool includeNameAndSelected=true) const
Definition: Track.cpp:1255
TimeWarper
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:62
LabelStruct::ENDS_IN_LABEL
@ ENDS_IN_LABEL
Definition: LabelTrack.h:59
LabelStruct::MoveLabel
void MoveLabel(int iEdge, double fNewTime)
Definition: LabelTrack.cpp:357
AudacityMessageBox
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
Definition: AudacityMessageBox.cpp:17
LabelStruct::width
int width
Text of the label.
Definition: LabelTrack.h:72
TrackView::Get
static TrackView & Get(Track &)
Definition: TrackView.cpp:63
LabelStruct::title
wxString title
Definition: LabelTrack.h:71
XMLValueChecker::IsGoodInt
static bool IsGoodInt(const wxString &strInt)
Check that the supplied string can be converted to a long (32bit) integer.
Definition: XMLTagHandler.cpp:157
gPrefs
FileConfig * gPrefs
Definition: Prefs.cpp:70
TrackView.h
LabelStruct::x
int x
width of the text in pixels.
Definition: LabelTrack.h:75
Project.h
Track::ConstIntervals
std::vector< ConstInterval > ConstIntervals
Definition: Track.h:331
TimeWarper.h
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
TrackControls::Get
static TrackControls & Get(Track &track)
Definition: TrackControls.cpp:25
XMLMethodRegistry::ObjectReaderEntry
Definition: XMLMethodRegistry.h:82
LabelTrack::WriteXML
void WriteXML(XMLWriter &xmlFile) const override
Definition: LabelTrack.cpp:650
LabelTrack::GetStartTime
double GetStartTime() const override
Definition: LabelTrack.cpp:316
LabelTrack::MakeInterval
ConstInterval MakeInterval(size_t index) const
Definition: LabelTrack.cpp:104
XO
#define XO(s)
Definition: Internat.h:31
SelectedRegion::setTimes
bool setTimes(double t0, double t1)
Definition: SelectedRegion.h:136
LabelTrack::Export
void Export(wxTextFile &f) const
Export labels including label start and end-times.
Definition: LabelTrack.cpp:537
LabelTrack
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:88
LabelStruct::Export
void Export(wxTextFile &file) const
Definition: LabelTrack.cpp:444
SelectedRegion::UndefinedFrequency
static const int UndefinedFrequency
Definition: SelectedRegion.h:41
TrackInterval
A start and an end time, and mutative access to optional extra information.
Definition: Track.h:219
Track::HandleCommonXMLAttribute
bool HandleCommonXMLAttribute(const wxChar *attr, const wxChar *value)
Definition: Track.cpp:1269
LabelStruct
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:30
DoMakeInterval
static IntervalType DoMakeInterval(const LabelStruct &label, size_t index)
Definition: LabelTrack.cpp:97
LabelTrack::Cut
Track::Holder Cut(double t0, double t1) override
Definition: LabelTrack.cpp:671
LabelTrack::InsertSilence
void InsertSilence(double t, double len) override
Definition: LabelTrack.cpp:907
LabelTrack::AddLabel
int AddLabel(const SelectedRegion &region, const wxString &title)
Definition: LabelTrack.cpp:931
LabelTrack::ShiftLabelsOnInsert
void ShiftLabelsOnInsert(double length, double pt)
Definition: LabelTrack.cpp:201
LabelStruct::WITHIN_LABEL
@ WITHIN_LABEL
Definition: LabelTrack.h:57
LabelStruct::y
int y
Pixel position of left hand side of text box.
Definition: LabelTrack.h:78
Track::SetSelected
virtual void SetSelected(bool s)
Definition: Track.cpp:90
LabelTrack::Clear
void Clear(double t0, double t1) override
Definition: LabelTrack.cpp:152
SelectedRegion::setT0
bool setT0(double t, bool maySwap=true)
Definition: SelectedRegion.h:112
LabelTrack::GetEndTime
double GetEndTime() const override
Definition: LabelTrack.cpp:324
LabelTrack::HandleXMLTag
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
Definition: LabelTrack.cpp:569
label
TranslatableString label
Definition: Tags.cpp:756
Track::Holder
std::shared_ptr< Track > Holder
Definition: Track.h:336
LabelStruct::selectedRegion
SelectedRegion selectedRegion
Definition: LabelTrack.h:70
Track::SetDefaultName
void SetDefaultName(const wxString &n)
Definition: Track.h:429
LabelTrack::miLastLabel
int miLastLabel
Definition: LabelTrack.h:180
LabelStruct::AdjustEdge
bool AdjustEdge(int iEdge, double fNewTime)
Definition: LabelTrack.cpp:347
LabelStruct::getT0
double getT0() const
Definition: LabelTrack.h:40
LabelTrack::SetOffset
void SetOffset(double dOffset) override
Definition: LabelTrack.cpp:146
LabelTrack::SyncLockAdjust
void SyncLockAdjust(double oldT1, double newT1) override
Definition: LabelTrack.cpp:843
LabelTrack::WarpLabels
void WarpLabels(const TimeWarper &warper)
Definition: LabelTrack.cpp:257
LabelStruct::BEFORE_LABEL
@ BEFORE_LABEL
Definition: LabelTrack.h:54
wxDEFINE_EVENT
wxDEFINE_EVENT(EVT_LABELTRACK_ADDITION, LabelTrackEvent)
ConstTrackInterval
A start and an end time, and non-mutative access to optional extra information.
Definition: Track.h:192
LabelStruct::BadFormatException
Definition: LabelTrack.h:46
LabelTrack::PasteOver
bool PasteOver(double t, const Track *src)
Definition: LabelTrack.cpp:745
LabelTrack::GetOffset
double GetOffset() const override
Definition: LabelTrack.cpp:311
LabelStruct::BEGINS_IN_LABEL
@ BEGINS_IN_LABEL
Definition: LabelTrack.h:58
TrackControls.h
DoMakeIntervals
static Container DoMakeIntervals(LabelTrack &track)
Definition: LabelTrack.cpp:115
readerEntry
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: LabelTrack.cpp:59
LabelTrackEvent
Definition: LabelTrack.h:184
LabelTrack::Silence
void Silence(double t0, double t1) override
Definition: LabelTrack.cpp:861
LabelStruct::RegionRelation
TimeRelations RegionRelation(double reg_t0, double reg_t1, const LabelTrack *parent=NULL) const
Definition: LabelTrack.cpp:470
ImportExportPrefs.h
TimeWarper::Warp
virtual double Warp(double originalTime) const =0
LabelTrack.h
LabelTrack::Clone
Track::Holder Clone() const override
Definition: LabelTrack.cpp:340
LabelTrack::AdjustTimeStampOnScale
double AdjustTimeStampOnScale(double t, double b, double e, double change)
Definition: LabelTrack.cpp:238
XMLTagHandler
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:62
Track::GetSelected
bool GetSelected() const
Definition: Track.h:431
Track::Intervals
std::vector< Interval > Intervals
Definition: Track.h:329
LabelStruct::Import
static LabelStruct Import(wxTextFile &file, int &index)
Definition: LabelTrack.cpp:372
LabelTrack::GetTextOfLabels
wxString GetTextOfLabels(double t0, double t1) const
Definition: LabelTrack.cpp:1004
LabelTrack::PasteInto
Track::Holder PasteInto(AudacityProject &) const override
Find or create the destination track for a paste, maybe in a different project.
Definition: LabelTrack.cpp:89
LabelTrack::Paste
void Paste(double t, const Track *src) override
Definition: LabelTrack.cpp:774
SelectedRegion::t0
double t0() const
Definition: SelectedRegion.h:91
LabelTrack::GetNumLabels
int GetNumLabels() const
Definition: LabelTrack.cpp:921
Track::TypeSwitch
R TypeSwitch(const Functions &...functions)
Use this function rather than testing track type explicitly and making down-casts.
Definition: Track.h:709
title
static const auto title
Definition: NoUpdatesAvailableDialog.cpp:22
TrackList::Get
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:506
Track::SetName
void SetName(const wxString &n)
Definition: Track.cpp:82
Track
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:239
XMLValueChecker::IsGoodLongString
static bool IsGoodLongString(const wxString &str)
Definition: XMLTagHandler.cpp:51
_
#define _(s)
Definition: Internat.h:75
LabelStruct::updated
bool updated
Pixel position of label.
Definition: LabelTrack.h:80
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:92
LabelTrack::Repeat
bool Repeat(double t0, double t1, int n)
Definition: LabelTrack.cpp:791
LabelTrack::HandleXMLChild
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
Definition: LabelTrack.cpp:642
AudacityMessageBox.h
LabelStruct::TimeRelations
TimeRelations
Relationships between selection region and labels.
Definition: LabelTrack.h:53
LabelTrack::GetLabel
const LabelStruct * GetLabel(int index) const
Definition: LabelTrack.cpp:926
Prefs.h
LabelTrack::GetIntervals
ConstIntervals GetIntervals() const override
Report times on the track where important intervals begin and end, for UI to snap to.
Definition: LabelTrack.cpp:123
SelectedRegion::HandleXMLAttribute
bool HandleXMLAttribute(const wxChar *attr, const wxChar *value, const wxChar *legacyT0Name=sDefaultT0Name, const wxChar *legacyT1Name=sDefaultT1Name)
Definition: SelectedRegion.cpp:39
LabelTrack::FindNextLabel
int FindNextLabel(const SelectedRegion &currentSelection)
Definition: LabelTrack.cpp:1023
LabelTrack::Copy
Track::Holder Copy(double t0, double t1, bool forClipboard=true) const override
Definition: LabelTrack.cpp:694
LabelTrack::mLabels
LabelArray mLabels
Definition: LabelTrack.h:175
LabelTrack::LabelTrack
LabelTrack()
Definition: LabelTrack.cpp:70
LabelTrack::ScaleLabels
void ScaleLabels(double b, double e, double change)
Definition: LabelTrack.cpp:229
ImportExportPrefs::LabelStyleSetting
static EnumSetting< bool > LabelStyleSetting
Definition: ImportExportPrefs.h:30
LabelStruct::xText
int xText
Pixel position of right hand glyph.
Definition: LabelTrack.h:77
LabelStruct::SURROUNDS_LABEL
@ SURROUNDS_LABEL
Definition: LabelTrack.h:56
LabelTrack::ChangeLabelsOnReverse
void ChangeLabelsOnReverse(double b, double e)
Definition: LabelTrack.cpp:214
LabelStruct::LabelStruct
LabelStruct()=default
Internat::ToString
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:151
LabelStruct::getT1
double getT1() const
Definition: LabelTrack.h:41
SelectedRegion::setT1
bool setT1(double t, bool maySwap=true)
Definition: SelectedRegion.h:124
LabelTrack::DeleteLabel
void DeleteLabel(int index)
Definition: LabelTrack.cpp:952
LabelTrack::FindPrevLabel
int FindPrevLabel(const SelectedRegion &currentSelection)
Definition: LabelTrack.cpp:1049
LabelTrack::SortLabels
void SortLabels()
Definition: LabelTrack.cpp:969
Internat::CompatibleToDouble
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:134
SelectedRegion
Defines a selected portion of a project.
Definition: SelectedRegion.h:35