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