Audacity  2.2.2
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 #include "Audacity.h"
32 #include "LabelTrack.h"
33 #include "Experimental.h"
34 #include "TrackPanel.h"
35 
36 #include <stdio.h>
37 #include <algorithm>
38 #include <limits.h>
39 #include <float.h>
40 
41 #include <wx/bitmap.h>
42 #include <wx/brush.h>
43 #include <wx/clipbrd.h>
44 #include <wx/dataobj.h>
45 #include <wx/dc.h>
46 #include <wx/dcclient.h>
47 #include <wx/event.h>
48 #include <wx/intl.h>
49 #include <wx/log.h>
50 #include <wx/pen.h>
51 #include <wx/string.h>
52 #include <wx/textfile.h>
53 #include <wx/tokenzr.h>
54 #include <wx/utils.h>
55 
56 #include "AudioIO.h"
57 #include "DirManager.h"
58 #include "Internat.h"
59 #include "Prefs.h"
60 #include "RefreshCode.h"
61 #include "Theme.h"
62 #include "AllThemeResources.h"
63 #include "AColor.h"
64 #include "Project.h"
65 #include "TrackArtist.h"
66 #include "TrackPanel.h"
67 #include "UndoManager.h"
69 
70 #include "effects/TimeWarper.h"
71 #include "widgets/ErrorDialog.h"
72 
73 enum
74 {
75  OnCutSelectedTextID = 1, // OSX doesn't like a 0 menu id
80 };
81 
82 wxFont LabelTrack::msFont;
83 
84 // static member variables.
85 bool LabelTrack::mbGlyphsReady=false;
86 
96 
98 
100 {
101  return std::make_unique<LabelTrack>(mDirManager);
102 }
103 
104 LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
105  Track(projDirManager),
106  mSelIndex(-1),
107  mRestoreFocus(-1),
108  mClipLen(0.0),
109  miLastLabel(-1)
110 {
111  SetDefaultName(_("Label Track"));
113 
114  // Label tracks are narrow
115  // Default is to allow two rows so that NEW users get the
116  // idea that labels can 'stack' when they would overlap.
117  SetHeight(73);
118 
119  ResetFont();
121 
122  // reset flags
123  ResetFlags();
124 }
125 
127  Track(orig),
128  mSelIndex(-1),
129  mClipLen(0.0)
130 {
131  for (auto &original: orig.mLabels) {
132  LabelStruct l { original.selectedRegion, original.title };
133  mLabels.push_back(l);
134  }
135  mSelIndex = orig.mSelIndex;
136 
137  // reset flags
138  ResetFlags();
139 }
140 
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 {
270  mInitialCursorPos = 1;
271  mCurrentCursorPos = 1;
272  mRightDragging = false;
273  mDrawCursor = false;
274 }
275 
276 void LabelTrack::RestoreFlags( const Flags& flags )
277 {
280  mSelIndex = flags.mSelIndex;
282  mDrawCursor = flags.mDrawCursor;
283 }
284 
285 wxFont LabelTrack::GetFont(const wxString &faceName, int size)
286 {
287  wxFontEncoding encoding;
288  if (faceName == wxT(""))
289  encoding = wxFONTENCODING_DEFAULT;
290  else
291  encoding = wxFONTENCODING_SYSTEM;
292  return wxFont(size, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
293  wxFONTWEIGHT_NORMAL, false, faceName, encoding);
294 }
295 
297 {
298  mFontHeight = -1;
299  wxString facename = gPrefs->Read(wxT("/GUI/LabelFontFacename"), wxT(""));
300  int size = gPrefs->Read(wxT("/GUI/LabelFontSize"), DefaultFontSize);
301  msFont = GetFont(facename, size);
302 }
303 
317 void LabelTrack::ComputeTextPosition(const wxRect & r, int index) const
318 {
319  auto &labelStruct = mLabels[index];
320 
321  // xExtra is extra space
322  // between the text and the endpoints.
323  const int xExtra=mIconWidth;
324  int x = labelStruct.x; // left endpoint
325  int x1 = labelStruct.x1; // right endpoint.
326  int width = labelStruct.width;
327 
328  int xText; // This is where the text will end up.
329 
330  // Will the text all fit at this zoom?
331  bool bTooWideForScreen = width > (r.width-2*xExtra);
332 // bool bSimpleCentering = !bTooWideForScreen;
333  bool bSimpleCentering = false;
334 
335  //TODO (possibly):
336  // Add configurable options as to when to use simple
337  // and when complex centering.
338  //
339  // Simple centering does its best to keep the text
340  // centered between the label limits.
341  //
342  // Complex centering positions the text proportionally
343  // to how far we are through the label.
344  //
345  // If we add preferences for this, we want to be able to
346  // choose separately whether:
347  // a) Wide text labels centered simple/complex.
348  // b) Other text labels centered simple/complex.
349  //
350 
351  if( bSimpleCentering )
352  {
353  // Center text between the two end points.
354  xText = (x+x1-width)/2;
355  }
356  else
357  {
358  // Calculate xText position to make text line
359  // scroll sideways evenly as r moves right.
360 
361  // xText is a linear function of r.x.
362  // These variables are used to compute that function.
363  int rx0,rx1,xText0,xText1;
364 
365  // Since we will be using a linear function,
366  // we should blend smoothly between left and right
367  // aligned text as r, the 'viewport' moves.
368  if( bTooWideForScreen )
369  {
370  rx0=x; // when viewport at label start.
371  xText0=x+xExtra; // text aligned left.
372  rx1=x1-r.width; // when viewport end at label end
373  xText1=x1-(width+xExtra); // text aligned right.
374  }
375  else
376  {
377  // when label start + width + extra spacing at viewport end..
378  rx0=x-r.width+width+2*xExtra;
379  // ..text aligned left.
380  xText0=x+xExtra;
381  // when viewport start + width + extra spacing at label end..
382  rx1=x1-(width+2*xExtra);
383  // ..text aligned right.
384  xText1=x1-(width+xExtra);
385  }
386 
387  if( rx1 > rx0 ) // Avoid divide by zero case.
388  {
389  // Compute the blend between left and right aligned.
390 
391  // Don't use:
392  //
393  // xText = xText0 + ((xText1-xText0)*(r.x-rx0))/(rx1-rx0);
394  //
395  // The problem with the above is that it integer-oveflows at
396  // high zoom.
397 
398  // Instead use:
399  xText = xText0 + (int)((xText1-xText0)*(((float)(r.x-rx0))/(rx1-rx0)));
400  }
401  else
402  {
403  // Avoid divide by zero by reverting to
404  // simple centering.
405  //
406  // We could also fall into this case if x and x1
407  // are swapped, in which case we'll end up
408  // left aligned anyway because code later on
409  // will catch that.
410  xText = (x+x1-width)/2;
411  }
412  }
413 
414  // Is the text now appearing partly outside r?
415  bool bOffLeft = xText < r.x+xExtra;
416  bool bOffRight = xText > r.x+r.width-width-xExtra;
417 
418  // IF any part of the text is offscreen
419  // THEN we may bring it back.
420  if( bOffLeft == bOffRight )
421  {
422  //IF both sides on screen, THEN nothing to do.
423  //IF both sides off screen THEN don't do
424  //anything about it.
425  //(because if we did, you'd never get to read
426  //all the text by scrolling).
427  }
428  else if( bOffLeft != bTooWideForScreen)
429  {
430  // IF we're off on the left, OR we're
431  // too wide for the screen and off on the right
432  // (only) THEN align left.
433  xText = r.x+xExtra;
434  }
435  else
436  {
437  // We're off on the right, OR we're
438  // too wide and off on the left (only)
439  // SO align right.
440  xText =r.x+r.width-width-xExtra;
441  }
442 
443  // But if we've taken the text out from its endpoints
444  // we must move it back so that it's between the endpoints.
445 
446  // We test the left end point last because the
447  // text might not even fit between the endpoints (at this
448  // zoom factor), and in that case we'd like to position
449  // the text at the left end point.
450  if( xText > (x1-width-xExtra))
451  xText=(x1-width-xExtra);
452  if( xText < x+xExtra )
453  xText=x+xExtra;
454 
455  labelStruct.xText = xText;
456 }
457 
461 void LabelTrack::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) const
462 {
463  int xUsed[MAX_NUM_ROWS];
464 
465  int iRow;
466  // Rows are the 'same' height as icons or as the text,
467  // whichever is taller.
468  const int yRowHeight = wxMax(mTextHeight,mIconHeight)+3;// pixels.
469  // Extra space at end of rows.
470  // We allow space for one half icon at the start and two
471  // half icon widths for extra x for the text frame.
472  // [we don't allow half a width space for the end icon since it is
473  // allowed to be obscured by the text].
474  const int xExtra= (3 * mIconWidth)/2;
475 
476  const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS);
477  // Initially none of the rows have been used.
478  // So set a value that is less than any valid value.
479  {
480  // Bug 502: With dragging left of zeros, labels can be in
481  // negative space. So set least possible value as starting point.
482  const int xStart = INT_MIN;
483  for (auto &x : xUsed)
484  x = xStart;
485  }
486  int nRowsUsed=0;
487 
488  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
489  const int x = zoomInfo.TimeToPosition(labelStruct.getT0(), r.x);
490  const int x1 = zoomInfo.TimeToPosition(labelStruct.getT1(), r.x);
491  int y = r.y;
492 
493  labelStruct.x=x;
494  labelStruct.x1=x1;
495  labelStruct.y=-1;// -ve indicates nothing doing.
496  iRow=0;
497  // Our first preference is a row that ends where we start.
498  // (This is to encourage merging of adjacent label boundaries).
499  while( (iRow<nRowsUsed) && (xUsed[iRow] != x ))
500  iRow++;
501  // IF we didn't find one THEN
502  // find any row that can take a span starting at x.
503  if( iRow >= nRowsUsed )
504  {
505  iRow=0;
506  while( (iRow<nRows) && (xUsed[iRow] > x ))
507  iRow++;
508  }
509  // IF we found such a row THEN record a valid position.
510  if( iRow<nRows )
511  {
512  // Possibly update the number of rows actually used.
513  if( iRow >= nRowsUsed )
514  nRowsUsed=iRow+1;
515  // Record the position for this label
516  y= r.y + iRow * yRowHeight +(yRowHeight/2)+1;
517  labelStruct.y=y;
518  // On this row we have used up to max of end marker and width.
519  // Plus also allow space to show the start icon and
520  // some space for the text frame.
521  xUsed[iRow]=x+labelStruct.width+xExtra;
522  if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
523  ComputeTextPosition( r, i );
524  }
525  }}
526 }
527 
529  const wxString& aTitle)
530 : selectedRegion(region)
531 , title(aTitle)
532 {
533  updated = false;
534  width = 0;
535  x = 0;
536  x1 = 0;
537  xText = 0;
538  y = 0;
539 }
540 
542  double t0, double t1,
543  const wxString& aTitle)
544 : selectedRegion(region)
545 , title(aTitle)
546 {
547  // Overwrite the times
548  selectedRegion.setTimes(t0, t1);
549 
550  updated = false;
551  width = 0;
552  x = 0;
553  x1 = 0;
554  xText = 0;
555  y = 0;
556 }
557 
562 void LabelStruct::DrawLines(wxDC & dc, const wxRect & r) const
563 {
564  // How far out from the centre line should the vertical lines
565  // start, i.e. what is the y position of the icon?
566  // We adjust this so that the line encroaches on the icon
567  // slightly (there is white space in the design).
568  const int yIconStart = y - (LabelTrack::mIconHeight /2)+1+(LabelTrack::mTextHeight+3)/2;
569  const int yIconEnd = yIconStart + LabelTrack::mIconHeight-2;
570 
571  // If y is positive then it is the center line for the
572  // Label.
573  if( y >= 0 )
574  {
575  if((x >= r.x) && (x <= (r.x+r.width)))
576  {
577  // Draw line above and below left dragging widget.
578  AColor::Line(dc, x, r.y, x, yIconStart - 1);
579  AColor::Line(dc, x, yIconEnd, x, r.y + r.height);
580  }
581  if((x1 >= r.x) && (x1 <= (r.x+r.width)))
582  {
583  // Draw line above and below right dragging widget.
584  AColor::Line(dc, x1, r.y, x1, yIconStart - 1);
585  AColor::Line(dc, x1, yIconEnd, x1, r.y + r.height);
586  }
587  }
588  else
589  {
590  // Draw the line, even though the widget is off screen
591  AColor::Line(dc, x, r.y, x, r.y + r.height);
592  AColor::Line(dc, x1, r.y, x1, r.y + r.height);
593  }
594 }
595 
600  (wxDC & dc, const wxRect & r, int GlyphLeft, int GlyphRight) const
601 {
602  const int xHalfWidth=LabelTrack::mIconWidth/2;
603  const int yStart=y-LabelTrack::mIconHeight/2+(LabelTrack::mTextHeight+3)/2;
604 
605  // If y == -1, nothing to draw
606  if( y == -1 )
607  return;
608 
609  if((x >= r.x) && (x <= (r.x+r.width)))
610  dc.DrawBitmap(LabelTrack::GetGlyph(GlyphLeft), x-xHalfWidth,yStart, true);
611  // The extra test commented out here would suppress right hand markers
612  // when they overlap the left hand marker (e.g. zoomed out) or to the left.
613  if((x1 >= r.x) && (x1 <= (r.x+r.width)) /*&& (x1>x+LabelTrack::mIconWidth)*/)
614  dc.DrawBitmap(LabelTrack::GetGlyph(GlyphRight), x1-xHalfWidth,yStart, true);
615 }
616 
623 void LabelStruct::DrawText(wxDC & dc, const wxRect & r) const
624 {
625  //If y is positive then it is the center line for the
626  //text we are about to draw.
627  //if it isn't, nothing to draw.
628 
629  if( y == -1 )
630  return;
631 
632  // Draw frame for the text...
633  // We draw it half an icon width left of the text itself.
634  {
635  const int xStart=wxMax(r.x,xText-LabelTrack::mIconWidth/2);
636  const int xEnd=wxMin(r.x+r.width,xText+width+LabelTrack::mIconWidth/2);
637  const int xWidth = xEnd-xStart;
638 
639  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
640  {
641  // Now draw the text itself.
642  dc.DrawText(title, xText, y-LabelTrack::mTextHeight/2);
643  }
644  }
645 
646 }
647 
648 void LabelStruct::DrawTextBox(wxDC & dc, const wxRect & r) const
649 {
650  //If y is positive then it is the center line for the
651  //text we are about to draw.
652  const int yBarHeight=3;
653  const int yFrameHeight = LabelTrack::mTextHeight+3;
654  const int xBarShorten = LabelTrack::mIconWidth+4;
655  if( y == -1 )
656  return;
657 
658  {
659  const int xStart=wxMax(r.x,x+xBarShorten/2);
660  const int xEnd=wxMin(r.x+r.width,x1-xBarShorten/2);
661  const int xWidth = xEnd-xStart;
662 
663  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
664  {
665 
666  wxRect bar( xStart,y-yBarHeight/2+yFrameHeight/2,
667  xWidth,yBarHeight);
668  if( x1 > x+xBarShorten )
669  dc.DrawRectangle(bar);
670  }
671  }
672 
673  // In drawing the bar and the frame, we compute the clipping
674  // to the viewport ourselves. Under Win98 the GDI does its
675  // calculations in 16 bit arithmetic, and so gets it completely
676  // wrong at higher zooms where the bar can easily be
677  // more than 65536 pixels wide.
678 
679  // Draw bar for label extent...
680  // We don't quite draw from x to x1 because we allow
681  // half an icon width at each end.
682  {
683  const int xStart=wxMax(r.x,xText-LabelTrack::mIconWidth/2);
684  const int xEnd=wxMin(r.x+r.width,xText+width+LabelTrack::mIconWidth/2);
685  const int xWidth = xEnd-xStart;
686 
687  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
688  {
689  wxRect frame(
690  xStart,y-yFrameHeight/2,
691  xWidth,yFrameHeight );
692  dc.DrawRectangle(frame);
693  }
694  }
695 }
696 
699  ( wxDC & dc, int xPos1, int xPos2, int charHeight) const
700 {
701  wxPen curPen = dc.GetPen();
702  curPen.SetColour(wxString(wxT("BLUE")));
703  wxBrush curBrush = dc.GetBrush();
704  curBrush.SetColour(wxString(wxT("BLUE")));
705  if (xPos1 < xPos2)
706  dc.DrawRectangle(xPos1-1, y-charHeight/2, xPos2-xPos1+1, charHeight);
707  else
708  dc.DrawRectangle(xPos2-1, y-charHeight/2, xPos1-xPos2+1, charHeight);
709 }
710 
711 void LabelStruct::getXPos( wxDC & dc, int * xPos1, int cursorPos) const
712 {
713  *xPos1 = xText;
714  if( cursorPos > 0)
715  {
716  int partWidth;
717  // Calculate the width of the substring and add it to Xpos
718  dc.GetTextExtent(title.Left(cursorPos), &partWidth, NULL);
719  *xPos1 += partWidth;
720  }
721 }
722 
723 bool LabelTrack::CalcCursorX(int * x) const
724 {
725  if (mSelIndex >= 0) {
726  wxMemoryDC dc;
727 
728  if (msFont.Ok()) {
729  dc.SetFont(msFont);
730  }
731 
732  mLabels[mSelIndex].getXPos(dc, x, mCurrentCursorPos);
733  *x += LabelTrack::mIconWidth / 2;
734  return true;
735  }
736 
737  return false;
738 }
739 
740 void LabelTrack::CalcHighlightXs(int *x1, int *x2) const
741 {
742  wxMemoryDC dc;
743 
744  if (msFont.Ok()) {
745  dc.SetFont(msFont);
746  }
747 
748  int pos1 = mInitialCursorPos, pos2 = mCurrentCursorPos;
749  if (pos1 > pos2)
750  std::swap(pos1, pos2);
751 
752  const auto &labelStruct = mLabels[mSelIndex];
753 
754  // find the left X pos of highlighted area
755  labelStruct.getXPos(dc, x1, pos1);
756  // find the right X pos of highlighted area
757  labelStruct.getXPos(dc, x2, pos2);
758 }
759 
761 // TODO: don't rely on the global ::GetActiveProject() to find this.
762 // Rather, give TrackPanelCell a drawing function and pass context into it.
763 namespace {
764  LabelTrackHit *findHit()
765  {
766  // Fetch the highlighting state
767  auto target = GetActiveProject()->GetTrackPanel()->Target();
768  if (target) {
769  auto handle = dynamic_cast<LabelGlyphHandle*>( target.get() );
770  if (handle)
771  return &handle->mHit;
772  }
773  return nullptr;
774  }
775 }
776 
779 
783 void LabelTrack::Draw
784 (TrackPanelDrawingContext &context, const wxRect & r,
785  const SelectedRegion &selectedRegion,
786  const ZoomInfo &zoomInfo) const
787 {
788  auto &dc = context.dc;
789  auto pHit = findHit();
790 
791  if(msFont.Ok())
792  dc.SetFont(msFont);
793 
794  if (mFontHeight == -1)
795  calculateFontHeight(dc);
796 
799  selectedRegion, zoomInfo);
800 
801  wxCoord textWidth, textHeight;
802 
803  // Get the text widths.
804  // TODO: Make more efficient by only re-computing when a
805  // text label title changes.
806  for (auto &labelStruct : mLabels) {
807  dc.GetTextExtent(labelStruct.title, &textWidth, &textHeight);
808  labelStruct.width = textWidth;
809  }
810 
811  // TODO: And this only needs to be done once, but we
812  // do need the dc to do it.
813  // We need to set mTextHeight to something sensible,
814  // guarding against the case where there are no
815  // labels or all are empty strings, which for example
816  // happens with a NEW label track.
817  dc.GetTextExtent(wxT("Demo Text x^y"), &textWidth, &textHeight);
818  mTextHeight = (int)textHeight;
819  ComputeLayout( r, zoomInfo );
820  dc.SetTextForeground(theTheme.Colour( clrLabelTrackText));
821  dc.SetBackgroundMode(wxTRANSPARENT);
822  dc.SetBrush(AColor::labelTextNormalBrush);
823  dc.SetPen(AColor::labelSurroundPen);
824  int GlyphLeft;
825  int GlyphRight;
826  // Now we draw the various items in this order,
827  // so that the correct things overpaint each other.
828 
829  // Draw vertical lines that show where the end positions are.
830  for (auto &labelStruct : mLabels)
831  labelStruct.DrawLines( dc, r );
832 
833  // Draw the end glyphs.
834  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
835  GlyphLeft=0;
836  GlyphRight=1;
837  if( pHit && i == pHit->mMouseOverLabelLeft )
838  GlyphLeft = (pHit->mEdge & 4) ? 6:9;
839  if( pHit && i == pHit->mMouseOverLabelRight )
840  GlyphRight = (pHit->mEdge & 4) ? 7:4;
841  labelStruct.DrawGlyphs( dc, r, GlyphLeft, GlyphRight );
842  }}
843 
844  // Draw the label boxes.
845  {
846 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
847  bool highlightTrack = false;
848  auto target = dynamic_cast<LabelTextHandle*>(context.target.get());
849  highlightTrack = target && target->GetTrack().get() == this;
850 #endif
851  int i = -1; for (auto &labelStruct : mLabels) { ++i;
852  bool highlight = false;
853 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
854  highlight = highlightTrack && target->GetLabelNum() == i;
855 #endif
856  bool selected = mSelIndex == i;
857 
858  if( selected )
859  dc.SetBrush( AColor::labelTextEditBrush );
860  else if ( highlight )
861  dc.SetBrush( AColor::uglyBrush );
862  labelStruct.DrawTextBox( dc, r );
863 
864  if (highlight || selected)
865  dc.SetBrush(AColor::labelTextNormalBrush);
866  }
867  }
868 
869  // Draw highlights
870  if ((mInitialCursorPos != mCurrentCursorPos) && (mSelIndex >= 0 ))
871  {
872  int xpos1, xpos2;
873  CalcHighlightXs(&xpos1, &xpos2);
874  mLabels[mSelIndex].DrawHighlight(dc, xpos1, xpos2, mFontHeight);
875  }
876 
877  // Draw the text and the label boxes.
878  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
879  if( mSelIndex==i)
880  dc.SetBrush(AColor::labelTextEditBrush);
881  labelStruct.DrawText( dc, r );
882  if( mSelIndex==i)
883  dc.SetBrush(AColor::labelTextNormalBrush);
884  }}
885 
886  // Draw the cursor, if there is one.
887  if( mDrawCursor && mSelIndex >=0 )
888  {
889  const auto &labelStruct = mLabels[mSelIndex];
890  int xPos = labelStruct.xText;
891 
892  if( mCurrentCursorPos > 0)
893  {
894  // Calculate the width of the substring and add it to Xpos
895  int partWidth;
896  dc.GetTextExtent(labelStruct.title.Left(mCurrentCursorPos), &partWidth, NULL);
897  xPos += partWidth;
898  }
899 
900  wxPen currentPen = dc.GetPen();
901  const int CursorWidth=2;
902  currentPen.SetWidth(CursorWidth);
903  AColor::Line(dc,
904  xPos-1, labelStruct.y - mFontHeight/2 + 1,
905  xPos-1, labelStruct.y + mFontHeight/2 - 1);
906  currentPen.SetWidth(1);
907  }
908 }
909 
913 {
914  int result = -1;
915  wxMemoryDC dc;
916  if(msFont.Ok())
917  dc.SetFont(msFont);
918 
919  // A bool indicator to see if set the cursor position or not
920  bool finished = false;
921  int charIndex = 1;
922  int partWidth;
923  int oneWidth;
924  double bound;
925  wxString subString;
926  const auto &labelStruct = mLabels[mSelIndex];
927  const auto &title = labelStruct.title;
928  const int length = title.length();
929  while (!finished && (charIndex < length + 1))
930  {
931  subString = title.Left(charIndex);
932  // Get the width of substring
933  dc.GetTextExtent(subString, &partWidth, NULL);
934 
935  // Get the width of the last character
936  dc.GetTextExtent(subString.Right(1), &oneWidth, NULL);
937  bound = labelStruct.xText + partWidth - oneWidth * 0.5;
938 
939  if (xPos <= bound)
940  {
941  // Found
942  result = charIndex - 1;
943  finished = true;
944  }
945  else
946  {
947  // Advance
948  charIndex++;
949  }
950  }
951  if (!finished)
952  // Cursor should be in the last position
953  result = length;
954 
955  return result;
956 }
957 
960 {
962 }
963 
964 void LabelTrack::calculateFontHeight(wxDC & dc) const
965 {
966  int charDescent;
967  int charLeading;
968 
969  // Calculate the width of the substring and add it to Xpos
970  dc.GetTextExtent(wxT("(Test String)|[yp]"), NULL, &mFontHeight, &charDescent, &charLeading);
971 
972  // The cursor will have height charHeight. We don't include the descender as
973  // part of the height because for phonetic fonts this leads to cursors which are
974  // too tall. We don't include leading either - it is usually 0.
975  // To make up for ignoring the descender height, we add one pixel above and below
976  // using CursorExtraHeight so that the cursor is just a little taller than the
977  // body of the characters.
978  const int CursorExtraHeight=2;
979  mFontHeight += CursorExtraHeight - (charLeading+charDescent);
980 }
981 
983 {
984  if (mSelIndex == -1)
985  return false;
987  return false;
988  return true;
989 }
990 
994 {
995  if (!IsTextSelected())
996  return false;
997 
998  wxString left, right;
999  auto &labelStruct = mLabels[mSelIndex];
1000  auto &text = labelStruct.title;
1001 
1002  int init = mInitialCursorPos;
1003  int cur = mCurrentCursorPos;
1004  if (init > cur)
1005  std::swap(init, cur);
1006 
1007  // data for cutting
1008  wxString data = text.Mid(init, cur - init);
1009 
1010  // get left-remaining text
1011  if (init > 0)
1012  left = text.Left(init);
1013 
1014  // get right-remaining text
1015  if (cur < (int)text.Length())
1016  right = text.Mid(cur);
1017 
1018  // set title to the combination of the two remainders
1019  text = left + right;
1020 
1021  // copy data onto clipboard
1022  if (wxTheClipboard->Open()) {
1023  // Clipboard owns the data you give it
1024  wxTheClipboard->SetData(safenew wxTextDataObject(data));
1025  wxTheClipboard->Close();
1026  }
1027 
1028  // set cursor positions
1029  mInitialCursorPos = mCurrentCursorPos = left.Length();
1030  return true;
1031 }
1032 
1036 {
1037  if (mSelIndex == -1)
1038  return false;
1039 
1040  const auto &labelStruct = mLabels[mSelIndex];
1041 
1042  int init = mInitialCursorPos;
1043  int cur = mCurrentCursorPos;
1044  if (init > cur)
1045  std::swap(init, cur);
1046 
1047  if (init == cur)
1048  return false;
1049 
1050  // data for copying
1051  wxString data = labelStruct.title.Mid(init, cur-init);
1052 
1053  // copy the data on clipboard
1054  if (wxTheClipboard->Open()) {
1055  // Clipboard owns the data you give it
1056  wxTheClipboard->SetData(safenew wxTextDataObject(data));
1057  wxTheClipboard->Close();
1058  }
1059 
1060  return true;
1061 }
1062 
1063 // PRL: should this set other fields of the label selection?
1066 bool LabelTrack::PasteSelectedText(double sel0, double sel1)
1067 {
1068  if (mSelIndex == -1)
1069  AddLabel(SelectedRegion(sel0, sel1), wxT(""));
1070 
1071  wxString text, left, right;
1072 
1073  // if text data is available
1074  if (IsTextClipSupported()) {
1075  if (wxTheClipboard->Open()) {
1076  wxTextDataObject data;
1077  wxTheClipboard->GetData(data);
1078  wxTheClipboard->Close();
1079  text = data.GetText();
1080  }
1081 
1082  // Convert control characters to blanks
1083  for (int i = 0; i < (int)text.Length(); i++) {
1084  if (wxIscntrl(text[i])) {
1085  text[i] = wxT(' ');
1086  }
1087  }
1088  }
1089 
1090  auto &labelStruct = mLabels[mSelIndex];
1091  auto &title = labelStruct.title;
1092  int cur = mCurrentCursorPos, init = mInitialCursorPos;
1093  if (init > cur)
1094  std::swap(init, cur);
1095  left = title.Left(init);
1096  if (cur < (int)title.Length())
1097  right = title.Mid(cur);
1098 
1099  title = left + text + right;
1100  mInitialCursorPos = mCurrentCursorPos = left.Length() + text.Length();
1101  return true;
1102 }
1103 
1104 
1107 {
1108  return wxTheClipboard->IsSupported(wxDF_TEXT);
1109 }
1110 
1111 
1113 {
1114  return mOffset;
1115 }
1116 
1118 {
1119  if (mLabels.empty())
1120  return 0.0;
1121  else
1122  return mLabels[0].getT0();
1123 }
1124 
1126 {
1127  //we need to scan through all the labels, because the last
1128  //label might not have the right-most end (if there is overlap).
1129  if (mLabels.empty())
1130  return 0.0;
1131 
1132  double end = 0.0;
1133  for (auto &labelStruct: mLabels) {
1134  const double t1 = labelStruct.getT1();
1135  if(t1 > end)
1136  end = t1;
1137  }
1138  return end;
1139 }
1140 
1142 {
1143  return std::make_unique<LabelTrack>( *this );
1144 }
1145 
1147 {
1148  Track::SetSelected(s);
1149  if (!s)
1150  Unselect();
1151 }
1152 
1156 void LabelTrack::OverGlyph(LabelTrackHit &hit, int x, int y) const
1157 {
1158  //Determine the NEW selection.
1159  int result=0;
1160  const int d1=10; //distance in pixels, used for have we hit drag handle.
1161  const int d2=5; //distance in pixels, used for have we hit drag handle center.
1162 
1163  //If not over a label, reset it
1164  hit.mMouseOverLabelLeft = -1;
1165  hit.mMouseOverLabelRight = -1;
1166  hit.mEdge = 0;
1167  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
1168  //over left or right selection bound
1169  //Check right bound first, since it is drawn after left bound,
1170  //so give it precedence for matching/highlighting.
1171  if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
1172  abs(labelStruct.x1 - d2 -x) < d1)
1173  {
1174  hit.mMouseOverLabelRight = i;
1175  if(abs(labelStruct.x1 - x) < d2 )
1176  {
1177  result |= 4;
1178  // If left and right co-incident at this resolution, then we drag both.
1179  // We could be a little less stringent about co-incidence here if we liked.
1180  if( abs(labelStruct.x1-labelStruct.x) < 1.0 )
1181  {
1182  result |=1;
1183  hit.mMouseOverLabelLeft = i;
1184  }
1185  }
1186  result |= 2;
1187  }
1188  // Use else-if here rather than else to avoid detecting left and right
1189  // of the same label.
1190  else if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
1191  abs(labelStruct.x + d2 - x) < d1 )
1192  {
1193  hit.mMouseOverLabelLeft = i;
1194  if(abs(labelStruct.x - x) < d2 )
1195  result |= 4;
1196  result |= 1;
1197  }
1198 
1199  // give text box better priority for selecting
1200  if(OverTextBox(&labelStruct, x, y))
1201  {
1202  result = 0;
1203  }
1204 
1205  }}
1206  hit.mEdge = result;
1207 }
1208 
1209 int LabelTrack::OverATextBox(int xx, int yy) const
1210 {
1211  for (int nn = (int)mLabels.size(); nn--;) {
1212  const auto &labelStruct = mLabels[nn];
1213  if (OverTextBox(&labelStruct, xx, yy))
1214  return nn;
1215  }
1216 
1217  return -1;
1218 }
1219 
1220 // return true if the mouse is over text box, false otherwise
1221 bool LabelTrack::OverTextBox(const LabelStruct *pLabel, int x, int y) const
1222 {
1223  if( (pLabel->xText-(mIconWidth/2) < x) &&
1224  (x<pLabel->xText+pLabel->width+(mIconWidth/2)) &&
1225  (abs(pLabel->y-y)<mIconHeight/2))
1226  {
1227  return true;
1228  }
1229  return false;
1230 }
1231 
1232 // Adjust label's left or right boundary, depending which is requested.
1233 // Return true iff the label flipped.
1234 bool LabelStruct::AdjustEdge( int iEdge, double fNewTime)
1235 {
1236  updated = true;
1237  if( iEdge < 0 )
1238  return selectedRegion.setT0(fNewTime);
1239  else
1240  return selectedRegion.setT1(fNewTime);
1241 }
1242 
1243 // We're moving the label. Adjust both left and right edge.
1244 void LabelStruct::MoveLabel( int iEdge, double fNewTime)
1245 {
1246  double fTimeSpan = getDuration();
1247 
1248  if( iEdge < 0 )
1249  {
1250  selectedRegion.setTimes(fNewTime, fNewTime+fTimeSpan);
1251  }
1252  else
1253  {
1254  selectedRegion.setTimes(fNewTime-fTimeSpan, fNewTime);
1255  }
1256  updated = true;
1257 }
1258 
1259 LabelStruct LabelStruct::Import(wxTextFile &file, int &index)
1260 {
1261  SelectedRegion sr;
1262  wxString title;
1263  static const wxString continuation{ wxT("\\") };
1264 
1265  wxString firstLine = file.GetLine(index++);
1266 
1267  {
1268  // Assume tab is an impossible character within the exported text
1269  // of the label, so can be only a delimiter. But other white space may
1270  // be part of the label text.
1271  wxStringTokenizer toker { firstLine, wxT("\t") };
1272 
1273  //get the timepoint of the left edge of the label.
1274  auto token = toker.GetNextToken();
1275 
1276  double t0;
1277  if (!Internat::CompatibleToDouble(token, &t0))
1278  throw BadFormatException{};
1279 
1280  token = toker.GetNextToken();
1281 
1282  double t1;
1283  if (!Internat::CompatibleToDouble(token, &t1))
1284  //s1 is not a number.
1285  t1 = t0; //This is a one-sided label; t1 == t0.
1286  else
1287  token = toker.GetNextToken();
1288 
1289  sr.setTimes( t0, t1 );
1290 
1291  title = token;
1292  }
1293 
1294  // Newer selection fields are written on additional lines beginning with
1295  // '\' which is an impossible numerical character that older versions of
1296  // audacity will ignore. Test for the presence of such a line and then
1297  // parse it if we can.
1298 
1299  // There may also be additional continuation lines from future formats that
1300  // we ignore.
1301 
1302  // Advance index over all continuation lines first, before we might throw
1303  // any exceptions.
1304  int index2 = index;
1305  while (index < (int)file.GetLineCount() &&
1306  file.GetLine(index).StartsWith(continuation))
1307  ++index;
1308 
1309  if (index2 < index) {
1310  wxStringTokenizer toker { file.GetLine(index2++), wxT("\t") };
1311  auto token = toker.GetNextToken();
1312  if (token != continuation)
1313  throw BadFormatException{};
1314 
1315  token = toker.GetNextToken();
1316  double f0;
1317  if (!Internat::CompatibleToDouble(token, &f0))
1318  throw BadFormatException{};
1319 
1320  token = toker.GetNextToken();
1321  double f1;
1322  if (!Internat::CompatibleToDouble(token, &f1))
1323  throw BadFormatException{};
1324 
1325  sr.setFrequencies(f0, f1);
1326  }
1327 
1328  return LabelStruct{ sr, title };
1329 }
1330 
1331 void LabelStruct::Export(wxTextFile &file) const
1332 {
1333  file.AddLine(wxString::Format(wxT("%s\t%s\t%s"),
1334  Internat::ToString(getT0(), FLT_DIG),
1335  Internat::ToString(getT1(), FLT_DIG),
1336  title
1337  ));
1338 
1339  // Do we need more lines?
1340  auto f0 = selectedRegion.f0();
1341  auto f1 = selectedRegion.f1();
1344  return;
1345 
1346  // Write a \ character at the start of a second line,
1347  // so that earlier versions of Audacity ignore it.
1348  file.AddLine(wxString::Format(wxT("\\\t%s\t%s"),
1349  Internat::ToString(f0, FLT_DIG),
1350  Internat::ToString(f1, FLT_DIG)
1351  ));
1352 
1353  // Additional lines in future formats should also start with '\'.
1354 }
1355 
1357  double reg_t0, double reg_t1, const LabelTrack * WXUNUSED(parent)) const
1358 -> TimeRelations
1359 {
1360  bool retainLabels = false;
1361 
1362  wxASSERT(reg_t0 <= reg_t1);
1363  gPrefs->Read(wxT("/GUI/RetainLabels"), &retainLabels);
1364 
1365  if(retainLabels) {
1366 
1367  // Desired behavior for edge cases: The length of the selection is smaller
1368  // than the length of the label if the selection is within the label or
1369  // matching exactly a (region) label.
1370 
1371  if (reg_t0 < getT0() && reg_t1 > getT1())
1372  return SURROUNDS_LABEL;
1373  else if (reg_t1 < getT0())
1374  return BEFORE_LABEL;
1375  else if (reg_t0 > getT1())
1376  return AFTER_LABEL;
1377 
1378  else if (reg_t0 >= getT0() && reg_t0 <= getT1() &&
1379  reg_t1 >= getT0() && reg_t1 <= getT1())
1380  return WITHIN_LABEL;
1381 
1382  else if (reg_t0 >= getT0() && reg_t0 <= getT1())
1383  return BEGINS_IN_LABEL;
1384  else
1385  return ENDS_IN_LABEL;
1386 
1387  } else {
1388 
1389  // AWD: Desired behavior for edge cases: point labels bordered by the
1390  // selection are included within it. Region labels are included in the
1391  // selection to the extent that the selection covers them; specifically,
1392  // they're not included at all if the selection borders them, and they're
1393  // fully included if the selection covers them fully, even if it just
1394  // borders their endpoints. This is just one of many possible schemes.
1395 
1396  // The first test catches bordered point-labels and selected-through
1397  // region-labels; move it to third and selection edges become inclusive
1398  // WRT point-labels.
1399  if (reg_t0 <= getT0() && reg_t1 >= getT1())
1400  return SURROUNDS_LABEL;
1401  else if (reg_t1 <= getT0())
1402  return BEFORE_LABEL;
1403  else if (reg_t0 >= getT1())
1404  return AFTER_LABEL;
1405 
1406  // At this point, all point labels should have returned.
1407 
1408  else if (reg_t0 > getT0() && reg_t0 < getT1() &&
1409  reg_t1 > getT0() && reg_t1 < getT1())
1410  return WITHIN_LABEL;
1411 
1412  // Knowing that none of the other relations match simplifies remaining
1413  // tests
1414  else if (reg_t0 > getT0() && reg_t0 < getT1())
1415  return BEGINS_IN_LABEL;
1416  else
1417  return ENDS_IN_LABEL;
1418 
1419  }
1420 }
1421 
1428 ( LabelTrackHit &hit, int iLabel, int iEdge, bool bAllowSwapping, double fNewTime)
1429 {
1430  if( iLabel < 0 )
1431  return;
1432  LabelStruct &labelStruct = mLabels[ iLabel ];
1433 
1434  // Adjust the requested edge.
1435  bool flipped = labelStruct.AdjustEdge( iEdge, fNewTime );
1436  // If the edges did not swap, then we are done.
1437  if( ! flipped )
1438  return;
1439 
1440  // If swapping's not allowed we must also move the edge
1441  // we didn't move. Then we're done.
1442  if( !bAllowSwapping )
1443  {
1444  labelStruct.AdjustEdge( -iEdge, fNewTime );
1445  return;
1446  }
1447 
1448  // Swap our record of what we are dragging.
1449  std::swap( hit.mMouseOverLabelLeft, hit.mMouseOverLabelRight );
1450 }
1451 
1452 // If the index is for a real label, adjust its left and right boundary.
1453 void LabelTrack::MayMoveLabel( int iLabel, int iEdge, double fNewTime)
1454 {
1455  if( iLabel < 0 )
1456  return;
1457  mLabels[ iLabel ].MoveLabel( iEdge, fNewTime );
1458 }
1459 
1460 // Constrain function, as in processing/arduino.
1461 // returned value will be between min and max (inclusive).
1462 static int Constrain( int value, int min, int max )
1463 {
1464  wxASSERT( min <= max );
1465  int result=value;
1466  if( result < min )
1467  result=min;
1468  if( result > max )
1469  result=max;
1470  return result;
1471 }
1472 
1474 (LabelTrackHit &hit, const wxMouseEvent & evt,
1475  wxRect & r, const ZoomInfo &zoomInfo,
1476  SelectedRegion *newSel)
1477 {
1478  if(evt.LeftUp())
1479  {
1480  bool lupd = false, rupd = false;
1481  if( hit.mMouseOverLabelLeft >= 0 ) {
1482  auto &labelStruct = mLabels[ hit.mMouseOverLabelLeft ];
1483  lupd = labelStruct.updated;
1484  labelStruct.updated = false;
1485  }
1486  if( hit.mMouseOverLabelRight >= 0 ) {
1487  auto &labelStruct = mLabels[ hit.mMouseOverLabelRight ];
1488  rupd = labelStruct.updated;
1489  labelStruct.updated = false;
1490  }
1491 
1492  hit.mIsAdjustingLabel = false;
1493  hit.mMouseOverLabelLeft = -1;
1494  hit.mMouseOverLabelRight = -1;
1495  return lupd || rupd;
1496  }
1497 
1498  if(evt.Dragging())
1499  {
1500  //If we are currently adjusting a label,
1501  //just reset its value and redraw.
1502  // LL: Constrain to inside track rectangle for now. Should be changed
1503  // to allow scrolling while dragging labels
1504  int x = Constrain( evt.m_x + mxMouseDisplacement - r.x, 0, r.width);
1505 
1506  // If exactly one edge is selected we allow swapping
1507  bool bAllowSwapping =
1508  ( hit.mMouseOverLabelLeft >=0 ) !=
1509  ( hit.mMouseOverLabelRight >= 0);
1510  // If we're on the 'dot' and nowe're moving,
1511  // Though shift-down inverts that.
1512  // and if both edges the same, then we're always moving the label.
1513  bool bLabelMoving = hit.mbIsMoving;
1514  bLabelMoving ^= evt.ShiftDown();
1515  bLabelMoving |= ( hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight );
1516  double fNewX = zoomInfo.PositionToTime(x, 0);
1517  if( bLabelMoving )
1518  {
1519  MayMoveLabel( hit.mMouseOverLabelLeft, -1, fNewX );
1520  MayMoveLabel( hit.mMouseOverLabelRight, +1, fNewX );
1521  }
1522  else
1523  {
1524  MayAdjustLabel( hit, hit.mMouseOverLabelLeft, -1, bAllowSwapping, fNewX );
1525  MayAdjustLabel( hit, hit.mMouseOverLabelRight, +1, bAllowSwapping, fNewX );
1526  }
1527 
1528  if( mSelIndex >=0 )
1529  {
1530  //Set the selection region to be equal to
1531  //the NEW size of the label.
1532  *newSel = mLabels[mSelIndex].selectedRegion;
1533  }
1534  SortLabels( &hit );
1535  }
1536 
1537  return false;
1538 }
1539 
1540 void LabelTrack::HandleTextDragRelease(const wxMouseEvent & evt)
1541 {
1542  if(evt.LeftUp())
1543  {
1544 #if 0
1545  // AWD: Due to wxWidgets bug #7491 (fix not ported to 2.8 branch) we
1546  // should never write the primary selection. We can enable this block
1547  // when we move to the 3.0 branch (or if a fixed 2.8 version is released
1548  // and we can do a runtime version check)
1549 #if defined (__WXGTK__) && defined (HAVE_GTK)
1550  // On GTK, if we just dragged out a text selection, set the primary
1551  // selection
1553  wxTheClipboard->UsePrimarySelection(true);
1554  CopySelectedText();
1555  wxTheClipboard->UsePrimarySelection(false);
1556  }
1557 #endif
1558 #endif
1559 
1560  return;
1561  }
1562 
1563  if(evt.Dragging())
1564  {
1565  if (!mRightDragging)
1566  // Update drag end
1567  SetCurrentCursorPosition(evt.m_x);
1568 
1569  return;
1570  }
1571 
1572  if (evt.RightUp()) {
1573  if ((mSelIndex != -1) && OverTextBox(GetLabel(mSelIndex), evt.m_x, evt.m_y)) {
1574  // popup menu for editing
1575  ShowContextMenu();
1576  }
1577  }
1578 
1579  return;
1580 }
1581 
1583 (LabelTrackHit &hit, const wxMouseEvent & evt,
1584  const wxRect & r, const ZoomInfo &zoomInfo,
1585  SelectedRegion *WXUNUSED(newSel))
1586 {
1587  if (evt.ButtonDown())
1588  {
1589  //OverGlyph sets mMouseOverLabel to be the chosen label.
1590  OverGlyph(hit, evt.m_x, evt.m_y);
1591  hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
1592  ( hit.mEdge & 3 ) != 0;
1593 
1594  if (hit.mIsAdjustingLabel)
1595  {
1596  float t = 0.0;
1597  // We move if we hit the centre, we adjust one edge if we hit a chevron.
1598  // This is if we are moving just one edge.
1599  hit.mbIsMoving = (hit.mEdge & 4)!=0;
1600  // When we start dragging the label(s) we don't want them to jump.
1601  // so we calculate the displacement of the mouse from the drag center
1602  // and use that in subsequent dragging calculations. The mouse stays
1603  // at the same relative displacement throughout dragging.
1604 
1605  // However, if two label's edges are being dragged
1606  // then the displacement is relative to the initial average
1607  // position of them, and in that case there can be a jump of at most
1608  // a few pixels to bring the two label boundaries to exactly the same
1609  // position when we start dragging.
1610 
1611  // Dragging of three label edges at the same time is not supported (yet).
1612  if( ( hit.mMouseOverLabelRight >= 0 ) &&
1613  ( hit.mMouseOverLabelLeft >= 0 )
1614  )
1615  {
1616  t = (mLabels[ hit.mMouseOverLabelRight ].getT1() +
1617  mLabels[ hit.mMouseOverLabelLeft ].getT0()) / 2.0f;
1618  // If we're moving two edges, then it's a move (label size preserved)
1619  // if both edges are the same label, and it's an adjust (label sizes change)
1620  // if we're on a boundary between two different labels.
1621  hit.mbIsMoving =
1623  }
1624  else if( hit.mMouseOverLabelRight >=0)
1625  {
1626  t = mLabels[ hit.mMouseOverLabelRight ].getT1();
1627  }
1628  else if( hit.mMouseOverLabelLeft >=0)
1629  {
1630  t = mLabels[ hit.mMouseOverLabelLeft ].getT0();
1631  }
1632  mxMouseDisplacement = zoomInfo.TimeToPosition(t, r.x) - evt.m_x;
1633  }
1634  }
1635 }
1636 
1637 void LabelTrack::HandleTextClick(const wxMouseEvent & evt,
1638  const wxRect & r, const ZoomInfo &zoomInfo,
1639  SelectedRegion *newSel)
1640 {
1641  static_cast<void>(r);//compiler food.
1642  static_cast<void>(zoomInfo);//compiler food.
1643  if (evt.ButtonDown())
1644  {
1645 
1646  mSelIndex = OverATextBox(evt.m_x, evt.m_y);
1647  if (mSelIndex != -1) {
1648  auto &labelStruct = mLabels[mSelIndex];
1649  *newSel = labelStruct.selectedRegion;
1650 
1651  if (evt.LeftDown()) {
1652  // Find the NEW drag end
1653  auto position = FindCurrentCursorPosition(evt.m_x);
1654 
1655  // Anchor shift-drag at the farther end of the previous highlight
1656  // that is farther from the click, on Mac, for consistency with
1657  // its text editors, but on the others, re-use the previous
1658  // anchor.
1659  if (evt.ShiftDown()) {
1660 #ifdef __WXMAC__
1661  // Set the drag anchor at the end of the previous selection
1662  // that is farther from the NEW drag end
1663  if (abs(position - mCurrentCursorPos) >
1664  abs(position - mInitialCursorPos))
1666 #else
1667  // mInitialCursorPos remains as before
1668 #endif
1669  }
1670  else
1671  mInitialCursorPos = position;
1672 
1673  mCurrentCursorPos = position;
1674 
1675  mDrawCursor = true;
1676  mRightDragging = false;
1677  }
1678  else
1679  // Actually this might be right or middle down
1680  mRightDragging = true;
1681 
1682  // reset the highlight indicator
1683  wxRect highlightedRect;
1684  {
1685  int xpos1, xpos2;
1686  CalcHighlightXs(&xpos1, &xpos2);
1687 
1688  wxASSERT(mFontHeight >= 0); // should have been set up while drawing
1689  // the rectangle of highlighted area
1690  highlightedRect = {
1691  xpos1, labelStruct.y - mFontHeight / 2,
1692  (int)(xpos2 - xpos1 + 0.5), mFontHeight
1693  };
1694  }
1695 
1696  // Middle click on GTK: paste from primary selection
1697 #if defined(__WXGTK__) && (HAVE_GTK)
1698  if (evt.MiddleDown()) {
1699  // Check for a click outside of the selected label's text box; in this
1700  // case PasteSelectedText() will start a NEW label at the click
1701  // location
1702  if (!OverTextBox(&labelStruct, evt.m_x, evt.m_y))
1703  mSelIndex = -1;
1704  double t = zoomInfo.PositionToTime(evt.m_x, r.x);
1705  *newSel = SelectedRegion(t, t);
1706  }
1707 #endif
1708  }
1709 #if defined(__WXGTK__) && (HAVE_GTK)
1710  if (evt.MiddleDown()) {
1711  // Paste text, making a NEW label if none is selected.
1712  wxTheClipboard->UsePrimarySelection(true);
1713  PasteSelectedText(newSel->t0(), newSel->t1());
1714  wxTheClipboard->UsePrimarySelection(false);
1715  }
1716 #endif
1717  }
1718 }
1719 
1720 // Check for keys that we will process
1721 bool LabelTrack::DoCaptureKey(wxKeyEvent & event)
1722 {
1723  // Check for modifiers and only allow shift
1724  int mods = event.GetModifiers();
1725  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1726  return false;
1727  }
1728 
1729  // Always capture the navigation keys, if we have any labels
1730  auto code = event.GetKeyCode();
1731  if ((code == WXK_TAB || code == WXK_NUMPAD_TAB) &&
1732  !mLabels.empty())
1733  return true;
1734 
1735  if (mSelIndex >= 0) {
1736  if (IsGoodLabelEditKey(event)) {
1737  return true;
1738  }
1739  }
1740  else {
1741  bool typeToCreateLabel;
1742  gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, true);
1743  if (IsGoodLabelFirstKey(event) && typeToCreateLabel) {
1744  AudacityProject * pProj = GetActiveProject();
1745 
1746 
1747 // The commented out code can prevent label creation, causing bug 1551
1748 // We should only be in DoCaptureKey IF this label track has focus,
1749 // and in that case creating a Label is the expected/intended thing.
1750 #if 0
1751  // If we're playing, don't capture if the selection is the same as the
1752  // playback region (this helps prevent label track creation from
1753  // stealing unmodified kbd. shortcuts)
1754  if (pProj->GetAudioIOToken() > 0 &&
1756  {
1757  double t0, t1;
1758  pProj->GetPlayRegion(&t0, &t1);
1759  if (pProj->mViewInfo.selectedRegion.t0() == t0 &&
1760  pProj->mViewInfo.selectedRegion.t1() == t1) {
1761  return false;
1762  }
1763  }
1764 #endif
1765 
1766  // If there's a label there already don't capture
1768  pProj->mViewInfo.selectedRegion.t1()) != wxNOT_FOUND ) {
1769  return false;
1770  }
1771 
1772  return true;
1773  }
1774  }
1775 
1776  return false;
1777 }
1778 
1779 unsigned LabelTrack::CaptureKey(wxKeyEvent & event, ViewInfo &, wxWindow *)
1780 {
1781  event.Skip(!DoCaptureKey(event));
1782  return RefreshCode::RefreshNone;
1783 }
1784 
1785 unsigned LabelTrack::KeyDown(wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *WXUNUSED(pParent))
1786 {
1787  double bkpSel0 = viewInfo.selectedRegion.t0(),
1788  bkpSel1 = viewInfo.selectedRegion.t1();
1789 
1790  AudacityProject *const pProj = GetActiveProject();
1791 
1792  // Pass keystroke to labeltrack's handler and add to history if any
1793  // updates were done
1794  if (OnKeyDown(viewInfo.selectedRegion, event)) {
1795  pProj->PushState(_("Modified Label"),
1796  _("Label Edit"),
1798  }
1799 
1800  // Make sure caret is in view
1801  int x;
1802  if (CalcCursorX(&x)) {
1803  pProj->GetTrackPanel()->ScrollIntoView(x);
1804  }
1805 
1806  // If selection modified, refresh
1807  // Otherwise, refresh track display if the keystroke was handled
1808  if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1809  bkpSel1 != viewInfo.selectedRegion.t1())
1810  return RefreshCode::RefreshAll;
1811  else if (!event.GetSkipped())
1812  return RefreshCode::RefreshCell;
1813 
1814  return RefreshCode::RefreshNone;
1815 }
1816 
1817 unsigned LabelTrack::Char(wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *)
1818 {
1819  double bkpSel0 = viewInfo.selectedRegion.t0(),
1820  bkpSel1 = viewInfo.selectedRegion.t1();
1821  // Pass keystroke to labeltrack's handler and add to history if any
1822  // updates were done
1823 
1824  AudacityProject *const pProj = GetActiveProject();
1825 
1826  if (OnChar(viewInfo.selectedRegion, event))
1827  pProj->PushState(_("Modified Label"),
1828  _("Label Edit"),
1830 
1831  // If selection modified, refresh
1832  // Otherwise, refresh track display if the keystroke was handled
1833  if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1834  bkpSel1 != viewInfo.selectedRegion.t1())
1835  return RefreshCode::RefreshAll;
1836  else if (!event.GetSkipped())
1837  return RefreshCode::RefreshCell;
1838 
1839  return RefreshCode::RefreshNone;
1840 }
1841 
1843 bool LabelTrack::OnKeyDown(SelectedRegion &newSel, wxKeyEvent & event)
1844 {
1845  // Only track true changes to the label
1846  bool updated = false;
1847 
1848  // Cache the keycode
1849  int keyCode = event.GetKeyCode();
1850  const int mods = event.GetModifiers();
1851 
1852  // Check for modifiers and only allow shift
1853  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1854  event.Skip();
1855  return updated;
1856  }
1857 
1858  // All editing keys are only active if we're currently editing a label
1859  if (mSelIndex >= 0) {
1860  auto &labelStruct = mLabels[mSelIndex];
1861  auto &title = labelStruct.title;
1862  switch (keyCode) {
1863 
1864  case WXK_BACK:
1865  {
1866  int len = title.Length();
1867 
1868  //IF the label is not blank THEN get rid of a letter or letters according to cursor position
1869  if (len > 0)
1870  {
1871  // IF there are some highlighted letters, THEN DELETE them
1874  else
1875  {
1876  // DELETE one letter
1877  if (mCurrentCursorPos > 0) {
1878  title.Remove(mCurrentCursorPos-1, 1);
1880  }
1881  }
1882  }
1883  else
1884  {
1885  // ELSE no text in text box, so DELETE whole label.
1887  }
1889  updated = true;
1890  }
1891  break;
1892 
1893  case WXK_DELETE:
1894  case WXK_NUMPAD_DELETE:
1895  {
1896  int len = title.Length();
1897 
1898  //If the label is not blank get rid of a letter according to cursor position
1899  if (len > 0)
1900  {
1901  // if there are some highlighted letters, DELETE them
1904  else
1905  {
1906  // DELETE one letter
1907  if (mCurrentCursorPos < len) {
1908  title.Remove(mCurrentCursorPos, 1);
1909  }
1910  }
1911  }
1912  else
1913  {
1914  // DELETE whole label if no text in text box
1916  }
1918  updated = true;
1919  }
1920  break;
1921 
1922  case WXK_HOME:
1923  case WXK_NUMPAD_HOME:
1924  // Move cursor to beginning of label
1925  mCurrentCursorPos = 0;
1926  if (mods == wxMOD_SHIFT)
1927  ;
1928  else
1930  break;
1931 
1932  case WXK_END:
1933  case WXK_NUMPAD_END:
1934  // Move cursor to end of label
1935  mCurrentCursorPos = (int)title.length();
1936  if (mods == wxMOD_SHIFT)
1937  ;
1938  else
1940  break;
1941 
1942  case WXK_LEFT:
1943  case WXK_NUMPAD_LEFT:
1944  // Moving cursor left
1945  if (mCurrentCursorPos > 0) {
1947  if (mods == wxMOD_SHIFT)
1948  ;
1949  else
1952  }
1953  break;
1954 
1955  case WXK_RIGHT:
1956  case WXK_NUMPAD_RIGHT:
1957  // Moving cursor right
1958  if (mCurrentCursorPos < (int)title.length()) {
1960  if (mods == wxMOD_SHIFT)
1961  ;
1962  else
1965  }
1966  break;
1967 
1968  case WXK_RETURN:
1969  case WXK_NUMPAD_ENTER:
1970 
1971  case WXK_ESCAPE:
1972  if (mRestoreFocus >= 0) {
1973  TrackListIterator iter(GetActiveProject()->GetTracks());
1974  Track *track = iter.First();
1975  while (track && mRestoreFocus--)
1976  track = iter.Next();
1977  if (track)
1979  mRestoreFocus = -1;
1980  }
1981  mSelIndex = -1;
1982  break;
1983 
1984  case WXK_TAB:
1985  case WXK_NUMPAD_TAB:
1986  if (event.ShiftDown()) {
1987  mSelIndex--;
1988  } else {
1989  mSelIndex++;
1990  }
1991 
1992  mSelIndex = (mSelIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
1993  {
1994  LabelStruct &newLabel = mLabels[mSelIndex];
1995  mCurrentCursorPos = newLabel.title.Length();
1997  //Set the selection region to be equal to the selection bounds of the tabbed-to label.
1998  newSel = newLabel.selectedRegion;
1999  }
2000  break;
2001 
2002  case '\x10': // OSX
2003  case WXK_MENU:
2004  case WXK_WINDOWS_MENU:
2005  ShowContextMenu();
2006  break;
2007 
2008  default:
2009  if (!IsGoodLabelEditKey(event)) {
2010  event.Skip();
2011  }
2012  break;
2013  }
2014  }
2015  else
2016  {
2017  switch (keyCode) {
2018 
2019  case WXK_TAB:
2020  case WXK_NUMPAD_TAB:
2021  if (!mLabels.empty()) {
2022  int len = (int) mLabels.size();
2023  if (event.ShiftDown()) {
2024  mSelIndex = len - 1;
2025  if (newSel.t0() > mLabels[0].getT0()) {
2026  while (mSelIndex >= 0 &&
2027  mLabels[mSelIndex].getT0() > newSel.t0()) {
2028  mSelIndex--;
2029  }
2030  }
2031  } else {
2032  mSelIndex = 0;
2033  if (newSel.t0() < mLabels[len - 1].getT0()) {
2034  while (mSelIndex < len &&
2035  mLabels[mSelIndex].getT0() < newSel.t0()) {
2036  mSelIndex++;
2037  }
2038  }
2039  }
2040 
2041  if (mSelIndex >= 0 && mSelIndex < len) {
2042  const auto &labelStruct = mLabels[mSelIndex];
2043  mCurrentCursorPos = labelStruct.title.Length();
2045  //Set the selection region to be equal to the selection bounds of the tabbed-to label.
2046  newSel = labelStruct.selectedRegion;
2047  }
2048  else {
2049  mSelIndex = -1;
2050  }
2051  }
2052  break;
2053 
2054  default:
2055  if (!IsGoodLabelFirstKey(event)) {
2056  event.Skip();
2057  }
2058  break;
2059  }
2060  }
2061 
2062  // Make sure the caret is visible
2063  mDrawCursor = true;
2064 
2065  return updated;
2066 }
2067 
2070 bool LabelTrack::OnChar(SelectedRegion &WXUNUSED(newSel), wxKeyEvent & event)
2071 {
2072  // Check for modifiers and only allow shift.
2073  //
2074  // We still need to check this or we will eat the top level menu accelerators
2075  // on Windows if our capture or key down handlers skipped the event.
2076  const int mods = event.GetModifiers();
2077  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
2078  event.Skip();
2079  return false;
2080  }
2081 
2082  // Only track true changes to the label
2083  bool updated = false;
2084 
2085  // Cache the character
2086  wxChar charCode = event.GetUnicodeKey();
2087 
2088  // Skip if it's not a valid unicode character or a control character
2089  if (charCode == 0 || wxIscntrl(charCode)) {
2090  event.Skip();
2091  return false;
2092  }
2093 
2094  // If we've reached this point and aren't currently editing, add NEW label
2095  if (mSelIndex < 0) {
2096  // Don't create a NEW label for a space
2097  if (wxIsspace(charCode)) {
2098  event.Skip();
2099  return false;
2100  }
2101  bool useDialog;
2103  gPrefs->Read(wxT("/Gui/DialogForNameNewLabel"), &useDialog, false);
2104  if (useDialog) {
2105  wxString title;
2106  if (p->DialogForLabelName(charCode, title) == wxID_CANCEL) {
2107  return false;
2108  }
2109  SetSelected(true);
2110  AddLabel(p->mViewInfo.selectedRegion, title, -2);
2111  p->PushState(_("Added label"), _("Label"));
2112  return false;
2113  }
2114  else {
2115  SetSelected(true);
2117  p->PushState(_("Added label"), _("Label"));
2118  }
2119  }
2120 
2121  //
2122  // Now we are definitely in a label; append the incoming character
2123  //
2124 
2125  auto &labelStruct = mLabels[mSelIndex];
2126  auto &title = labelStruct.title;
2127 
2128  // Test if cursor is in the end of string or not
2131 
2132  if (mCurrentCursorPos < (int)title.length()) {
2133  // Get substring on the righthand side of cursor
2134  wxString rightPart = title.Mid(mCurrentCursorPos);
2135  // Set title to substring on the lefthand side of cursor
2136  title = title.Left(mCurrentCursorPos);
2137  //append charcode
2138  title += charCode;
2139  //append the right part substring
2140  title += rightPart;
2141  }
2142  else
2143  //append charCode
2144  title += charCode;
2145 
2146  //moving cursor position forward
2148  updated = true;
2149 
2150  // Make sure the caret is visible
2151  mDrawCursor = true;
2152 
2153  return updated;
2154 }
2155 
2157 {
2158  wxWindow *parent = wxWindow::FindFocus();
2159 
2160  {
2161  wxMenu menu;
2162  menu.Bind(wxEVT_MENU, &LabelTrack::OnContextMenu, this);
2163 
2164  menu.Append(OnCutSelectedTextID, _("Cu&t"));
2165  menu.Append(OnCopySelectedTextID, _("&Copy"));
2166  menu.Append(OnPasteSelectedTextID, _("&Paste"));
2167  menu.Append(OnDeleteSelectedLabelID, _("&Delete Label"));
2168  menu.Append(OnEditSelectedLabelID, _("&Edit..."));
2169 
2170  menu.Enable(OnCutSelectedTextID, IsTextSelected());
2171  menu.Enable(OnCopySelectedTextID, IsTextSelected());
2173  menu.Enable(OnDeleteSelectedLabelID, true);
2174  menu.Enable(OnEditSelectedLabelID, true);
2175 
2176  wxASSERT(mSelIndex >= 0);
2177  const LabelStruct *ls = GetLabel(mSelIndex);
2178 
2179  wxClientDC dc(parent);
2180 
2181  if (msFont.Ok())
2182  {
2183  dc.SetFont(msFont);
2184  }
2185 
2186  int x = 0;
2187  bool success = CalcCursorX(&x);
2188  wxASSERT(success);
2189  static_cast<void>(success); // Suppress unused variable warning if debug mode is disabled
2190 
2191  parent->PopupMenu(&menu, x, ls->y + (mIconHeight / 2) - 1);
2192  }
2193 }
2194 
2195 void LabelTrack::OnContextMenu(wxCommandEvent & evt)
2196 {
2198 
2199  switch (evt.GetId())
2200  {
2202  case OnCutSelectedTextID:
2203  if (CutSelectedText())
2204  {
2205  p->PushState(_("Modified Label"),
2206  _("Label Edit"),
2208  }
2209  break;
2210 
2212  case OnCopySelectedTextID:
2213  CopySelectedText();
2214  break;
2215 
2217  case OnPasteSelectedTextID:
2218  if (PasteSelectedText(p->GetSel0(), p->GetSel1()))
2219  {
2220  p->PushState(_("Modified Label"),
2221  _("Label Edit"),
2223  }
2224  break;
2225 
2227  case OnDeleteSelectedLabelID: {
2228  int ndx = GetLabelIndex(p->GetSel0(), p->GetSel1());
2229  if (ndx != -1)
2230  {
2231  DeleteLabel(ndx);
2232  p->PushState(_("Deleted Label"),
2233  _("Label Edit"),
2235  }
2236  }
2237  break;
2238 
2239  case OnEditSelectedLabelID: {
2240  int ndx = GetLabelIndex(p->GetSel0(), p->GetSel1());
2241  if (ndx != -1)
2242  p->DoEditLabels(this, ndx);
2243  }
2244  break;
2245  }
2246 }
2247 
2249 {
2250  wxString left, right;
2251 
2252  int init = mInitialCursorPos;
2253  int cur = mCurrentCursorPos;
2254  if (init > cur)
2255  std::swap(init, cur);
2256 
2257  auto &labelStruct = mLabels[mSelIndex];
2258  auto &title = labelStruct.title;
2259 
2260  if (init > 0)
2261  left = title.Left(init);
2262 
2263  if (cur < (int)title.Length())
2264  right = title.Mid(cur);
2265 
2266  title = left + right;
2267  mInitialCursorPos = mCurrentCursorPos = left.Length();
2268 }
2269 
2271 {
2272  mSelIndex = -1;
2273 }
2274 
2276 {
2277  return (mSelIndex >= 0 && mSelIndex < (int)mLabels.size());
2278 }
2279 
2281 void LabelTrack::Export(wxTextFile & f) const
2282 {
2283  // PRL: to do: export other selection fields
2284  for (auto &labelStruct: mLabels)
2285  labelStruct.Export(f);
2286 }
2287 
2289 void LabelTrack::Import(wxTextFile & in)
2290 {
2291  int lines = in.GetLineCount();
2292 
2293  mLabels.clear();
2294  mLabels.reserve(lines);
2295 
2296  //Currently, we expect a tag file to have two values and a label
2297  //on each line. If the second token is not a number, we treat
2298  //it as a single-value label.
2299  bool error = false;
2300  for (int index = 0; index < lines;) {
2301  try {
2302  // Let LabelStruct::Import advance index
2303  LabelStruct l { LabelStruct::Import(in, index) };
2304  mLabels.push_back(l);
2305  }
2306  catch(const LabelStruct::BadFormatException&) { error = true; }
2307  }
2308  if (error)
2309  ::AudacityMessageBox( _("One or more saved labels could not be read.") );
2310  SortLabels();
2311 }
2312 
2313 bool LabelTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
2314 {
2315  if (!wxStrcmp(tag, wxT("label"))) {
2316 
2317  SelectedRegion selectedRegion;
2318  wxString title;
2319 
2320  // loop through attrs, which is a null-terminated list of
2321  // attribute-value pairs
2322  while(*attrs) {
2323  const wxChar *attr = *attrs++;
2324  const wxChar *value = *attrs++;
2325 
2326  if (!value)
2327  break;
2328 
2329  const wxString strValue = value;
2330  // Bug 1905 was about long label strings.
2331  if (!XMLValueChecker::IsGoodLongString(strValue))
2332  {
2333  return false;
2334  }
2335 
2336  if (selectedRegion.HandleXMLAttribute(attr, value, wxT("t"), wxT("t1")))
2337  ;
2338  else if (!wxStrcmp(attr, wxT("title")))
2339  title = strValue;
2340 
2341  } // while
2342 
2343  // Handle files created by Audacity 1.1. Labels in Audacity 1.1
2344  // did not have separate start- and end-times.
2345  // PRL: this superfluous now, given class SelectedRegion's internal
2346  // consistency guarantees
2347  //if (selectedRegion.t1() < 0)
2348  // selectedRegion.collapseToT0();
2349 
2350  LabelStruct l { selectedRegion, title };
2351  mLabels.push_back(l);
2352 
2353  return true;
2354  }
2355  else if (!wxStrcmp(tag, wxT("labeltrack"))) {
2356  long nValue = -1;
2357  while (*attrs) {
2358  const wxChar *attr = *attrs++;
2359  const wxChar *value = *attrs++;
2360 
2361  if (!value)
2362  return true;
2363 
2364  const wxString strValue = value;
2365  if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue))
2366  mName = strValue;
2367  else if (!wxStrcmp(attr, wxT("numlabels")) &&
2368  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
2369  {
2370  if (nValue < 0)
2371  {
2372  wxLogWarning(wxT("Project shows negative number of labels: %d"), nValue);
2373  return false;
2374  }
2375  mLabels.clear();
2376  mLabels.reserve(nValue);
2377  }
2378  else if (!wxStrcmp(attr, wxT("height")) &&
2379  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
2380  SetHeight(nValue);
2381  else if (!wxStrcmp(attr, wxT("minimized")) &&
2382  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
2383  SetMinimized(nValue != 0);
2384  else if (!wxStrcmp(attr, wxT("isSelected")) &&
2385  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
2386  this->SetSelected(nValue != 0);
2387  }
2388 
2389  return true;
2390  }
2391 
2392  return false;
2393 }
2394 
2396 {
2397  if (!wxStrcmp(tag, wxT("label")))
2398  return this;
2399  else
2400  return NULL;
2401 }
2402 
2403 void LabelTrack::WriteXML(XMLWriter &xmlFile) const
2404 // may throw
2405 {
2406  int len = mLabels.size();
2407 
2408  xmlFile.StartTag(wxT("labeltrack"));
2409  xmlFile.WriteAttr(wxT("name"), mName);
2410  xmlFile.WriteAttr(wxT("numlabels"), len);
2411  xmlFile.WriteAttr(wxT("height"), this->GetActualHeight());
2412  xmlFile.WriteAttr(wxT("minimized"), this->GetMinimized());
2413  xmlFile.WriteAttr(wxT("isSelected"), this->GetSelected());
2414 
2415  for (auto &labelStruct: mLabels) {
2416  xmlFile.StartTag(wxT("label"));
2417  labelStruct.getSelectedRegion()
2418  .WriteXMLAttributes(xmlFile, wxT("t"), wxT("t1"));
2419  // PRL: to do: write other selection fields
2420  xmlFile.WriteAttr(wxT("title"), labelStruct.title);
2421  xmlFile.EndTag(wxT("label"));
2422  }
2423 
2424  xmlFile.EndTag(wxT("labeltrack"));
2425 }
2426 
2427 #if LEGACY_PROJECT_FILE_SUPPORT
2428 bool LabelTrack::Load(wxTextFile * in, DirManager * dirManager)
2429 {
2430  if (in->GetNextLine() != wxT("NumMLabels"))
2431  return false;
2432 
2433  unsigned long len;
2434  if (!(in->GetNextLine().ToULong(&len)))
2435  return false;
2436 
2437  mLabels.clear();
2438  mLabels.reserve(len);
2439 
2440  for (int i = 0; i < len; i++) {
2441  double t0;
2442  if (!Internat::CompatibleToDouble(in->GetNextLine(), &t0))
2443  return false;
2444  // Legacy file format does not include label end-times.
2445  // PRL: nothing NEW to do, legacy file support
2446  mLabels.push_back(LabelStruct {
2447  SelectedRegion{ t0, t0 }, in->GetNextLine()
2448  });
2449  }
2450 
2451  if (in->GetNextLine() != wxT("MLabelsEnd"))
2452  return false;
2453  SortLabels();
2454  return true;
2455 }
2456 
2457 bool LabelTrack::Save(wxTextFile * out, bool overwrite)
2458 {
2459  out->AddLine(wxT("NumMLabels"));
2460  int len = mLabels.size();
2461  out->AddLine(wxString::Format(wxT("%d"), len));
2462 
2463  for (auto pLabel : mLabels) {
2464  const auto &labelStruct = *pLabel;
2465  out->AddLine(wxString::Format(wxT("%lf"), labelStruct.selectedRegion.mT0));
2466  out->AddLine(labelStruct.title);
2467  }
2468  out->AddLine(wxT("MLabelsEnd"));
2469 
2470  return true;
2471 }
2472 #endif
2473 
2474 Track::Holder LabelTrack::Cut(double t0, double t1)
2475 {
2476  auto tmp = Copy(t0, t1);
2477 
2478  Clear(t0, t1);
2479 
2480  return tmp;
2481 }
2482 
2483 #if 0
2484 Track::Holder LabelTrack::SplitCut(double t0, double t1)
2485 {
2486  // SplitCut() == Copy() + SplitDelete()
2487 
2488  Track::Holder tmp = Copy(t0, t1);
2489 
2490  if (!SplitDelete(t0, t1))
2491  return {};
2492 
2493  return tmp;
2494 }
2495 #endif
2496 
2497 Track::Holder LabelTrack::Copy(double t0, double t1, bool) const
2498 {
2499  auto tmp = std::make_unique<LabelTrack>(GetDirManager());
2500  const auto lt = static_cast<LabelTrack*>(tmp.get());
2501 
2502  for (auto &labelStruct: mLabels) {
2503  LabelStruct::TimeRelations relation =
2504  labelStruct.RegionRelation(t0, t1, this);
2505  if (relation == LabelStruct::SURROUNDS_LABEL) {
2506  LabelStruct l {
2507  labelStruct.selectedRegion,
2508  labelStruct.getT0() - t0,
2509  labelStruct.getT1() - t0,
2510  labelStruct.title
2511  };
2512  lt->mLabels.push_back(l);
2513  }
2514  else if (relation == LabelStruct::WITHIN_LABEL) {
2515  LabelStruct l {
2516  labelStruct.selectedRegion,
2517  0,
2518  t1-t0,
2519  labelStruct.title
2520  };
2521  lt->mLabels.push_back(l);
2522  }
2523  else if (relation == LabelStruct::BEGINS_IN_LABEL) {
2524  LabelStruct l {
2525  labelStruct.selectedRegion,
2526  0,
2527  labelStruct.getT1() - t0,
2528  labelStruct.title
2529  };
2530  lt->mLabels.push_back(l);
2531  }
2532  else if (relation == LabelStruct::ENDS_IN_LABEL) {
2533  LabelStruct l {
2534  labelStruct.selectedRegion,
2535  labelStruct.getT0() - t0,
2536  t1 - t0,
2537  labelStruct.title
2538  };
2539  lt->mLabels.push_back(l);
2540  }
2541  }
2542  lt->mClipLen = (t1 - t0);
2543 
2544  // This std::move is needed to "upcast" the pointer type
2545  return std::move(tmp);
2546 }
2547 
2548 
2549 bool LabelTrack::PasteOver(double t, const Track * src)
2550 {
2551  if (src->GetKind() != Track::Label)
2552  // THROW_INCONSISTENCY_EXCEPTION; // ?
2553  return false;
2554 
2555  int len = mLabels.size();
2556  int pos = 0;
2557 
2558  while (pos < len && mLabels[pos].getT0() < t)
2559  pos++;
2560 
2561  auto sl = static_cast<const LabelTrack *>(src);
2562  for (auto &labelStruct: sl->mLabels) {
2563  LabelStruct l {
2564  labelStruct.selectedRegion,
2565  labelStruct.getT0() + t,
2566  labelStruct.getT1() + t,
2567  labelStruct.title
2568  };
2569  mLabels.insert(mLabels.begin() + pos++, l);
2570  }
2571 
2572  return true;
2573 }
2574 
2575 void LabelTrack::Paste(double t, const Track *src)
2576 {
2577  if (src->GetKind() != Track::Label)
2578  // THROW_INCONSISTENCY_EXCEPTION; // ?
2579  return;
2580 
2581  LabelTrack *lt = (LabelTrack *)src;
2582 
2583  double shiftAmt = lt->mClipLen > 0.0 ? lt->mClipLen : lt->GetEndTime();
2584 
2585  ShiftLabelsOnInsert(shiftAmt, t);
2586  PasteOver(t, src);
2587 }
2588 
2589 // This repeats the labels in a time interval a specified number of times.
2590 bool LabelTrack::Repeat(double t0, double t1, int n)
2591 {
2592  // Sanity-check the arguments
2593  if (n < 0 || t1 < t0)
2594  return false;
2595 
2596  double tLen = t1 - t0;
2597 
2598  // Insert space for the repetitions
2599  ShiftLabelsOnInsert(tLen * n, t1);
2600 
2601  // mLabels may resize as we iterate, so use subscripting
2602  for (unsigned int i = 0; i < mLabels.size(); ++i)
2603  {
2604  LabelStruct::TimeRelations relation =
2605  mLabels[i].RegionRelation(t0, t1, this);
2606  if (relation == LabelStruct::SURROUNDS_LABEL)
2607  {
2608  // Label is completely inside the selection; duplicate it in each
2609  // repeat interval
2610  unsigned int pos = i; // running label insertion position in mLabels
2611 
2612  for (int j = 1; j <= n; j++)
2613  {
2614  const LabelStruct &label = mLabels[i];
2615  LabelStruct l {
2616  label.selectedRegion,
2617  label.getT0() + j * tLen,
2618  label.getT1() + j * tLen,
2619  label.title
2620  };
2621 
2622  // Figure out where to insert
2623  while (pos < mLabels.size() &&
2624  mLabels[pos].getT0() < l.getT0())
2625  pos++;
2626  mLabels.insert(mLabels.begin() + pos, l);
2627  }
2628  }
2629  else if (relation == LabelStruct::BEGINS_IN_LABEL)
2630  {
2631  // Label ends inside the selection; ShiftLabelsOnInsert() hasn't touched
2632  // it, and we need to extend it through to the last repeat interval
2633  mLabels[i].selectedRegion.moveT1(n * tLen);
2634  }
2635 
2636  // Other cases have already been handled by ShiftLabelsOnInsert()
2637  }
2638 
2639  return true;
2640 }
2641 
2642 void LabelTrack::Silence(double t0, double t1)
2643 {
2644  int len = mLabels.size();
2645 
2646  // mLabels may resize as we iterate, so use subscripting
2647  for (int i = 0; i < len; ++i) {
2648  LabelStruct::TimeRelations relation =
2649  mLabels[i].RegionRelation(t0, t1, this);
2650  if (relation == LabelStruct::WITHIN_LABEL)
2651  {
2652  // Split label around the selection
2653  const LabelStruct &label = mLabels[i];
2654  LabelStruct l {
2655  label.selectedRegion,
2656  t1,
2657  label.getT1(),
2658  label.title
2659  };
2660 
2661  mLabels[i].selectedRegion.setT1(t0);
2662 
2663  // This might not be the right place to insert, but we sort at the end
2664  ++i;
2665  mLabels.insert(mLabels.begin() + i, l);
2666  }
2667  else if (relation == LabelStruct::ENDS_IN_LABEL)
2668  {
2669  // Beginning of label to selection end
2670  mLabels[i].selectedRegion.setT0(t1);
2671  }
2672  else if (relation == LabelStruct::BEGINS_IN_LABEL)
2673  {
2674  // End of label to selection beginning
2675  mLabels[i].selectedRegion.setT1(t0);
2676  }
2677  else if (relation == LabelStruct::SURROUNDS_LABEL)
2678  {
2679  DeleteLabel( i );
2680  len--;
2681  i--;
2682  }
2683  }
2684 
2685  SortLabels();
2686 }
2687 
2688 void LabelTrack::InsertSilence(double t, double len)
2689 {
2690  for (auto &labelStruct: mLabels) {
2691  double t0 = labelStruct.getT0();
2692  double t1 = labelStruct.getT1();
2693  if (t0 >= t)
2694  t0 += len;
2695 
2696  if (t1 >= t)
2697  t1 += len;
2698  labelStruct.selectedRegion.setTimes(t0, t1);
2699  }
2700 }
2701 
2703 {
2704  return mLabels.size();
2705 }
2706 
2707 const LabelStruct *LabelTrack::GetLabel(int index) const
2708 {
2709  return &mLabels[index];
2710 }
2711 
2712 int LabelTrack::GetLabelIndex(double t, double t1)
2713 {
2714  //We'd have liked to have times in terms of samples,
2715  //because then we're doing an intrger comparison.
2716  //Never mind. Instead we look for near enough.
2717  //This level of (in)accuracy is only a problem if we
2718  //deal with sounds in the MHz range.
2719  const double delta = 1.0e-7;
2720  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
2721  if( fabs( labelStruct.getT0() - t ) > delta )
2722  continue;
2723  if( fabs( labelStruct.getT1() - t1 ) > delta )
2724  continue;
2725  return i;
2726  }}
2727 
2728  return wxNOT_FOUND;
2729 }
2730 
2731 
2732 // restoreFocus of -1 is the default, and sets the focus to this label.
2733 // restoreFocus of -2 or other value leaves the focus unchanged.
2734 // restoreFocus >= 0 will later cause focus to move to that track.
2735 int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
2736  const wxString &title, int restoreFocus)
2737 {
2738  LabelStruct l { selectedRegion, title };
2739  mInitialCursorPos = mCurrentCursorPos = title.length();
2740 
2741  int len = mLabels.size();
2742  int pos = 0;
2743 
2744  while (pos < len && mLabels[pos].getT0() < selectedRegion.t0())
2745  pos++;
2746 
2747  mLabels.insert(mLabels.begin() + pos, l);
2748 
2749  // restoreFocus is -2 e.g. from Nyquist label creation, when we should not
2750  // even lose the focus and open the label to edit in the first place.
2751  // -1 means we don't need to restore it to anywhere.
2752  // 0 or above is the track to restore to afetr editing the label is complete.
2753  if( restoreFocus >= -1 )
2754  mSelIndex = pos;
2755 
2756  // Make sure the caret is visible
2757  //
2758  // LLL: The cursor will not be drawn when the first label
2759  // is added since mDrawCursor will be false. Presumably,
2760  // if the user adds a label, then a cursor should be drawn
2761  // to indicate that typing is expected.
2762  //
2763  // If the label is added during actions like import, then the
2764  // mDrawCursor flag will be reset once the action is complete.
2765  mDrawCursor = true;
2766 
2767  mRestoreFocus = restoreFocus;
2768 
2769  return pos;
2770 }
2771 
2773 {
2774  wxASSERT((index < (int)mLabels.size()));
2775  mLabels.erase(mLabels.begin() + index);
2776  // IF we've deleted the selected label
2777  // THEN set no label selected.
2778  if( mSelIndex== index )
2779  {
2780  mSelIndex = -1;
2781  mCurrentCursorPos = 1;
2782  }
2783  // IF we removed a label before the selected label
2784  // THEN the NEW selected label number is one less.
2785  else if( index < mSelIndex )
2786  {
2787  mSelIndex--;
2788  }
2789 }
2790 
2791 wxBitmap & LabelTrack::GetGlyph( int i)
2792 {
2793  return theTheme.Bitmap( i + bmpLabelGlyph0);
2794 }
2795 
2796 // This one XPM spec is used to generate a number of
2797 // different wxIcons.
2798 /* XPM */
2799 static const char *const GlyphXpmRegionSpec[] = {
2800 /* columns rows colors chars-per-pixel */
2801 "15 23 7 1",
2802 /* Default colors, with first color transparent */
2803 ". c none",
2804 "2 c black",
2805 "3 c black",
2806 "4 c black",
2807 "5 c #BEBEF0",
2808 "6 c #BEBEF0",
2809 "7 c #BEBEF0",
2810 /* pixels */
2811 "...............",
2812 "...............",
2813 "...............",
2814 "....333.444....",
2815 "...3553.4774...",
2816 "...3553.4774...",
2817 "..35553.47774..",
2818 "..35522222774..",
2819 ".3552666662774.",
2820 ".3526666666274.",
2821 "355266666662774",
2822 "355266666662774",
2823 "355266666662774",
2824 ".3526666666274.",
2825 ".3552666662774.",
2826 "..35522222774..",
2827 "..35553.47774..",
2828 "...3553.4774...",
2829 "...3553.4774...",
2830 "....333.444....",
2831 "...............",
2832 "...............",
2833 "..............."
2834 };
2835 
2852 {
2853  int iConfig;
2854  int iHighlight;
2855  int index;
2856  const int nSpecRows =
2857  sizeof( GlyphXpmRegionSpec )/sizeof( GlyphXpmRegionSpec[0]);
2858  const char *XmpBmp[nSpecRows];
2859 
2860  // The glyphs are declared static wxIcon; so we only need
2861  // to create them once, no matter how many LabelTracks.
2862  if( mbGlyphsReady )
2863  return;
2864 
2865  // We're about to tweak the basic color spec to get 12 variations.
2866  for( iConfig=0;iConfig<NUM_GLYPH_CONFIGS;iConfig++)
2867  {
2868  for( iHighlight=0;iHighlight<NUM_GLYPH_HIGHLIGHTS;iHighlight++)
2869  {
2870  index = iConfig + NUM_GLYPH_CONFIGS * iHighlight;
2871  // Copy the basic spec...
2872  memcpy( XmpBmp, GlyphXpmRegionSpec, sizeof( GlyphXpmRegionSpec ));
2873  // The higlighted region (if any) is white...
2874  if( iHighlight==1 ) XmpBmp[5]="5 c #FFFFFF";
2875  if( iHighlight==2 ) XmpBmp[6]="6 c #FFFFFF";
2876  if( iHighlight==3 ) XmpBmp[7]="7 c #FFFFFF";
2877  // For left or right arrow the other side of the glyph
2878  // is the transparent color.
2879  if( iConfig==0) { XmpBmp[3]="3 c none"; XmpBmp[5]="5 c none"; }
2880  if( iConfig==1) { XmpBmp[4]="4 c none"; XmpBmp[7]="7 c none"; }
2881  // Create the icon from the tweaked spec.
2882  mBoundaryGlyphs[index] = wxBitmap(XmpBmp);
2883  // Create the mask
2884  // SetMask takes ownership
2885  mBoundaryGlyphs[index].SetMask(safenew wxMask(mBoundaryGlyphs[index], wxColour(192, 192, 192)));
2886  }
2887  }
2888 
2889  mIconWidth = mBoundaryGlyphs[0].GetWidth();
2890  mIconHeight = mBoundaryGlyphs[0].GetHeight();
2891  mTextHeight = mIconHeight; // until proved otherwise...
2892  // The icon should have an odd width so that the
2893  // line goes exactly down the middle.
2894  wxASSERT( (mIconWidth %2)==1);
2895 
2896  mbGlyphsReady=true;
2897 }
2898 
2900 bool LabelTrack::IsGoodLabelFirstKey(const wxKeyEvent & evt)
2901 {
2902  int keyCode = evt.GetKeyCode();
2903  return (keyCode < WXK_START
2904  && keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) ||
2905  (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
2906  (keyCode >= WXK_NUMPAD_EQUAL && keyCode <= WXK_NUMPAD_DIVIDE) ||
2907 #if defined(__WXMAC__)
2908  (keyCode > WXK_RAW_CONTROL) ||
2909 #endif
2910  (keyCode > WXK_WINDOWS_MENU);
2911 }
2912 
2914 bool LabelTrack::IsGoodLabelEditKey(const wxKeyEvent & evt)
2915 {
2916  int keyCode = evt.GetKeyCode();
2917 
2918  // Accept everything outside of WXK_START through WXK_COMMAND, plus the keys
2919  // within that range that are usually printable, plus the ones we use for
2920  // keyboard navigation.
2921  return keyCode < WXK_START ||
2922  (keyCode >= WXK_END && keyCode < WXK_UP) ||
2923  (keyCode == WXK_RIGHT) ||
2924  (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
2925  (keyCode >= WXK_NUMPAD_SPACE && keyCode <= WXK_NUMPAD_ENTER) ||
2926  (keyCode >= WXK_NUMPAD_HOME && keyCode <= WXK_NUMPAD_END) ||
2927  (keyCode >= WXK_NUMPAD_DELETE && keyCode <= WXK_NUMPAD_DIVIDE) ||
2928 #if defined(__WXMAC__)
2929  (keyCode > WXK_RAW_CONTROL) ||
2930 #endif
2931  (keyCode > WXK_WINDOWS_MENU);
2932 }
2933 
2939 {
2940  const auto begin = mLabels.begin();
2941  const auto nn = (int)mLabels.size();
2942  int i = 1;
2943  while (true)
2944  {
2945  // Find the next disorder
2946  while (i < nn && mLabels[i - 1].getT0() <= mLabels[i].getT0())
2947  ++i;
2948  if (i >= nn)
2949  break;
2950 
2951  // Where must element i sink to? At most i - 1, maybe less
2952  int j = i - 2;
2953  while( (j >= 0) && (mLabels[j].getT0() > mLabels[i].getT0()) )
2954  --j;
2955  ++j;
2956 
2957  // Now fix the disorder
2958  std::rotate(
2959  begin + j,
2960  begin + i,
2961  begin + i + 1
2962  );
2963 
2964  // Various indices need to be updated with the moved items...
2965  auto update = [=](int &index) {
2966  if( index <= i ) {
2967  if( index == i )
2968  index = j;
2969  else if( index >= j)
2970  ++index;
2971  }
2972  };
2973  if ( pHit ) {
2974  update( pHit->mMouseOverLabelLeft );
2975  update( pHit->mMouseOverLabelRight );
2976  }
2977  update(mSelIndex);
2978  }
2979 }
2980 
2981 wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
2982 {
2983  bool firstLabel = true;
2984  wxString retVal;
2985 
2986  for (auto &labelStruct: mLabels) {
2987  if (labelStruct.getT0() >= t0 &&
2988  labelStruct.getT1() <= t1)
2989  {
2990  if (!firstLabel)
2991  retVal += '\t';
2992  firstLabel = false;
2993  retVal += labelStruct.title;
2994  }
2995  }
2996 
2997  return retVal;
2998 }
2999 
3001 {
3002  int i = -1;
3003 
3004  if (!mLabels.empty()) {
3005  int len = (int) mLabels.size();
3006  if (miLastLabel >= 0 && miLastLabel + 1 < len
3007  && currentRegion.t0() == mLabels[miLastLabel].getT0()
3008  && currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
3009  i = miLastLabel + 1;
3010  }
3011  else {
3012  i = 0;
3013  if (currentRegion.t0() < mLabels[len - 1].getT0()) {
3014  while (i < len &&
3015  mLabels[i].getT0() <= currentRegion.t0()) {
3016  i++;
3017  }
3018  }
3019  }
3020  }
3021 
3022  miLastLabel = i;
3023  return i;
3024 }
3025 
3026  int LabelTrack::FindPrevLabel(const SelectedRegion& currentRegion)
3027 {
3028  int i = -1;
3029 
3030  if (!mLabels.empty()) {
3031  int len = (int) mLabels.size();
3032  if (miLastLabel > 0 && miLastLabel < len
3033  && currentRegion.t0() == mLabels[miLastLabel].getT0()
3034  && currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
3035  i = miLastLabel - 1;
3036  }
3037  else {
3038  i = len - 1;
3039  if (currentRegion.t0() > mLabels[0].getT0()) {
3040  while (i >=0 &&
3041  mLabels[i].getT0() >= currentRegion.t0()) {
3042  i--;
3043  }
3044  }
3045  }
3046  }
3047 
3048  miLastLabel = i;
3049  return i;
3050 }
static int mIconHeight
Definition: LabelTrack.h:284
bool IsTextSelected()
Definition: LabelTrack.cpp:982
void getXPos(wxDC &dc, int *xPos1, int cursorPos) const
Definition: LabelTrack.cpp:711
double GetOffset() const override
static int Constrain(int value, int min, int max)
bool OnChar(SelectedRegion &sel, wxKeyEvent &event)
std::shared_ptr< LabelTrack > GetTrack() const
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
void WriteXML(XMLWriter &xmlFile) const override
void Unselect()
static wxBrush labelSelectedBrush
Definition: AColor.h:129
int mCurrentCursorPos
Definition: LabelTrack.h:291
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
std::unique_ptr< LabelTrack > Holder
Definition: LabelTrack.h:162
void ChangeLabelsOnReverse(double b, double e)
Definition: LabelTrack.cpp:213
Creates and manages BlockFile objects.
Definition: DirManager.h:52
void SetOffset(double dOffset) override
Definition: LabelTrack.cpp:145
double t0() const
static bool IsTextClipSupported()
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
double GetSel0() const
Definition: Project.h:204
bool DoCaptureKey(wxKeyEvent &event)
void InsertSilence(double t, double len) override
SelectedRegion selectedRegion
Definition: ViewInfo.h:160
void HandleTextDragRelease(const wxMouseEvent &evt)
unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent) override
bool IsGoodLabelFirstKey(const wxKeyEvent &evt)
Returns true for keys we capture to start a label.
int FindCurrentCursorPosition(int xPos)
Definition: LabelTrack.cpp:912
void SetCurrentCursorPosition(int xPos)
Set the cursor position according to x position of mouse.
Definition: LabelTrack.cpp:959
TimeRelations RegionRelation(double reg_t0, double reg_t1, const LabelTrack *parent=NULL) const
void MayMoveLabel(int iLabel, int iEdge, double fNewTime)
void Draw(TrackPanelDrawingContext &context, const wxRect &r, const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo) const
Definition: LabelTrack.cpp:784
static wxBrush uglyBrush
Definition: AColor.h:148
Track::Holder Duplicate() const override
bool Repeat(double t0, double t1, int n)
void DeleteLabel(int index)
bool CalcCursorX(int *x) const
Definition: LabelTrack.cpp:723
void calculateFontHeight(wxDC &dc) const
Definition: LabelTrack.cpp:964
static wxBrush labelTextNormalBrush
Definition: AColor.h:126
void SetHeight(int h)
Definition: Track.cpp:189
wxString label
Definition: Tags.cpp:733
bool CopySelectedText()
wxString title
Definition: LabelTrack.h:92
UIHandlePtr Target()
void ComputeLayout(const wxRect &r, const ZoomInfo &zoomInfo) const
Definition: LabelTrack.cpp:461
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
int mInitialCursorPos
current cursor position
Definition: LabelTrack.h:292
int xText
Pixel position of right hand glyph.
Definition: LabelTrack.h:98
bool updated
Pixel position of label.
Definition: LabelTrack.h:101
const std::shared_ptr< DirManager > mDirManager
Definition: Track.h:876
const int MAX_NUM_ROWS
Definition: LabelTrack.h:108
bool OverTextBox(const LabelStruct *pLabel, int x, int y) const
int GetAudioIOToken() const
Definition: Project.cpp:1447
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
bool IsStreamActive() const
Returns true if the audio i/o is running at all, but not during cleanup.
Definition: AudioIO.cpp:2860
double GetStartTime() const override
void Clear(double t0, double t1) override
Definition: LabelTrack.cpp:151
TimeRelations
Relationships between selection region and labels.
Definition: LabelTrack.h:73
int GetNumLabels() const
int mMouseOverLabelRight
Keeps track of which left label the mouse is currently over.
void ScaleLabels(double b, double e, double change)
Definition: LabelTrack.cpp:228
void SetMinimized(bool isMinimized)
Definition: Track.cpp:219
void DrawHighlight(wxDC &dc, int xPos1, int xPos2, int charHeight) const
Draws text-selected region within the label.
Definition: LabelTrack.cpp:699
double t1() const
void MayAdjustLabel(LabelTrackHit &hit, int iLabel, int iEdge, bool bAllowSwapping, double fNewTime)
double GetEndTime() const override
int x1
Pixel position of left hand glyph.
Definition: LabelTrack.h:97
double getDuration() const
Definition: LabelTrack.h:60
int miLastLabel
Definition: LabelTrack.h:303
void ShowContextMenu()
void Export(wxTextFile &file) const
const std::shared_ptr< DirManager > & GetDirManager() const
Definition: Track.h:304
static int mIconWidth
Definition: LabelTrack.h:285
static void ResetFont()
Definition: LabelTrack.cpp:296
LabelArray mLabels
Displacement of mouse cursor from the centre being dragged.
Definition: LabelTrack.h:282
int DialogForLabelName(const wxString &initialValue, wxString &value)
Definition: Menus.cpp:8571
void ScrollIntoView(double pos)
void OverGlyph(LabelTrackHit &hit, int x, int y) const
#define safenew
Definition: Audacity.h:230
static bool IsGoodInt(const wxString &strInt)
Check that the supplied string can be converted to a long (32bit) integer.
virtual void SetSelected(bool s)
Definition: Track.cpp:99
virtual int GetKind() const
Definition: Track.h:334
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:113
void Export(wxTextFile &f) const
Export labels including label start and end-times.
static const char *const GlyphXpmRegionSpec[]
void SetFocusedTrack(Track *t)
wxString GetDefaultName() const
Definition: Track.h:277
double f0() const
static void DrawBackgroundWithSelection(wxDC *dc, const wxRect &rect, const Track *track, wxBrush &selBrush, wxBrush &unselBrush, const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo)
wxBitmap & Bitmap(int iIndex)
Definition: Theme.cpp:1244
double AdjustTimeStampOnScale(double t, double b, double e, double change)
Definition: LabelTrack.cpp:237
double mClipLen
when done editing
Definition: LabelTrack.h:301
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper, LinearInputRateSlideTimeWarper, LinearOutputRateSlideTimeWarper, LinearInputInverseRateTimeWarper, GeometricInputRateTimeWarper, GeometricOutputRateTimeWarper classes.
int x
width of the text in pixels.
Definition: LabelTrack.h:96
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:44
bool mRightDragging
initial cursor position
Definition: LabelTrack.h:294
void SetSelected(bool s) override
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:138
int mSelIndex
Definition: LabelTrack.h:280
static wxFont GetFont(const wxString &faceName, int size=DefaultFontSize)
Definition: LabelTrack.cpp:285
void DrawTextBox(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:648
static bool IsGoodString(const wxString &str)
void ComputeTextPosition(const wxRect &r, int index) const
Definition: LabelTrack.cpp:317
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:122
Defines a selected portion of a project.
wxString GetTextOfLabels(double t0, double t1) const
const int NUM_GLYPH_CONFIGS
Definition: LabelTrack.h:106
bool HandleXMLAttribute(const wxChar *attr, const wxChar *value, const wxChar *legacyT0Name=sDefaultT0Name, const wxChar *legacyT1Name=sDefaultT1Name)
bool setT1(double t, bool maySwap=true)
static int mFontHeight
Definition: LabelTrack.h:290
static wxBitmap & GetGlyph(int i)
static wxBrush labelTextEditBrush
Definition: AColor.h:127
int FindPrevLabel(const SelectedRegion &currentSelection)
const LabelStruct * GetLabel(int index) const
void WarpLabels(const TimeWarper &warper)
Definition: LabelTrack.cpp:256
LabelStruct(const SelectedRegion &region, const wxString &aTitle)
Definition: LabelTrack.cpp:528
static LabelStruct Import(wxTextFile &file, int &index)
static bool IsGoodLongString(const wxString &str)
void Silence(double t0, double t1) override
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:102
bool mDrawCursor
flag to tell if it's a valid dragging
Definition: LabelTrack.h:295
Track::Holder Copy(double t0, double t1, bool forClipboard=true) const override
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:72
ViewInfo mViewInfo
Definition: Project.h:559
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ViewInfo.cpp:59
int width
Text of the label.
Definition: LabelTrack.h:93
int min(int a, int b)
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:418
bool PasteOver(double t, const Track *src)
void HandleTextClick(const wxMouseEvent &evt, const wxRect &r, const ZoomInfo &zoomInfo, SelectedRegion *newSel)
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent) override
void DoEditLabels(LabelTrack *lt=nullptr, int index=-1)
Definition: Menus.cpp:8855
void RemoveSelectedText()
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:122
bool PasteSelectedText(double sel0, double sel1)
static wxBitmap mBoundaryGlyphs[NUM_GLYPH_CONFIGS *NUM_GLYPH_HIGHLIGHTS]
Definition: LabelTrack.h:288
Track::Holder Cut(double t0, double t1) override
int mRestoreFocus
cursor or not
Definition: LabelTrack.h:297
double mOffset
Definition: Track.h:234
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
LabelTrack(const std::shared_ptr< DirManager > &projDirManager)
Definition: LabelTrack.cpp:104
virtual ~LabelTrack()
Definition: LabelTrack.cpp:141
void SetDefaultName(const wxString &n)
Definition: Track.h:278
An iterator for a TrackList.
Definition: Track.h:406
void OnContextMenu(wxCommandEvent &evt)
bool HandleGlyphDragRelease(LabelTrackHit &hit, const wxMouseEvent &evt, wxRect &r, const ZoomInfo &zoomInfo, SelectedRegion *newSel)
bool setTimes(double t0, double t1)
int GetLabelIndex(double t, double t1)
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
AudioIO * gAudioIO
Definition: AudioIO.cpp:483
static bool mbGlyphsReady
Definition: LabelTrack.h:287
void CalcHighlightXs(int *x1, int *x2) const
Definition: LabelTrack.cpp:740
void PushState(const wxString &desc, const wxString &shortDesc)
Definition: Project.cpp:4714
void SetName(const wxString &n)
Definition: Track.h:276
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent) override
double getT1() const
Definition: LabelTrack.h:62
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:460
void SortLabels(LabelTrackHit *pHit=nullptr)
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:309
void MoveLabel(int iEdge, double fNewTime)
static wxFont msFont
Definition: LabelTrack.h:316
SelectedRegion selectedRegion
Definition: LabelTrack.h:91
void CreateCustomGlyphs()
void Paste(double t, const Track *src) override
const int NUM_GLYPH_HIGHLIGHTS
Definition: LabelTrack.h:107
int FindNextLabel(const SelectedRegion &currentSelection)
virtual double Warp(double originalTime) const =0
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:61
void HandleGlyphClick(LabelTrackHit &hit, const wxMouseEvent &evt, const wxRect &r, const ZoomInfo &zoomInfo, SelectedRegion *newSel)
void Import(wxTextFile &f)
Import labels, handling files with or without end-times.
double f1() const
TrackPanel * GetTrackPanel()
Definition: Project.h:307
bool setFrequencies(double f0, double f1)
void ShiftLabelsOnInsert(double length, double pt)
Definition: LabelTrack.cpp:200
bool OnKeyDown(SelectedRegion &sel, wxKeyEvent &event)
KeyEvent is called for every keypress when over the label track.
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1225
bool setT0(double t, bool maySwap=true)
std::unique_ptr< Track > Holder
Definition: Track.h:268
static wxPen labelSurroundPen
Definition: AColor.h:134
double GetSel1() const
Definition: Project.h:205
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
static int mTextHeight
Definition: LabelTrack.h:286
double getT0() const
Definition: LabelTrack.h:61
void DrawText(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:623
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:22
int y
Pixel position of left hand side of text box.
Definition: LabelTrack.h:99
void GetPlayRegion(double *playRegionStart, double *playRegionEnd)
Definition: Project.cpp:5472
int OverATextBox(int xx, int yy) const
void ResetFlags()
Definition: LabelTrack.cpp:268
std::unique_ptr< LabelTrack > NewLabelTrack()
Definition: LabelTrack.cpp:99
int AddLabel(const SelectedRegion &region, const wxString &title=wxT(""), int restoreFocus=-1)
void DrawLines(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:562
void DrawGlyphs(wxDC &dc, const wxRect &r, int GlyphLeft, int GlyphRight) const
Definition: LabelTrack.cpp:600
void RestoreFlags(const Flags &flags)
Definition: LabelTrack.cpp:276
static const int UndefinedFrequency
bool CutSelectedText()
Definition: LabelTrack.cpp:993
LabelTrackHit mHit
bool IsSelected() const
static const int DefaultFontSize
Definition: LabelTrack.h:145
bool AdjustEdge(int iEdge, double fNewTime)
bool mbIsMoving
Keeps track of which right label the mouse is currently over.
bool IsGoodLabelEditKey(const wxKeyEvent &evt)
This returns true for keys we capture for label editing.
static wxBrush labelUnselectedBrush
Definition: AColor.h:128
wxString mName
Definition: Track.h:119