Audacity  2.3.1
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 "Menus.h"
60 #include "Prefs.h"
61 #include "RefreshCode.h"
62 #include "Theme.h"
63 #include "AllThemeResources.h"
64 #include "AColor.h"
65 #include "Project.h"
66 #include "TrackArtist.h"
67 #include "TrackPanel.h"
68 #include "UndoManager.h"
70 
71 #include "effects/TimeWarper.h"
72 #include "widgets/ErrorDialog.h"
73 
74 enum
75 {
76  OnCutSelectedTextID = 1, // OSX doesn't like a 0 menu id
81 };
82 
83 wxFont LabelTrack::msFont;
84 
85 // static member variables.
86 bool LabelTrack::mbGlyphsReady=false;
87 
97 
99 
101 {
102  return std::make_unique<LabelTrack>(mDirManager);
103 }
104 
105 LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
106  Track(projDirManager),
107  mSelIndex(-1),
108  mRestoreFocus(-1),
109  mClipLen(0.0),
110  miLastLabel(-1)
111 {
112  SetDefaultName(_("Label Track"));
114 
115  // Label tracks are narrow
116  // Default is to allow two rows so that NEW users get the
117  // idea that labels can 'stack' when they would overlap.
118  SetHeight(73);
119 
120  ResetFont();
122 
123  // reset flags
124  ResetFlags();
125 }
126 
128  Track(orig),
129  mSelIndex(-1),
130  mClipLen(0.0)
131 {
132  for (auto &original: orig.mLabels) {
133  LabelStruct l { original.selectedRegion, original.title };
134  mLabels.push_back(l);
135  }
136  mSelIndex = orig.mSelIndex;
137 
138  // reset flags
139  ResetFlags();
140 }
141 
143 {
144 }
145 
146 void LabelTrack::SetOffset(double dOffset)
147 {
148  for (auto &labelStruct: mLabels)
149  labelStruct.selectedRegion.move(dOffset);
150 }
151 
152 void LabelTrack::Clear(double b, double e)
153 {
154  // May DELETE labels, so use subscripts to iterate
155  for (size_t i = 0; i < mLabels.size(); ++i) {
156  auto &labelStruct = mLabels[i];
157  LabelStruct::TimeRelations relation =
158  labelStruct.RegionRelation(b, e, this);
159  if (relation == LabelStruct::BEFORE_LABEL)
160  labelStruct.selectedRegion.move(- (e-b));
161  else if (relation == LabelStruct::SURROUNDS_LABEL) {
162  DeleteLabel( i );
163  --i;
164  }
165  else if (relation == LabelStruct::ENDS_IN_LABEL)
166  labelStruct.selectedRegion.setTimes(
167  b,
168  labelStruct.getT1() - (e - b));
169  else if (relation == LabelStruct::BEGINS_IN_LABEL)
170  labelStruct.selectedRegion.setT1(b);
171  else if (relation == LabelStruct::WITHIN_LABEL)
172  labelStruct.selectedRegion.moveT1( - (e-b));
173  }
174 }
175 
176 #if 0
177 //used when we want to use clear only on the labels
178 bool LabelTrack::SplitDelete(double b, double e)
179 {
180  // May DELETE labels, so use subscripts to iterate
181  for (size_t i = 0, len = mLabels.size(); i < len; ++i) {
182  auto &labelStruct = mLabels[i];
183  LabelStruct::TimeRelations relation =
184  labelStruct.RegionRelation(b, e, this);
185  if (relation == LabelStruct::SURROUNDS_LABEL) {
186  DeleteLabel(i);
187  --i;
188  }
189  else if (relation == LabelStruct::WITHIN_LABEL)
190  labelStruct.selectedRegion.moveT1( - (e-b));
191  else if (relation == LabelStruct::ENDS_IN_LABEL)
192  labelStruct.selectedRegion.setT0(e);
193  else if (relation == LabelStruct::BEGINS_IN_LABEL)
194  labelStruct.selectedRegion.setT1(b);
195  }
196 
197  return true;
198 }
199 #endif
200 
201 void LabelTrack::ShiftLabelsOnInsert(double length, double pt)
202 {
203  for (auto &labelStruct: mLabels) {
204  LabelStruct::TimeRelations relation =
205  labelStruct.RegionRelation(pt, pt, this);
206 
207  if (relation == LabelStruct::BEFORE_LABEL)
208  labelStruct.selectedRegion.move(length);
209  else if (relation == LabelStruct::WITHIN_LABEL)
210  labelStruct.selectedRegion.moveT1(length);
211  }
212 }
213 
214 void LabelTrack::ChangeLabelsOnReverse(double b, double e)
215 {
216  for (auto &labelStruct: mLabels) {
217  if (labelStruct.RegionRelation(b, e, this) ==
219  {
220  double aux = b + (e - labelStruct.getT1());
221  labelStruct.selectedRegion.setTimes(
222  aux,
223  e - (labelStruct.getT0() - b));
224  }
225  }
226  SortLabels();
227 }
228 
229 void LabelTrack::ScaleLabels(double b, double e, double change)
230 {
231  for (auto &labelStruct: mLabels) {
232  labelStruct.selectedRegion.setTimes(
233  AdjustTimeStampOnScale(labelStruct.getT0(), b, e, change),
234  AdjustTimeStampOnScale(labelStruct.getT1(), b, e, change));
235  }
236 }
237 
238 double LabelTrack::AdjustTimeStampOnScale(double t, double b, double e, double change)
239 {
240 //t is the time stamp we'll be changing
241 //b and e are the selection start and end
242 
243  if (t < b){
244  return t;
245  }else if (t > e){
246  double shift = (e-b)*change - (e-b);
247  return t + shift;
248  }else{
249  double shift = (t-b)*change - (t-b);
250  return t + shift;
251  }
252 }
253 
254 // Move the labels in the track according to the given TimeWarper.
255 // (If necessary this could be optimised by ignoring labels that occur before a
256 // specified time, as in most cases they don't need to move.)
257 void LabelTrack::WarpLabels(const TimeWarper &warper) {
258  for (auto &labelStruct: mLabels) {
259  labelStruct.selectedRegion.setTimes(
260  warper.Warp(labelStruct.getT0()),
261  warper.Warp(labelStruct.getT1()));
262  }
263 
264  // This should not be needed, assuming the warper is nondecreasing, but
265  // let's not assume too much.
266  SortLabels();
267 }
268 
270 {
271  mInitialCursorPos = 1;
272  mCurrentCursorPos = 1;
273  mRightDragging = false;
274  mDrawCursor = false;
275 }
276 
277 void LabelTrack::RestoreFlags( const Flags& flags )
278 {
281  mSelIndex = flags.mSelIndex;
283  mDrawCursor = flags.mDrawCursor;
284 }
285 
286 wxFont LabelTrack::GetFont(const wxString &faceName, int size)
287 {
288  wxFontEncoding encoding;
289  if (faceName == wxT(""))
290  encoding = wxFONTENCODING_DEFAULT;
291  else
292  encoding = wxFONTENCODING_SYSTEM;
293  return wxFont(size, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
294  wxFONTWEIGHT_NORMAL, false, faceName, encoding);
295 }
296 
298 {
299  mFontHeight = -1;
300  wxString facename = gPrefs->Read(wxT("/GUI/LabelFontFacename"), wxT(""));
301  int size = gPrefs->Read(wxT("/GUI/LabelFontSize"), DefaultFontSize);
302  msFont = GetFont(facename, size);
303 }
304 
318 void LabelTrack::ComputeTextPosition(const wxRect & r, int index) const
319 {
320  auto &labelStruct = mLabels[index];
321 
322  // xExtra is extra space
323  // between the text and the endpoints.
324  const int xExtra=mIconWidth;
325  int x = labelStruct.x; // left endpoint
326  int x1 = labelStruct.x1; // right endpoint.
327  int width = labelStruct.width;
328 
329  int xText; // This is where the text will end up.
330 
331  // Will the text all fit at this zoom?
332  bool bTooWideForScreen = width > (r.width-2*xExtra);
333 // bool bSimpleCentering = !bTooWideForScreen;
334  bool bSimpleCentering = false;
335 
336  //TODO (possibly):
337  // Add configurable options as to when to use simple
338  // and when complex centering.
339  //
340  // Simple centering does its best to keep the text
341  // centered between the label limits.
342  //
343  // Complex centering positions the text proportionally
344  // to how far we are through the label.
345  //
346  // If we add preferences for this, we want to be able to
347  // choose separately whether:
348  // a) Wide text labels centered simple/complex.
349  // b) Other text labels centered simple/complex.
350  //
351 
352  if( bSimpleCentering )
353  {
354  // Center text between the two end points.
355  xText = (x+x1-width)/2;
356  }
357  else
358  {
359  // Calculate xText position to make text line
360  // scroll sideways evenly as r moves right.
361 
362  // xText is a linear function of r.x.
363  // These variables are used to compute that function.
364  int rx0,rx1,xText0,xText1;
365 
366  // Since we will be using a linear function,
367  // we should blend smoothly between left and right
368  // aligned text as r, the 'viewport' moves.
369  if( bTooWideForScreen )
370  {
371  rx0=x; // when viewport at label start.
372  xText0=x+xExtra; // text aligned left.
373  rx1=x1-r.width; // when viewport end at label end
374  xText1=x1-(width+xExtra); // text aligned right.
375  }
376  else
377  {
378  // when label start + width + extra spacing at viewport end..
379  rx0=x-r.width+width+2*xExtra;
380  // ..text aligned left.
381  xText0=x+xExtra;
382  // when viewport start + width + extra spacing at label end..
383  rx1=x1-(width+2*xExtra);
384  // ..text aligned right.
385  xText1=x1-(width+xExtra);
386  }
387 
388  if( rx1 > rx0 ) // Avoid divide by zero case.
389  {
390  // Compute the blend between left and right aligned.
391 
392  // Don't use:
393  //
394  // xText = xText0 + ((xText1-xText0)*(r.x-rx0))/(rx1-rx0);
395  //
396  // The problem with the above is that it integer-oveflows at
397  // high zoom.
398 
399  // Instead use:
400  xText = xText0 + (int)((xText1-xText0)*(((float)(r.x-rx0))/(rx1-rx0)));
401  }
402  else
403  {
404  // Avoid divide by zero by reverting to
405  // simple centering.
406  //
407  // We could also fall into this case if x and x1
408  // are swapped, in which case we'll end up
409  // left aligned anyway because code later on
410  // will catch that.
411  xText = (x+x1-width)/2;
412  }
413  }
414 
415  // Is the text now appearing partly outside r?
416  bool bOffLeft = xText < r.x+xExtra;
417  bool bOffRight = xText > r.x+r.width-width-xExtra;
418 
419  // IF any part of the text is offscreen
420  // THEN we may bring it back.
421  if( bOffLeft == bOffRight )
422  {
423  //IF both sides on screen, THEN nothing to do.
424  //IF both sides off screen THEN don't do
425  //anything about it.
426  //(because if we did, you'd never get to read
427  //all the text by scrolling).
428  }
429  else if( bOffLeft != bTooWideForScreen)
430  {
431  // IF we're off on the left, OR we're
432  // too wide for the screen and off on the right
433  // (only) THEN align left.
434  xText = r.x+xExtra;
435  }
436  else
437  {
438  // We're off on the right, OR we're
439  // too wide and off on the left (only)
440  // SO align right.
441  xText =r.x+r.width-width-xExtra;
442  }
443 
444  // But if we've taken the text out from its endpoints
445  // we must move it back so that it's between the endpoints.
446 
447  // We test the left end point last because the
448  // text might not even fit between the endpoints (at this
449  // zoom factor), and in that case we'd like to position
450  // the text at the left end point.
451  if( xText > (x1-width-xExtra))
452  xText=(x1-width-xExtra);
453  if( xText < x+xExtra )
454  xText=x+xExtra;
455 
456  labelStruct.xText = xText;
457 }
458 
462 void LabelTrack::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) const
463 {
464  int xUsed[MAX_NUM_ROWS];
465 
466  int iRow;
467  // Rows are the 'same' height as icons or as the text,
468  // whichever is taller.
469  const int yRowHeight = wxMax(mTextHeight,mIconHeight)+3;// pixels.
470  // Extra space at end of rows.
471  // We allow space for one half icon at the start and two
472  // half icon widths for extra x for the text frame.
473  // [we don't allow half a width space for the end icon since it is
474  // allowed to be obscured by the text].
475  const int xExtra= (3 * mIconWidth)/2;
476 
477  const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS);
478  // Initially none of the rows have been used.
479  // So set a value that is less than any valid value.
480  {
481  // Bug 502: With dragging left of zeros, labels can be in
482  // negative space. So set least possible value as starting point.
483  const int xStart = INT_MIN;
484  for (auto &x : xUsed)
485  x = xStart;
486  }
487  int nRowsUsed=0;
488 
489  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
490  const int x = zoomInfo.TimeToPosition(labelStruct.getT0(), r.x);
491  const int x1 = zoomInfo.TimeToPosition(labelStruct.getT1(), r.x);
492  int y = r.y;
493 
494  labelStruct.x=x;
495  labelStruct.x1=x1;
496  labelStruct.y=-1;// -ve indicates nothing doing.
497  iRow=0;
498  // Our first preference is a row that ends where we start.
499  // (This is to encourage merging of adjacent label boundaries).
500  while( (iRow<nRowsUsed) && (xUsed[iRow] != x ))
501  iRow++;
502  // IF we didn't find one THEN
503  // find any row that can take a span starting at x.
504  if( iRow >= nRowsUsed )
505  {
506  iRow=0;
507  while( (iRow<nRows) && (xUsed[iRow] > x ))
508  iRow++;
509  }
510  // IF we found such a row THEN record a valid position.
511  if( iRow<nRows )
512  {
513  // Possibly update the number of rows actually used.
514  if( iRow >= nRowsUsed )
515  nRowsUsed=iRow+1;
516  // Record the position for this label
517  y= r.y + iRow * yRowHeight +(yRowHeight/2)+1;
518  labelStruct.y=y;
519  // On this row we have used up to max of end marker and width.
520  // Plus also allow space to show the start icon and
521  // some space for the text frame.
522  xUsed[iRow]=x+labelStruct.width+xExtra;
523  if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
524  ComputeTextPosition( r, i );
525  }
526  }}
527 }
528 
530  const wxString& aTitle)
531 : selectedRegion(region)
532 , title(aTitle)
533 {
534  updated = false;
535  width = 0;
536  x = 0;
537  x1 = 0;
538  xText = 0;
539  y = 0;
540 }
541 
543  double t0, double t1,
544  const wxString& aTitle)
545 : selectedRegion(region)
546 , title(aTitle)
547 {
548  // Overwrite the times
549  selectedRegion.setTimes(t0, t1);
550 
551  updated = false;
552  width = 0;
553  x = 0;
554  x1 = 0;
555  xText = 0;
556  y = 0;
557 }
558 
563 void LabelStruct::DrawLines(wxDC & dc, const wxRect & r) const
564 {
565  // How far out from the centre line should the vertical lines
566  // start, i.e. what is the y position of the icon?
567  // We adjust this so that the line encroaches on the icon
568  // slightly (there is white space in the design).
569  const int yIconStart = y - (LabelTrack::mIconHeight /2)+1+(LabelTrack::mTextHeight+3)/2;
570  const int yIconEnd = yIconStart + LabelTrack::mIconHeight-2;
571 
572  // If y is positive then it is the center line for the
573  // Label.
574  if( y >= 0 )
575  {
576  if((x >= r.x) && (x <= (r.x+r.width)))
577  {
578  // Draw line above and below left dragging widget.
579  AColor::Line(dc, x, r.y, x, yIconStart - 1);
580  AColor::Line(dc, x, yIconEnd, x, r.y + r.height);
581  }
582  if((x1 >= r.x) && (x1 <= (r.x+r.width)))
583  {
584  // Draw line above and below right dragging widget.
585  AColor::Line(dc, x1, r.y, x1, yIconStart - 1);
586  AColor::Line(dc, x1, yIconEnd, x1, r.y + r.height);
587  }
588  }
589  else
590  {
591  // Draw the line, even though the widget is off screen
592  AColor::Line(dc, x, r.y, x, r.y + r.height);
593  AColor::Line(dc, x1, r.y, x1, r.y + r.height);
594  }
595 }
596 
601  (wxDC & dc, const wxRect & r, int GlyphLeft, int GlyphRight) const
602 {
603  const int xHalfWidth=LabelTrack::mIconWidth/2;
604  const int yStart=y-LabelTrack::mIconHeight/2+(LabelTrack::mTextHeight+3)/2;
605 
606  // If y == -1, nothing to draw
607  if( y == -1 )
608  return;
609 
610  if((x >= r.x) && (x <= (r.x+r.width)))
611  dc.DrawBitmap(LabelTrack::GetGlyph(GlyphLeft), x-xHalfWidth,yStart, true);
612  // The extra test commented out here would suppress right hand markers
613  // when they overlap the left hand marker (e.g. zoomed out) or to the left.
614  if((x1 >= r.x) && (x1 <= (r.x+r.width)) /*&& (x1>x+LabelTrack::mIconWidth)*/)
615  dc.DrawBitmap(LabelTrack::GetGlyph(GlyphRight), x1-xHalfWidth,yStart, true);
616 }
617 
624 void LabelStruct::DrawText(wxDC & dc, const wxRect & r) const
625 {
626  //If y is positive then it is the center line for the
627  //text we are about to draw.
628  //if it isn't, nothing to draw.
629 
630  if( y == -1 )
631  return;
632 
633  // Draw frame for the text...
634  // We draw it half an icon width left of the text itself.
635  {
636  const int xStart=wxMax(r.x,xText-LabelTrack::mIconWidth/2);
637  const int xEnd=wxMin(r.x+r.width,xText+width+LabelTrack::mIconWidth/2);
638  const int xWidth = xEnd-xStart;
639 
640  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
641  {
642  // Now draw the text itself.
643  dc.DrawText(title, xText, y-LabelTrack::mTextHeight/2);
644  }
645  }
646 
647 }
648 
649 void LabelStruct::DrawTextBox(wxDC & dc, const wxRect & r) const
650 {
651  //If y is positive then it is the center line for the
652  //text we are about to draw.
653  const int yBarHeight=3;
654  const int yFrameHeight = LabelTrack::mTextHeight+3;
655  const int xBarShorten = LabelTrack::mIconWidth+4;
656  if( y == -1 )
657  return;
658 
659  {
660  const int xStart=wxMax(r.x,x+xBarShorten/2);
661  const int xEnd=wxMin(r.x+r.width,x1-xBarShorten/2);
662  const int xWidth = xEnd-xStart;
663 
664  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
665  {
666 
667  wxRect bar( xStart,y-yBarHeight/2+yFrameHeight/2,
668  xWidth,yBarHeight);
669  if( x1 > x+xBarShorten )
670  dc.DrawRectangle(bar);
671  }
672  }
673 
674  // In drawing the bar and the frame, we compute the clipping
675  // to the viewport ourselves. Under Win98 the GDI does its
676  // calculations in 16 bit arithmetic, and so gets it completely
677  // wrong at higher zooms where the bar can easily be
678  // more than 65536 pixels wide.
679 
680  // Draw bar for label extent...
681  // We don't quite draw from x to x1 because we allow
682  // half an icon width at each end.
683  {
684  const int xStart=wxMax(r.x,xText-LabelTrack::mIconWidth/2);
685  const int xEnd=wxMin(r.x+r.width,xText+width+LabelTrack::mIconWidth/2);
686  const int xWidth = xEnd-xStart;
687 
688  if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
689  {
690  wxRect frame(
691  xStart,y-yFrameHeight/2,
692  xWidth,yFrameHeight );
693  dc.DrawRectangle(frame);
694  }
695  }
696 }
697 
700  ( wxDC & dc, int xPos1, int xPos2, int charHeight) const
701 {
702  wxPen curPen = dc.GetPen();
703  curPen.SetColour(wxString(wxT("BLUE")));
704  wxBrush curBrush = dc.GetBrush();
705  curBrush.SetColour(wxString(wxT("BLUE")));
706  if (xPos1 < xPos2)
707  dc.DrawRectangle(xPos1-1, y-charHeight/2, xPos2-xPos1+1, charHeight);
708  else
709  dc.DrawRectangle(xPos2-1, y-charHeight/2, xPos1-xPos2+1, charHeight);
710 }
711 
712 void LabelStruct::getXPos( wxDC & dc, int * xPos1, int cursorPos) const
713 {
714  *xPos1 = xText;
715  if( cursorPos > 0)
716  {
717  int partWidth;
718  // Calculate the width of the substring and add it to Xpos
719  dc.GetTextExtent(title.Left(cursorPos), &partWidth, NULL);
720  *xPos1 += partWidth;
721  }
722 }
723 
724 bool LabelTrack::CalcCursorX(int * x) const
725 {
726  if (mSelIndex >= 0) {
727  wxMemoryDC dc;
728 
729  if (msFont.Ok()) {
730  dc.SetFont(msFont);
731  }
732 
733  mLabels[mSelIndex].getXPos(dc, x, mCurrentCursorPos);
734  *x += LabelTrack::mIconWidth / 2;
735  return true;
736  }
737 
738  return false;
739 }
740 
741 void LabelTrack::CalcHighlightXs(int *x1, int *x2) const
742 {
743  wxMemoryDC dc;
744 
745  if (msFont.Ok()) {
746  dc.SetFont(msFont);
747  }
748 
749  int pos1 = mInitialCursorPos, pos2 = mCurrentCursorPos;
750  if (pos1 > pos2)
751  std::swap(pos1, pos2);
752 
753  const auto &labelStruct = mLabels[mSelIndex];
754 
755  // find the left X pos of highlighted area
756  labelStruct.getXPos(dc, x1, pos1);
757  // find the right X pos of highlighted area
758  labelStruct.getXPos(dc, x2, pos2);
759 }
760 
762 // TODO: don't rely on the global ::GetActiveProject() to find this.
763 // Rather, give TrackPanelCell a drawing function and pass context into it.
764 namespace {
765  LabelTrackHit *findHit()
766  {
767  // Fetch the highlighting state
768  auto target = GetActiveProject()->GetTrackPanel()->Target();
769  if (target) {
770  auto handle = dynamic_cast<LabelGlyphHandle*>( target.get() );
771  if (handle)
772  return &handle->mHit;
773  }
774  return nullptr;
775  }
776 }
777 
780 
784 void LabelTrack::Draw
785 (TrackPanelDrawingContext &context, const wxRect & r,
786  const SelectedRegion &selectedRegion,
787  const ZoomInfo &zoomInfo) const
788 {
789  auto &dc = context.dc;
790  auto pHit = findHit();
791 
792  if(msFont.Ok())
793  dc.SetFont(msFont);
794 
795  if (mFontHeight == -1)
796  calculateFontHeight(dc);
797 
800  selectedRegion, zoomInfo);
801 
802  wxCoord textWidth, textHeight;
803 
804  // Get the text widths.
805  // TODO: Make more efficient by only re-computing when a
806  // text label title changes.
807  for (auto &labelStruct : mLabels) {
808  dc.GetTextExtent(labelStruct.title, &textWidth, &textHeight);
809  labelStruct.width = textWidth;
810  }
811 
812  // TODO: And this only needs to be done once, but we
813  // do need the dc to do it.
814  // We need to set mTextHeight to something sensible,
815  // guarding against the case where there are no
816  // labels or all are empty strings, which for example
817  // happens with a NEW label track.
818  dc.GetTextExtent(wxT("Demo Text x^y"), &textWidth, &textHeight);
819  mTextHeight = (int)textHeight;
820  ComputeLayout( r, zoomInfo );
821  dc.SetTextForeground(theTheme.Colour( clrLabelTrackText));
822  dc.SetBackgroundMode(wxTRANSPARENT);
823  dc.SetBrush(AColor::labelTextNormalBrush);
824  dc.SetPen(AColor::labelSurroundPen);
825  int GlyphLeft;
826  int GlyphRight;
827  // Now we draw the various items in this order,
828  // so that the correct things overpaint each other.
829 
830  // Draw vertical lines that show where the end positions are.
831  for (auto &labelStruct : mLabels)
832  labelStruct.DrawLines( dc, r );
833 
834  // Draw the end glyphs.
835  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
836  GlyphLeft=0;
837  GlyphRight=1;
838  if( pHit && i == pHit->mMouseOverLabelLeft )
839  GlyphLeft = (pHit->mEdge & 4) ? 6:9;
840  if( pHit && i == pHit->mMouseOverLabelRight )
841  GlyphRight = (pHit->mEdge & 4) ? 7:4;
842  labelStruct.DrawGlyphs( dc, r, GlyphLeft, GlyphRight );
843  }}
844 
845  // Draw the label boxes.
846  {
847 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
848  bool highlightTrack = false;
849  auto target = dynamic_cast<LabelTextHandle*>(context.target.get());
850  highlightTrack = target && target->GetTrack().get() == this;
851 #endif
852  int i = -1; for (auto &labelStruct : mLabels) { ++i;
853  bool highlight = false;
854 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
855  highlight = highlightTrack && target->GetLabelNum() == i;
856 #endif
857  bool selected = mSelIndex == i;
858 
859  if( selected )
860  dc.SetBrush( AColor::labelTextEditBrush );
861  else if ( highlight )
862  dc.SetBrush( AColor::uglyBrush );
863  labelStruct.DrawTextBox( dc, r );
864 
865  if (highlight || selected)
866  dc.SetBrush(AColor::labelTextNormalBrush);
867  }
868  }
869 
870  // Draw highlights
871  if ((mInitialCursorPos != mCurrentCursorPos) && (mSelIndex >= 0 ))
872  {
873  int xpos1, xpos2;
874  CalcHighlightXs(&xpos1, &xpos2);
875  mLabels[mSelIndex].DrawHighlight(dc, xpos1, xpos2, mFontHeight);
876  }
877 
878  // Draw the text and the label boxes.
879  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
880  if( mSelIndex==i)
881  dc.SetBrush(AColor::labelTextEditBrush);
882  labelStruct.DrawText( dc, r );
883  if( mSelIndex==i)
884  dc.SetBrush(AColor::labelTextNormalBrush);
885  }}
886 
887  // Draw the cursor, if there is one.
888  if( mDrawCursor && mSelIndex >=0 )
889  {
890  const auto &labelStruct = mLabels[mSelIndex];
891  int xPos = labelStruct.xText;
892 
893  if( mCurrentCursorPos > 0)
894  {
895  // Calculate the width of the substring and add it to Xpos
896  int partWidth;
897  dc.GetTextExtent(labelStruct.title.Left(mCurrentCursorPos), &partWidth, NULL);
898  xPos += partWidth;
899  }
900 
901  wxPen currentPen = dc.GetPen();
902  const int CursorWidth=2;
903  currentPen.SetWidth(CursorWidth);
904  AColor::Line(dc,
905  xPos-1, labelStruct.y - mFontHeight/2 + 1,
906  xPos-1, labelStruct.y + mFontHeight/2 - 1);
907  currentPen.SetWidth(1);
908  }
909 }
910 
914 {
915  int result = -1;
916  wxMemoryDC dc;
917  if(msFont.Ok())
918  dc.SetFont(msFont);
919 
920  // A bool indicator to see if set the cursor position or not
921  bool finished = false;
922  int charIndex = 1;
923  int partWidth;
924  int oneWidth;
925  double bound;
926  wxString subString;
927  const auto &labelStruct = mLabels[mSelIndex];
928  const auto &title = labelStruct.title;
929  const int length = title.length();
930  while (!finished && (charIndex < length + 1))
931  {
932  subString = title.Left(charIndex);
933  // Get the width of substring
934  dc.GetTextExtent(subString, &partWidth, NULL);
935 
936  // Get the width of the last character
937  dc.GetTextExtent(subString.Right(1), &oneWidth, NULL);
938  bound = labelStruct.xText + partWidth - oneWidth * 0.5;
939 
940  if (xPos <= bound)
941  {
942  // Found
943  result = charIndex - 1;
944  finished = true;
945  }
946  else
947  {
948  // Advance
949  charIndex++;
950  }
951  }
952  if (!finished)
953  // Cursor should be in the last position
954  result = length;
955 
956  return result;
957 }
958 
961 {
963 }
964 
965 void LabelTrack::calculateFontHeight(wxDC & dc) const
966 {
967  int charDescent;
968  int charLeading;
969 
970  // Calculate the width of the substring and add it to Xpos
971  dc.GetTextExtent(wxT("(Test String)|[yp]"), NULL, &mFontHeight, &charDescent, &charLeading);
972 
973  // The cursor will have height charHeight. We don't include the descender as
974  // part of the height because for phonetic fonts this leads to cursors which are
975  // too tall. We don't include leading either - it is usually 0.
976  // To make up for ignoring the descender height, we add one pixel above and below
977  // using CursorExtraHeight so that the cursor is just a little taller than the
978  // body of the characters.
979  const int CursorExtraHeight=2;
980  mFontHeight += CursorExtraHeight - (charLeading+charDescent);
981 }
982 
984 {
985  if (mSelIndex == -1)
986  return false;
988  return false;
989  return true;
990 }
991 
995 {
996  if (!IsTextSelected())
997  return false;
998 
999  wxString left, right;
1000  auto &labelStruct = mLabels[mSelIndex];
1001  auto &text = labelStruct.title;
1002 
1003  int init = mInitialCursorPos;
1004  int cur = mCurrentCursorPos;
1005  if (init > cur)
1006  std::swap(init, cur);
1007 
1008  // data for cutting
1009  wxString data = text.Mid(init, cur - init);
1010 
1011  // get left-remaining text
1012  if (init > 0)
1013  left = text.Left(init);
1014 
1015  // get right-remaining text
1016  if (cur < (int)text.Length())
1017  right = text.Mid(cur);
1018 
1019  // set title to the combination of the two remainders
1020  text = left + right;
1021 
1022  // copy data onto clipboard
1023  if (wxTheClipboard->Open()) {
1024  // Clipboard owns the data you give it
1025  wxTheClipboard->SetData(safenew wxTextDataObject(data));
1026  wxTheClipboard->Close();
1027  }
1028 
1029  // set cursor positions
1030  mInitialCursorPos = mCurrentCursorPos = left.Length();
1031  return true;
1032 }
1033 
1037 {
1038  if (mSelIndex == -1)
1039  return false;
1040 
1041  const auto &labelStruct = mLabels[mSelIndex];
1042 
1043  int init = mInitialCursorPos;
1044  int cur = mCurrentCursorPos;
1045  if (init > cur)
1046  std::swap(init, cur);
1047 
1048  if (init == cur)
1049  return false;
1050 
1051  // data for copying
1052  wxString data = labelStruct.title.Mid(init, cur-init);
1053 
1054  // copy the data on clipboard
1055  if (wxTheClipboard->Open()) {
1056  // Clipboard owns the data you give it
1057  wxTheClipboard->SetData(safenew wxTextDataObject(data));
1058  wxTheClipboard->Close();
1059  }
1060 
1061  return true;
1062 }
1063 
1064 // PRL: should this set other fields of the label selection?
1067 bool LabelTrack::PasteSelectedText(double sel0, double sel1)
1068 {
1069  if (mSelIndex == -1)
1070  AddLabel(SelectedRegion(sel0, sel1), wxT(""));
1071 
1072  wxString text, left, right;
1073 
1074  // if text data is available
1075  if (IsTextClipSupported()) {
1076  if (wxTheClipboard->Open()) {
1077  wxTextDataObject data;
1078  wxTheClipboard->GetData(data);
1079  wxTheClipboard->Close();
1080  text = data.GetText();
1081  }
1082 
1083  // Convert control characters to blanks
1084  for (int i = 0; i < (int)text.Length(); i++) {
1085  if (wxIscntrl(text[i])) {
1086  text[i] = wxT(' ');
1087  }
1088  }
1089  }
1090 
1091  auto &labelStruct = mLabels[mSelIndex];
1092  auto &title = labelStruct.title;
1093  int cur = mCurrentCursorPos, init = mInitialCursorPos;
1094  if (init > cur)
1095  std::swap(init, cur);
1096  left = title.Left(init);
1097  if (cur < (int)title.Length())
1098  right = title.Mid(cur);
1099 
1100  title = left + text + right;
1101  mInitialCursorPos = mCurrentCursorPos = left.Length() + text.Length();
1102  return true;
1103 }
1104 
1105 
1108 {
1109  return wxTheClipboard->IsSupported(wxDF_TEXT);
1110 }
1111 
1112 
1114 {
1115  return mOffset;
1116 }
1117 
1119 {
1120  if (mLabels.empty())
1121  return 0.0;
1122  else
1123  return mLabels[0].getT0();
1124 }
1125 
1127 {
1128  //we need to scan through all the labels, because the last
1129  //label might not have the right-most end (if there is overlap).
1130  if (mLabels.empty())
1131  return 0.0;
1132 
1133  double end = 0.0;
1134  for (auto &labelStruct: mLabels) {
1135  const double t1 = labelStruct.getT1();
1136  if(t1 > end)
1137  end = t1;
1138  }
1139  return end;
1140 }
1141 
1143 {
1144  return std::make_unique<LabelTrack>( *this );
1145 }
1146 
1148 {
1149  Track::SetSelected(s);
1150  if (!s)
1151  Unselect();
1152 }
1153 
1157 void LabelTrack::OverGlyph(LabelTrackHit &hit, int x, int y) const
1158 {
1159  //Determine the NEW selection.
1160  int result=0;
1161  const int d1=10; //distance in pixels, used for have we hit drag handle.
1162  const int d2=5; //distance in pixels, used for have we hit drag handle center.
1163 
1164  //If not over a label, reset it
1165  hit.mMouseOverLabelLeft = -1;
1166  hit.mMouseOverLabelRight = -1;
1167  hit.mEdge = 0;
1168  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
1169  //over left or right selection bound
1170  //Check right bound first, since it is drawn after left bound,
1171  //so give it precedence for matching/highlighting.
1172  if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
1173  abs(labelStruct.x1 - d2 -x) < d1)
1174  {
1175  hit.mMouseOverLabelRight = i;
1176  if(abs(labelStruct.x1 - x) < d2 )
1177  {
1178  result |= 4;
1179  // If left and right co-incident at this resolution, then we drag both.
1180  // We could be a little less stringent about co-incidence here if we liked.
1181  if( abs(labelStruct.x1-labelStruct.x) < 1.0 )
1182  {
1183  result |=1;
1184  hit.mMouseOverLabelLeft = i;
1185  }
1186  }
1187  result |= 2;
1188  }
1189  // Use else-if here rather than else to avoid detecting left and right
1190  // of the same label.
1191  else if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
1192  abs(labelStruct.x + d2 - x) < d1 )
1193  {
1194  hit.mMouseOverLabelLeft = i;
1195  if(abs(labelStruct.x - x) < d2 )
1196  result |= 4;
1197  result |= 1;
1198  }
1199 
1200  // give text box better priority for selecting
1201  if(OverTextBox(&labelStruct, x, y))
1202  {
1203  result = 0;
1204  }
1205 
1206  }}
1207  hit.mEdge = result;
1208 }
1209 
1210 int LabelTrack::OverATextBox(int xx, int yy) const
1211 {
1212  for (int nn = (int)mLabels.size(); nn--;) {
1213  const auto &labelStruct = mLabels[nn];
1214  if (OverTextBox(&labelStruct, xx, yy))
1215  return nn;
1216  }
1217 
1218  return -1;
1219 }
1220 
1221 // return true if the mouse is over text box, false otherwise
1222 bool LabelTrack::OverTextBox(const LabelStruct *pLabel, int x, int y) const
1223 {
1224  if( (pLabel->xText-(mIconWidth/2) < x) &&
1225  (x<pLabel->xText+pLabel->width+(mIconWidth/2)) &&
1226  (abs(pLabel->y-y)<mIconHeight/2))
1227  {
1228  return true;
1229  }
1230  return false;
1231 }
1232 
1233 // Adjust label's left or right boundary, depending which is requested.
1234 // Return true iff the label flipped.
1235 bool LabelStruct::AdjustEdge( int iEdge, double fNewTime)
1236 {
1237  updated = true;
1238  if( iEdge < 0 )
1239  return selectedRegion.setT0(fNewTime);
1240  else
1241  return selectedRegion.setT1(fNewTime);
1242 }
1243 
1244 // We're moving the label. Adjust both left and right edge.
1245 void LabelStruct::MoveLabel( int iEdge, double fNewTime)
1246 {
1247  double fTimeSpan = getDuration();
1248 
1249  if( iEdge < 0 )
1250  {
1251  selectedRegion.setTimes(fNewTime, fNewTime+fTimeSpan);
1252  }
1253  else
1254  {
1255  selectedRegion.setTimes(fNewTime-fTimeSpan, fNewTime);
1256  }
1257  updated = true;
1258 }
1259 
1260 LabelStruct LabelStruct::Import(wxTextFile &file, int &index)
1261 {
1262  SelectedRegion sr;
1263  wxString title;
1264  static const wxString continuation{ wxT("\\") };
1265 
1266  wxString firstLine = file.GetLine(index++);
1267 
1268  {
1269  // Assume tab is an impossible character within the exported text
1270  // of the label, so can be only a delimiter. But other white space may
1271  // be part of the label text.
1272  wxStringTokenizer toker { firstLine, wxT("\t") };
1273 
1274  //get the timepoint of the left edge of the label.
1275  auto token = toker.GetNextToken();
1276 
1277  double t0;
1278  if (!Internat::CompatibleToDouble(token, &t0))
1279  throw BadFormatException{};
1280 
1281  token = toker.GetNextToken();
1282 
1283  double t1;
1284  if (!Internat::CompatibleToDouble(token, &t1))
1285  //s1 is not a number.
1286  t1 = t0; //This is a one-sided label; t1 == t0.
1287  else
1288  token = toker.GetNextToken();
1289 
1290  sr.setTimes( t0, t1 );
1291 
1292  title = token;
1293  }
1294 
1295  // Newer selection fields are written on additional lines beginning with
1296  // '\' which is an impossible numerical character that older versions of
1297  // audacity will ignore. Test for the presence of such a line and then
1298  // parse it if we can.
1299 
1300  // There may also be additional continuation lines from future formats that
1301  // we ignore.
1302 
1303  // Advance index over all continuation lines first, before we might throw
1304  // any exceptions.
1305  int index2 = index;
1306  while (index < (int)file.GetLineCount() &&
1307  file.GetLine(index).StartsWith(continuation))
1308  ++index;
1309 
1310  if (index2 < index) {
1311  wxStringTokenizer toker { file.GetLine(index2++), wxT("\t") };
1312  auto token = toker.GetNextToken();
1313  if (token != continuation)
1314  throw BadFormatException{};
1315 
1316  token = toker.GetNextToken();
1317  double f0;
1318  if (!Internat::CompatibleToDouble(token, &f0))
1319  throw BadFormatException{};
1320 
1321  token = toker.GetNextToken();
1322  double f1;
1323  if (!Internat::CompatibleToDouble(token, &f1))
1324  throw BadFormatException{};
1325 
1326  sr.setFrequencies(f0, f1);
1327  }
1328 
1329  return LabelStruct{ sr, title };
1330 }
1331 
1332 void LabelStruct::Export(wxTextFile &file) const
1333 {
1334  file.AddLine(wxString::Format(wxT("%s\t%s\t%s"),
1335  Internat::ToString(getT0(), FLT_DIG),
1336  Internat::ToString(getT1(), FLT_DIG),
1337  title
1338  ));
1339 
1340  // Do we need more lines?
1341  auto f0 = selectedRegion.f0();
1342  auto f1 = selectedRegion.f1();
1345  return;
1346 
1347  // Write a \ character at the start of a second line,
1348  // so that earlier versions of Audacity ignore it.
1349  file.AddLine(wxString::Format(wxT("\\\t%s\t%s"),
1350  Internat::ToString(f0, FLT_DIG),
1351  Internat::ToString(f1, FLT_DIG)
1352  ));
1353 
1354  // Additional lines in future formats should also start with '\'.
1355 }
1356 
1358  double reg_t0, double reg_t1, const LabelTrack * WXUNUSED(parent)) const
1359 -> TimeRelations
1360 {
1361  bool retainLabels = false;
1362 
1363  wxASSERT(reg_t0 <= reg_t1);
1364  gPrefs->Read(wxT("/GUI/RetainLabels"), &retainLabels);
1365 
1366  if(retainLabels) {
1367 
1368  // Desired behavior for edge cases: The length of the selection is smaller
1369  // than the length of the label if the selection is within the label or
1370  // matching exactly a (region) label.
1371 
1372  if (reg_t0 < getT0() && reg_t1 > getT1())
1373  return SURROUNDS_LABEL;
1374  else if (reg_t1 < getT0())
1375  return BEFORE_LABEL;
1376  else if (reg_t0 > getT1())
1377  return AFTER_LABEL;
1378 
1379  else if (reg_t0 >= getT0() && reg_t0 <= getT1() &&
1380  reg_t1 >= getT0() && reg_t1 <= getT1())
1381  return WITHIN_LABEL;
1382 
1383  else if (reg_t0 >= getT0() && reg_t0 <= getT1())
1384  return BEGINS_IN_LABEL;
1385  else
1386  return ENDS_IN_LABEL;
1387 
1388  } else {
1389 
1390  // AWD: Desired behavior for edge cases: point labels bordered by the
1391  // selection are included within it. Region labels are included in the
1392  // selection to the extent that the selection covers them; specifically,
1393  // they're not included at all if the selection borders them, and they're
1394  // fully included if the selection covers them fully, even if it just
1395  // borders their endpoints. This is just one of many possible schemes.
1396 
1397  // The first test catches bordered point-labels and selected-through
1398  // region-labels; move it to third and selection edges become inclusive
1399  // WRT point-labels.
1400  if (reg_t0 <= getT0() && reg_t1 >= getT1())
1401  return SURROUNDS_LABEL;
1402  else if (reg_t1 <= getT0())
1403  return BEFORE_LABEL;
1404  else if (reg_t0 >= getT1())
1405  return AFTER_LABEL;
1406 
1407  // At this point, all point labels should have returned.
1408 
1409  else if (reg_t0 > getT0() && reg_t0 < getT1() &&
1410  reg_t1 > getT0() && reg_t1 < getT1())
1411  return WITHIN_LABEL;
1412 
1413  // Knowing that none of the other relations match simplifies remaining
1414  // tests
1415  else if (reg_t0 > getT0() && reg_t0 < getT1())
1416  return BEGINS_IN_LABEL;
1417  else
1418  return ENDS_IN_LABEL;
1419 
1420  }
1421 }
1422 
1429 ( LabelTrackHit &hit, int iLabel, int iEdge, bool bAllowSwapping, double fNewTime)
1430 {
1431  if( iLabel < 0 )
1432  return;
1433  LabelStruct &labelStruct = mLabels[ iLabel ];
1434 
1435  // Adjust the requested edge.
1436  bool flipped = labelStruct.AdjustEdge( iEdge, fNewTime );
1437  // If the edges did not swap, then we are done.
1438  if( ! flipped )
1439  return;
1440 
1441  // If swapping's not allowed we must also move the edge
1442  // we didn't move. Then we're done.
1443  if( !bAllowSwapping )
1444  {
1445  labelStruct.AdjustEdge( -iEdge, fNewTime );
1446  return;
1447  }
1448 
1449  // Swap our record of what we are dragging.
1450  std::swap( hit.mMouseOverLabelLeft, hit.mMouseOverLabelRight );
1451 }
1452 
1453 // If the index is for a real label, adjust its left and right boundary.
1454 void LabelTrack::MayMoveLabel( int iLabel, int iEdge, double fNewTime)
1455 {
1456  if( iLabel < 0 )
1457  return;
1458  mLabels[ iLabel ].MoveLabel( iEdge, fNewTime );
1459 }
1460 
1461 // Constrain function, as in processing/arduino.
1462 // returned value will be between min and max (inclusive).
1463 static int Constrain( int value, int min, int max )
1464 {
1465  wxASSERT( min <= max );
1466  int result=value;
1467  if( result < min )
1468  result=min;
1469  if( result > max )
1470  result=max;
1471  return result;
1472 }
1473 
1475 (LabelTrackHit &hit, const wxMouseEvent & evt,
1476  wxRect & r, const ZoomInfo &zoomInfo,
1477  SelectedRegion *newSel)
1478 {
1479  if(evt.LeftUp())
1480  {
1481  bool lupd = false, rupd = false;
1482  if( hit.mMouseOverLabelLeft >= 0 ) {
1483  auto &labelStruct = mLabels[ hit.mMouseOverLabelLeft ];
1484  lupd = labelStruct.updated;
1485  labelStruct.updated = false;
1486  }
1487  if( hit.mMouseOverLabelRight >= 0 ) {
1488  auto &labelStruct = mLabels[ hit.mMouseOverLabelRight ];
1489  rupd = labelStruct.updated;
1490  labelStruct.updated = false;
1491  }
1492 
1493  hit.mIsAdjustingLabel = false;
1494  hit.mMouseOverLabelLeft = -1;
1495  hit.mMouseOverLabelRight = -1;
1496  return lupd || rupd;
1497  }
1498 
1499  if(evt.Dragging())
1500  {
1501  //If we are currently adjusting a label,
1502  //just reset its value and redraw.
1503  // LL: Constrain to inside track rectangle for now. Should be changed
1504  // to allow scrolling while dragging labels
1505  int x = Constrain( evt.m_x + mxMouseDisplacement - r.x, 0, r.width);
1506 
1507  // If exactly one edge is selected we allow swapping
1508  bool bAllowSwapping =
1509  ( hit.mMouseOverLabelLeft >=0 ) !=
1510  ( hit.mMouseOverLabelRight >= 0);
1511  // If we're on the 'dot' and nowe're moving,
1512  // Though shift-down inverts that.
1513  // and if both edges the same, then we're always moving the label.
1514  bool bLabelMoving = hit.mbIsMoving;
1515  bLabelMoving ^= evt.ShiftDown();
1516  bLabelMoving |= ( hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight );
1517  double fNewX = zoomInfo.PositionToTime(x, 0);
1518  if( bLabelMoving )
1519  {
1520  MayMoveLabel( hit.mMouseOverLabelLeft, -1, fNewX );
1521  MayMoveLabel( hit.mMouseOverLabelRight, +1, fNewX );
1522  }
1523  else
1524  {
1525  MayAdjustLabel( hit, hit.mMouseOverLabelLeft, -1, bAllowSwapping, fNewX );
1526  MayAdjustLabel( hit, hit.mMouseOverLabelRight, +1, bAllowSwapping, fNewX );
1527  }
1528 
1529  if( mSelIndex >=0 )
1530  {
1531  //Set the selection region to be equal to
1532  //the NEW size of the label.
1533  *newSel = mLabels[mSelIndex].selectedRegion;
1534  }
1535  SortLabels( &hit );
1536  }
1537 
1538  return false;
1539 }
1540 
1541 void LabelTrack::HandleTextDragRelease(const wxMouseEvent & evt)
1542 {
1543  if(evt.LeftUp())
1544  {
1545 #if 0
1546  // AWD: Due to wxWidgets bug #7491 (fix not ported to 2.8 branch) we
1547  // should never write the primary selection. We can enable this block
1548  // when we move to the 3.0 branch (or if a fixed 2.8 version is released
1549  // and we can do a runtime version check)
1550 #if defined (__WXGTK__) && defined (HAVE_GTK)
1551  // On GTK, if we just dragged out a text selection, set the primary
1552  // selection
1554  wxTheClipboard->UsePrimarySelection(true);
1555  CopySelectedText();
1556  wxTheClipboard->UsePrimarySelection(false);
1557  }
1558 #endif
1559 #endif
1560 
1561  return;
1562  }
1563 
1564  if(evt.Dragging())
1565  {
1566  if (!mRightDragging)
1567  // Update drag end
1568  SetCurrentCursorPosition(evt.m_x);
1569 
1570  return;
1571  }
1572 
1573  if (evt.RightUp()) {
1574  if ((mSelIndex != -1) && OverTextBox(GetLabel(mSelIndex), evt.m_x, evt.m_y)) {
1575  // popup menu for editing
1576  ShowContextMenu();
1577  }
1578  }
1579 
1580  return;
1581 }
1582 
1584 (LabelTrackHit &hit, const wxMouseEvent & evt,
1585  const wxRect & r, const ZoomInfo &zoomInfo,
1586  SelectedRegion *WXUNUSED(newSel))
1587 {
1588  if (evt.ButtonDown())
1589  {
1590  //OverGlyph sets mMouseOverLabel to be the chosen label.
1591  OverGlyph(hit, evt.m_x, evt.m_y);
1592  hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
1593  ( hit.mEdge & 3 ) != 0;
1594 
1595  if (hit.mIsAdjustingLabel)
1596  {
1597  float t = 0.0;
1598  // We move if we hit the centre, we adjust one edge if we hit a chevron.
1599  // This is if we are moving just one edge.
1600  hit.mbIsMoving = (hit.mEdge & 4)!=0;
1601  // When we start dragging the label(s) we don't want them to jump.
1602  // so we calculate the displacement of the mouse from the drag center
1603  // and use that in subsequent dragging calculations. The mouse stays
1604  // at the same relative displacement throughout dragging.
1605 
1606  // However, if two label's edges are being dragged
1607  // then the displacement is relative to the initial average
1608  // position of them, and in that case there can be a jump of at most
1609  // a few pixels to bring the two label boundaries to exactly the same
1610  // position when we start dragging.
1611 
1612  // Dragging of three label edges at the same time is not supported (yet).
1613  if( ( hit.mMouseOverLabelRight >= 0 ) &&
1614  ( hit.mMouseOverLabelLeft >= 0 )
1615  )
1616  {
1617  t = (mLabels[ hit.mMouseOverLabelRight ].getT1() +
1618  mLabels[ hit.mMouseOverLabelLeft ].getT0()) / 2.0f;
1619  // If we're moving two edges, then it's a move (label size preserved)
1620  // if both edges are the same label, and it's an adjust (label sizes change)
1621  // if we're on a boundary between two different labels.
1622  hit.mbIsMoving =
1624  }
1625  else if( hit.mMouseOverLabelRight >=0)
1626  {
1627  t = mLabels[ hit.mMouseOverLabelRight ].getT1();
1628  }
1629  else if( hit.mMouseOverLabelLeft >=0)
1630  {
1631  t = mLabels[ hit.mMouseOverLabelLeft ].getT0();
1632  }
1633  mxMouseDisplacement = zoomInfo.TimeToPosition(t, r.x) - evt.m_x;
1634  }
1635  }
1636 }
1637 
1638 void LabelTrack::HandleTextClick(const wxMouseEvent & evt,
1639  const wxRect & r, const ZoomInfo &zoomInfo,
1640  SelectedRegion *newSel)
1641 {
1642  static_cast<void>(r);//compiler food.
1643  static_cast<void>(zoomInfo);//compiler food.
1644  if (evt.ButtonDown())
1645  {
1646 
1647  mSelIndex = OverATextBox(evt.m_x, evt.m_y);
1648  if (mSelIndex != -1) {
1649  auto &labelStruct = mLabels[mSelIndex];
1650  *newSel = labelStruct.selectedRegion;
1651 
1652  if (evt.LeftDown()) {
1653  // Find the NEW drag end
1654  auto position = FindCurrentCursorPosition(evt.m_x);
1655 
1656  // Anchor shift-drag at the farther end of the previous highlight
1657  // that is farther from the click, on Mac, for consistency with
1658  // its text editors, but on the others, re-use the previous
1659  // anchor.
1660  if (evt.ShiftDown()) {
1661 #ifdef __WXMAC__
1662  // Set the drag anchor at the end of the previous selection
1663  // that is farther from the NEW drag end
1664  if (abs(position - mCurrentCursorPos) >
1665  abs(position - mInitialCursorPos))
1667 #else
1668  // mInitialCursorPos remains as before
1669 #endif
1670  }
1671  else
1672  mInitialCursorPos = position;
1673 
1674  mCurrentCursorPos = position;
1675 
1676  mDrawCursor = true;
1677  mRightDragging = false;
1678  }
1679  else
1680  // Actually this might be right or middle down
1681  mRightDragging = true;
1682 
1683  // reset the highlight indicator
1684  wxRect highlightedRect;
1685  {
1686  int xpos1, xpos2;
1687  CalcHighlightXs(&xpos1, &xpos2);
1688 
1689  wxASSERT(mFontHeight >= 0); // should have been set up while drawing
1690  // the rectangle of highlighted area
1691  highlightedRect = {
1692  xpos1, labelStruct.y - mFontHeight / 2,
1693  (int)(xpos2 - xpos1 + 0.5), mFontHeight
1694  };
1695  }
1696 
1697  // Middle click on GTK: paste from primary selection
1698 #if defined(__WXGTK__) && (HAVE_GTK)
1699  if (evt.MiddleDown()) {
1700  // Check for a click outside of the selected label's text box; in this
1701  // case PasteSelectedText() will start a NEW label at the click
1702  // location
1703  if (!OverTextBox(&labelStruct, evt.m_x, evt.m_y))
1704  mSelIndex = -1;
1705  double t = zoomInfo.PositionToTime(evt.m_x, r.x);
1706  *newSel = SelectedRegion(t, t);
1707  }
1708 #endif
1709  }
1710 #if defined(__WXGTK__) && (HAVE_GTK)
1711  if (evt.MiddleDown()) {
1712  // Paste text, making a NEW label if none is selected.
1713  wxTheClipboard->UsePrimarySelection(true);
1714  PasteSelectedText(newSel->t0(), newSel->t1());
1715  wxTheClipboard->UsePrimarySelection(false);
1716  }
1717 #endif
1718  }
1719 }
1720 
1721 // Check for keys that we will process
1722 bool LabelTrack::DoCaptureKey(wxKeyEvent & event)
1723 {
1724  // Check for modifiers and only allow shift
1725  int mods = event.GetModifiers();
1726  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1727  return false;
1728  }
1729 
1730  // Always capture the navigation keys, if we have any labels
1731  auto code = event.GetKeyCode();
1732  if ((code == WXK_TAB || code == WXK_NUMPAD_TAB) &&
1733  !mLabels.empty())
1734  return true;
1735 
1736  if (mSelIndex >= 0) {
1737  if (IsGoodLabelEditKey(event)) {
1738  return true;
1739  }
1740  }
1741  else {
1742  bool typeToCreateLabel;
1743  gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, true);
1744  if (IsGoodLabelFirstKey(event) && typeToCreateLabel) {
1745  AudacityProject * pProj = GetActiveProject();
1746 
1747 
1748 // The commented out code can prevent label creation, causing bug 1551
1749 // We should only be in DoCaptureKey IF this label track has focus,
1750 // and in that case creating a Label is the expected/intended thing.
1751 #if 0
1752  // If we're playing, don't capture if the selection is the same as the
1753  // playback region (this helps prevent label track creation from
1754  // stealing unmodified kbd. shortcuts)
1755  if (pProj->GetAudioIOToken() > 0 &&
1757  {
1758  double t0, t1;
1759  pProj->GetPlayRegion(&t0, &t1);
1760  if (pProj->mViewInfo.selectedRegion.t0() == t0 &&
1761  pProj->mViewInfo.selectedRegion.t1() == t1) {
1762  return false;
1763  }
1764  }
1765 #endif
1766 
1767  // If there's a label there already don't capture
1769  pProj->mViewInfo.selectedRegion.t1()) != wxNOT_FOUND ) {
1770  return false;
1771  }
1772 
1773  return true;
1774  }
1775  }
1776 
1777  return false;
1778 }
1779 
1780 unsigned LabelTrack::CaptureKey(wxKeyEvent & event, ViewInfo &, wxWindow *)
1781 {
1782  event.Skip(!DoCaptureKey(event));
1783  return RefreshCode::RefreshNone;
1784 }
1785 
1786 unsigned LabelTrack::KeyDown(wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *WXUNUSED(pParent))
1787 {
1788  double bkpSel0 = viewInfo.selectedRegion.t0(),
1789  bkpSel1 = viewInfo.selectedRegion.t1();
1790 
1791  AudacityProject *const pProj = GetActiveProject();
1792 
1793  // Pass keystroke to labeltrack's handler and add to history if any
1794  // updates were done
1795  if (OnKeyDown(viewInfo.selectedRegion, event)) {
1796  pProj->PushState(_("Modified Label"),
1797  _("Label Edit"),
1799  }
1800 
1801  // Make sure caret is in view
1802  int x;
1803  if (CalcCursorX(&x)) {
1804  pProj->GetTrackPanel()->ScrollIntoView(x);
1805  }
1806 
1807  // If selection modified, refresh
1808  // Otherwise, refresh track display if the keystroke was handled
1809  if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1810  bkpSel1 != viewInfo.selectedRegion.t1())
1811  return RefreshCode::RefreshAll;
1812  else if (!event.GetSkipped())
1813  return RefreshCode::RefreshCell;
1814 
1815  return RefreshCode::RefreshNone;
1816 }
1817 
1818 unsigned LabelTrack::Char(wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *)
1819 {
1820  double bkpSel0 = viewInfo.selectedRegion.t0(),
1821  bkpSel1 = viewInfo.selectedRegion.t1();
1822  // Pass keystroke to labeltrack's handler and add to history if any
1823  // updates were done
1824 
1825  AudacityProject *const pProj = GetActiveProject();
1826 
1827  if (OnChar(viewInfo.selectedRegion, event))
1828  pProj->PushState(_("Modified Label"),
1829  _("Label Edit"),
1831 
1832  // If selection modified, refresh
1833  // Otherwise, refresh track display if the keystroke was handled
1834  if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1835  bkpSel1 != viewInfo.selectedRegion.t1())
1836  return RefreshCode::RefreshAll;
1837  else if (!event.GetSkipped())
1838  return RefreshCode::RefreshCell;
1839 
1840  return RefreshCode::RefreshNone;
1841 }
1842 
1844 bool LabelTrack::OnKeyDown(SelectedRegion &newSel, wxKeyEvent & event)
1845 {
1846  // Only track true changes to the label
1847  bool updated = false;
1848 
1849  // Cache the keycode
1850  int keyCode = event.GetKeyCode();
1851  const int mods = event.GetModifiers();
1852 
1853  // Check for modifiers and only allow shift
1854  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1855  event.Skip();
1856  return updated;
1857  }
1858 
1859  // All editing keys are only active if we're currently editing a label
1860  if (mSelIndex >= 0) {
1861  auto &labelStruct = mLabels[mSelIndex];
1862  auto &title = labelStruct.title;
1863  switch (keyCode) {
1864 
1865  case WXK_BACK:
1866  {
1867  int len = title.Length();
1868 
1869  //IF the label is not blank THEN get rid of a letter or letters according to cursor position
1870  if (len > 0)
1871  {
1872  // IF there are some highlighted letters, THEN DELETE them
1875  else
1876  {
1877  // DELETE one letter
1878  if (mCurrentCursorPos > 0) {
1879  title.Remove(mCurrentCursorPos-1, 1);
1881  }
1882  }
1883  }
1884  else
1885  {
1886  // ELSE no text in text box, so DELETE whole label.
1888  }
1890  updated = true;
1891  }
1892  break;
1893 
1894  case WXK_DELETE:
1895  case WXK_NUMPAD_DELETE:
1896  {
1897  int len = title.Length();
1898 
1899  //If the label is not blank get rid of a letter according to cursor position
1900  if (len > 0)
1901  {
1902  // if there are some highlighted letters, DELETE them
1905  else
1906  {
1907  // DELETE one letter
1908  if (mCurrentCursorPos < len) {
1909  title.Remove(mCurrentCursorPos, 1);
1910  }
1911  }
1912  }
1913  else
1914  {
1915  // DELETE whole label if no text in text box
1917  }
1919  updated = true;
1920  }
1921  break;
1922 
1923  case WXK_HOME:
1924  case WXK_NUMPAD_HOME:
1925  // Move cursor to beginning of label
1926  mCurrentCursorPos = 0;
1927  if (mods == wxMOD_SHIFT)
1928  ;
1929  else
1931  break;
1932 
1933  case WXK_END:
1934  case WXK_NUMPAD_END:
1935  // Move cursor to end of label
1936  mCurrentCursorPos = (int)title.length();
1937  if (mods == wxMOD_SHIFT)
1938  ;
1939  else
1941  break;
1942 
1943  case WXK_LEFT:
1944  case WXK_NUMPAD_LEFT:
1945  // Moving cursor left
1946  if (mCurrentCursorPos > 0) {
1948  if (mods == wxMOD_SHIFT)
1949  ;
1950  else
1953  }
1954  break;
1955 
1956  case WXK_RIGHT:
1957  case WXK_NUMPAD_RIGHT:
1958  // Moving cursor right
1959  if (mCurrentCursorPos < (int)title.length()) {
1961  if (mods == wxMOD_SHIFT)
1962  ;
1963  else
1966  }
1967  break;
1968 
1969  case WXK_RETURN:
1970  case WXK_NUMPAD_ENTER:
1971 
1972  case WXK_ESCAPE:
1973  if (mRestoreFocus >= 0) {
1974  auto track = *GetActiveProject()->GetTracks()->Any()
1975  .begin().advance(mRestoreFocus);
1976  if (track)
1978  mRestoreFocus = -1;
1979  }
1980  mSelIndex = -1;
1981  break;
1982 
1983  case WXK_TAB:
1984  case WXK_NUMPAD_TAB:
1985  if (event.ShiftDown()) {
1986  mSelIndex--;
1987  } else {
1988  mSelIndex++;
1989  }
1990 
1991  mSelIndex = (mSelIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
1992  {
1993  LabelStruct &newLabel = mLabels[mSelIndex];
1994  mCurrentCursorPos = newLabel.title.Length();
1996  //Set the selection region to be equal to the selection bounds of the tabbed-to label.
1997  newSel = newLabel.selectedRegion;
1998  }
1999  break;
2000 
2001  case '\x10': // OSX
2002  case WXK_MENU:
2003  case WXK_WINDOWS_MENU:
2004  ShowContextMenu();
2005  break;
2006 
2007  default:
2008  if (!IsGoodLabelEditKey(event)) {
2009  event.Skip();
2010  }
2011  break;
2012  }
2013  }
2014  else
2015  {
2016  switch (keyCode) {
2017 
2018  case WXK_TAB:
2019  case WXK_NUMPAD_TAB:
2020  if (!mLabels.empty()) {
2021  int len = (int) mLabels.size();
2022  if (event.ShiftDown()) {
2023  mSelIndex = len - 1;
2024  if (newSel.t0() > mLabels[0].getT0()) {
2025  while (mSelIndex >= 0 &&
2026  mLabels[mSelIndex].getT0() > newSel.t0()) {
2027  mSelIndex--;
2028  }
2029  }
2030  } else {
2031  mSelIndex = 0;
2032  if (newSel.t0() < mLabels[len - 1].getT0()) {
2033  while (mSelIndex < len &&
2034  mLabels[mSelIndex].getT0() < newSel.t0()) {
2035  mSelIndex++;
2036  }
2037  }
2038  }
2039 
2040  if (mSelIndex >= 0 && mSelIndex < len) {
2041  const auto &labelStruct = mLabels[mSelIndex];
2042  mCurrentCursorPos = labelStruct.title.Length();
2044  //Set the selection region to be equal to the selection bounds of the tabbed-to label.
2045  newSel = labelStruct.selectedRegion;
2046  }
2047  else {
2048  mSelIndex = -1;
2049  }
2050  }
2051  break;
2052 
2053  default:
2054  if (!IsGoodLabelFirstKey(event)) {
2055  event.Skip();
2056  }
2057  break;
2058  }
2059  }
2060 
2061  // Make sure the caret is visible
2062  mDrawCursor = true;
2063 
2064  return updated;
2065 }
2066 
2069 bool LabelTrack::OnChar(SelectedRegion &WXUNUSED(newSel), wxKeyEvent & event)
2070 {
2071  // Check for modifiers and only allow shift.
2072  //
2073  // We still need to check this or we will eat the top level menu accelerators
2074  // on Windows if our capture or key down handlers skipped the event.
2075  const int mods = event.GetModifiers();
2076  if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
2077  event.Skip();
2078  return false;
2079  }
2080 
2081  // Only track true changes to the label
2082  bool updated = false;
2083 
2084  // Cache the character
2085  wxChar charCode = event.GetUnicodeKey();
2086 
2087  // Skip if it's not a valid unicode character or a control character
2088  if (charCode == 0 || wxIscntrl(charCode)) {
2089  event.Skip();
2090  return false;
2091  }
2092 
2093  // If we've reached this point and aren't currently editing, add NEW label
2094  if (mSelIndex < 0) {
2095  // Don't create a NEW label for a space
2096  if (wxIsspace(charCode)) {
2097  event.Skip();
2098  return false;
2099  }
2100  bool useDialog;
2102  gPrefs->Read(wxT("/Gui/DialogForNameNewLabel"), &useDialog, false);
2103  if (useDialog) {
2104  wxString title;
2105  if (MenuCommandHandler::DialogForLabelName(*p, charCode, title) ==
2106  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  GetMenuCommandHandler(*p).DoEditLabels(*p, 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  auto result = src->TypeSwitch< bool >( [&](const LabelTrack *sl) {
2552  int len = mLabels.size();
2553  int pos = 0;
2554 
2555  while (pos < len && mLabels[pos].getT0() < t)
2556  pos++;
2557 
2558  for (auto &labelStruct: sl->mLabels) {
2559  LabelStruct l {
2560  labelStruct.selectedRegion,
2561  labelStruct.getT0() + t,
2562  labelStruct.getT1() + t,
2563  labelStruct.title
2564  };
2565  mLabels.insert(mLabels.begin() + pos++, l);
2566  }
2567 
2568  return true;
2569  } );
2570 
2571  if (! result )
2572  // THROW_INCONSISTENCY_EXCEPTION; // ?
2573  (void)0;// intentionally do nothing
2574 
2575  return result;
2576 }
2577 
2578 void LabelTrack::Paste(double t, const Track *src)
2579 {
2580  bool bOk = src->TypeSwitch< bool >( [&](const LabelTrack *lt) {
2581  double shiftAmt = lt->mClipLen > 0.0 ? lt->mClipLen : lt->GetEndTime();
2582 
2583  ShiftLabelsOnInsert(shiftAmt, t);
2584  PasteOver(t, src);
2585 
2586  return true;
2587  } );
2588 
2589  if ( !bOk )
2590  // THROW_INCONSISTENCY_EXCEPTION; // ?
2591  (void)0;// intentionally do nothing
2592 }
2593 
2594 // This repeats the labels in a time interval a specified number of times.
2595 bool LabelTrack::Repeat(double t0, double t1, int n)
2596 {
2597  // Sanity-check the arguments
2598  if (n < 0 || t1 < t0)
2599  return false;
2600 
2601  double tLen = t1 - t0;
2602 
2603  // Insert space for the repetitions
2604  ShiftLabelsOnInsert(tLen * n, t1);
2605 
2606  // mLabels may resize as we iterate, so use subscripting
2607  for (unsigned int i = 0; i < mLabels.size(); ++i)
2608  {
2609  LabelStruct::TimeRelations relation =
2610  mLabels[i].RegionRelation(t0, t1, this);
2611  if (relation == LabelStruct::SURROUNDS_LABEL)
2612  {
2613  // Label is completely inside the selection; duplicate it in each
2614  // repeat interval
2615  unsigned int pos = i; // running label insertion position in mLabels
2616 
2617  for (int j = 1; j <= n; j++)
2618  {
2619  const LabelStruct &label = mLabels[i];
2620  LabelStruct l {
2621  label.selectedRegion,
2622  label.getT0() + j * tLen,
2623  label.getT1() + j * tLen,
2624  label.title
2625  };
2626 
2627  // Figure out where to insert
2628  while (pos < mLabels.size() &&
2629  mLabels[pos].getT0() < l.getT0())
2630  pos++;
2631  mLabels.insert(mLabels.begin() + pos, l);
2632  }
2633  }
2634  else if (relation == LabelStruct::BEGINS_IN_LABEL)
2635  {
2636  // Label ends inside the selection; ShiftLabelsOnInsert() hasn't touched
2637  // it, and we need to extend it through to the last repeat interval
2638  mLabels[i].selectedRegion.moveT1(n * tLen);
2639  }
2640 
2641  // Other cases have already been handled by ShiftLabelsOnInsert()
2642  }
2643 
2644  return true;
2645 }
2646 
2647 void LabelTrack::Silence(double t0, double t1)
2648 {
2649  int len = mLabels.size();
2650 
2651  // mLabels may resize as we iterate, so use subscripting
2652  for (int i = 0; i < len; ++i) {
2653  LabelStruct::TimeRelations relation =
2654  mLabels[i].RegionRelation(t0, t1, this);
2655  if (relation == LabelStruct::WITHIN_LABEL)
2656  {
2657  // Split label around the selection
2658  const LabelStruct &label = mLabels[i];
2659  LabelStruct l {
2660  label.selectedRegion,
2661  t1,
2662  label.getT1(),
2663  label.title
2664  };
2665 
2666  mLabels[i].selectedRegion.setT1(t0);
2667 
2668  // This might not be the right place to insert, but we sort at the end
2669  ++i;
2670  mLabels.insert(mLabels.begin() + i, l);
2671  }
2672  else if (relation == LabelStruct::ENDS_IN_LABEL)
2673  {
2674  // Beginning of label to selection end
2675  mLabels[i].selectedRegion.setT0(t1);
2676  }
2677  else if (relation == LabelStruct::BEGINS_IN_LABEL)
2678  {
2679  // End of label to selection beginning
2680  mLabels[i].selectedRegion.setT1(t0);
2681  }
2682  else if (relation == LabelStruct::SURROUNDS_LABEL)
2683  {
2684  DeleteLabel( i );
2685  len--;
2686  i--;
2687  }
2688  }
2689 
2690  SortLabels();
2691 }
2692 
2693 void LabelTrack::InsertSilence(double t, double len)
2694 {
2695  for (auto &labelStruct: mLabels) {
2696  double t0 = labelStruct.getT0();
2697  double t1 = labelStruct.getT1();
2698  if (t0 >= t)
2699  t0 += len;
2700 
2701  if (t1 >= t)
2702  t1 += len;
2703  labelStruct.selectedRegion.setTimes(t0, t1);
2704  }
2705 }
2706 
2708 {
2709  return mLabels.size();
2710 }
2711 
2712 const LabelStruct *LabelTrack::GetLabel(int index) const
2713 {
2714  return &mLabels[index];
2715 }
2716 
2717 int LabelTrack::GetLabelIndex(double t, double t1)
2718 {
2719  //We'd have liked to have times in terms of samples,
2720  //because then we're doing an intrger comparison.
2721  //Never mind. Instead we look for near enough.
2722  //This level of (in)accuracy is only a problem if we
2723  //deal with sounds in the MHz range.
2724  const double delta = 1.0e-7;
2725  { int i = -1; for (auto &labelStruct : mLabels) { ++i;
2726  if( fabs( labelStruct.getT0() - t ) > delta )
2727  continue;
2728  if( fabs( labelStruct.getT1() - t1 ) > delta )
2729  continue;
2730  return i;
2731  }}
2732 
2733  return wxNOT_FOUND;
2734 }
2735 
2736 
2737 // restoreFocus of -1 is the default, and sets the focus to this label.
2738 // restoreFocus of -2 or other value leaves the focus unchanged.
2739 // restoreFocus >= 0 will later cause focus to move to that track.
2740 int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
2741  const wxString &title, int restoreFocus)
2742 {
2743  LabelStruct l { selectedRegion, title };
2744  mInitialCursorPos = mCurrentCursorPos = title.length();
2745 
2746  int len = mLabels.size();
2747  int pos = 0;
2748 
2749  while (pos < len && mLabels[pos].getT0() < selectedRegion.t0())
2750  pos++;
2751 
2752  mLabels.insert(mLabels.begin() + pos, l);
2753 
2754  // restoreFocus is -2 e.g. from Nyquist label creation, when we should not
2755  // even lose the focus and open the label to edit in the first place.
2756  // -1 means we don't need to restore it to anywhere.
2757  // 0 or above is the track to restore to afetr editing the label is complete.
2758  if( restoreFocus >= -1 )
2759  mSelIndex = pos;
2760 
2761  // Make sure the caret is visible
2762  //
2763  // LLL: The cursor will not be drawn when the first label
2764  // is added since mDrawCursor will be false. Presumably,
2765  // if the user adds a label, then a cursor should be drawn
2766  // to indicate that typing is expected.
2767  //
2768  // If the label is added during actions like import, then the
2769  // mDrawCursor flag will be reset once the action is complete.
2770  mDrawCursor = true;
2771 
2772  mRestoreFocus = restoreFocus;
2773 
2774  return pos;
2775 }
2776 
2778 {
2779  wxASSERT((index < (int)mLabels.size()));
2780  mLabels.erase(mLabels.begin() + index);
2781  // IF we've deleted the selected label
2782  // THEN set no label selected.
2783  if( mSelIndex== index )
2784  {
2785  mSelIndex = -1;
2786  mCurrentCursorPos = 1;
2787  }
2788  // IF we removed a label before the selected label
2789  // THEN the NEW selected label number is one less.
2790  else if( index < mSelIndex )
2791  {
2792  mSelIndex--;
2793  }
2794 }
2795 
2796 wxBitmap & LabelTrack::GetGlyph( int i)
2797 {
2798  return theTheme.Bitmap( i + bmpLabelGlyph0);
2799 }
2800 
2801 // This one XPM spec is used to generate a number of
2802 // different wxIcons.
2803 /* XPM */
2804 static const char *const GlyphXpmRegionSpec[] = {
2805 /* columns rows colors chars-per-pixel */
2806 "15 23 7 1",
2807 /* Default colors, with first color transparent */
2808 ". c none",
2809 "2 c black",
2810 "3 c black",
2811 "4 c black",
2812 "5 c #BEBEF0",
2813 "6 c #BEBEF0",
2814 "7 c #BEBEF0",
2815 /* pixels */
2816 "...............",
2817 "...............",
2818 "...............",
2819 "....333.444....",
2820 "...3553.4774...",
2821 "...3553.4774...",
2822 "..35553.47774..",
2823 "..35522222774..",
2824 ".3552666662774.",
2825 ".3526666666274.",
2826 "355266666662774",
2827 "355266666662774",
2828 "355266666662774",
2829 ".3526666666274.",
2830 ".3552666662774.",
2831 "..35522222774..",
2832 "..35553.47774..",
2833 "...3553.4774...",
2834 "...3553.4774...",
2835 "....333.444....",
2836 "...............",
2837 "...............",
2838 "..............."
2839 };
2840 
2857 {
2858  int iConfig;
2859  int iHighlight;
2860  int index;
2861  const int nSpecRows =
2862  sizeof( GlyphXpmRegionSpec )/sizeof( GlyphXpmRegionSpec[0]);
2863  const char *XmpBmp[nSpecRows];
2864 
2865  // The glyphs are declared static wxIcon; so we only need
2866  // to create them once, no matter how many LabelTracks.
2867  if( mbGlyphsReady )
2868  return;
2869 
2870  // We're about to tweak the basic color spec to get 12 variations.
2871  for( iConfig=0;iConfig<NUM_GLYPH_CONFIGS;iConfig++)
2872  {
2873  for( iHighlight=0;iHighlight<NUM_GLYPH_HIGHLIGHTS;iHighlight++)
2874  {
2875  index = iConfig + NUM_GLYPH_CONFIGS * iHighlight;
2876  // Copy the basic spec...
2877  memcpy( XmpBmp, GlyphXpmRegionSpec, sizeof( GlyphXpmRegionSpec ));
2878  // The higlighted region (if any) is white...
2879  if( iHighlight==1 ) XmpBmp[5]="5 c #FFFFFF";
2880  if( iHighlight==2 ) XmpBmp[6]="6 c #FFFFFF";
2881  if( iHighlight==3 ) XmpBmp[7]="7 c #FFFFFF";
2882  // For left or right arrow the other side of the glyph
2883  // is the transparent color.
2884  if( iConfig==0) { XmpBmp[3]="3 c none"; XmpBmp[5]="5 c none"; }
2885  if( iConfig==1) { XmpBmp[4]="4 c none"; XmpBmp[7]="7 c none"; }
2886  // Create the icon from the tweaked spec.
2887  mBoundaryGlyphs[index] = wxBitmap(XmpBmp);
2888  // Create the mask
2889  // SetMask takes ownership
2890  mBoundaryGlyphs[index].SetMask(safenew wxMask(mBoundaryGlyphs[index], wxColour(192, 192, 192)));
2891  }
2892  }
2893 
2894  mIconWidth = mBoundaryGlyphs[0].GetWidth();
2895  mIconHeight = mBoundaryGlyphs[0].GetHeight();
2896  mTextHeight = mIconHeight; // until proved otherwise...
2897  // The icon should have an odd width so that the
2898  // line goes exactly down the middle.
2899  wxASSERT( (mIconWidth %2)==1);
2900 
2901  mbGlyphsReady=true;
2902 }
2903 
2905 bool LabelTrack::IsGoodLabelFirstKey(const wxKeyEvent & evt)
2906 {
2907  int keyCode = evt.GetKeyCode();
2908  return (keyCode < WXK_START
2909  && keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) ||
2910  (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
2911  (keyCode >= WXK_NUMPAD_EQUAL && keyCode <= WXK_NUMPAD_DIVIDE) ||
2912 #if defined(__WXMAC__)
2913  (keyCode > WXK_RAW_CONTROL) ||
2914 #endif
2915  (keyCode > WXK_WINDOWS_MENU);
2916 }
2917 
2919 bool LabelTrack::IsGoodLabelEditKey(const wxKeyEvent & evt)
2920 {
2921  int keyCode = evt.GetKeyCode();
2922 
2923  // Accept everything outside of WXK_START through WXK_COMMAND, plus the keys
2924  // within that range that are usually printable, plus the ones we use for
2925  // keyboard navigation.
2926  return keyCode < WXK_START ||
2927  (keyCode >= WXK_END && keyCode < WXK_UP) ||
2928  (keyCode == WXK_RIGHT) ||
2929  (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
2930  (keyCode >= WXK_NUMPAD_SPACE && keyCode <= WXK_NUMPAD_ENTER) ||
2931  (keyCode >= WXK_NUMPAD_HOME && keyCode <= WXK_NUMPAD_END) ||
2932  (keyCode >= WXK_NUMPAD_DELETE && keyCode <= WXK_NUMPAD_DIVIDE) ||
2933 #if defined(__WXMAC__)
2934  (keyCode > WXK_RAW_CONTROL) ||
2935 #endif
2936  (keyCode > WXK_WINDOWS_MENU);
2937 }
2938 
2944 {
2945  const auto begin = mLabels.begin();
2946  const auto nn = (int)mLabels.size();
2947  int i = 1;
2948  while (true)
2949  {
2950  // Find the next disorder
2951  while (i < nn && mLabels[i - 1].getT0() <= mLabels[i].getT0())
2952  ++i;
2953  if (i >= nn)
2954  break;
2955 
2956  // Where must element i sink to? At most i - 1, maybe less
2957  int j = i - 2;
2958  while( (j >= 0) && (mLabels[j].getT0() > mLabels[i].getT0()) )
2959  --j;
2960  ++j;
2961 
2962  // Now fix the disorder
2963  std::rotate(
2964  begin + j,
2965  begin + i,
2966  begin + i + 1
2967  );
2968 
2969  // Various indices need to be updated with the moved items...
2970  auto update = [=](int &index) {
2971  if( index <= i ) {
2972  if( index == i )
2973  index = j;
2974  else if( index >= j)
2975  ++index;
2976  }
2977  };
2978  if ( pHit ) {
2979  update( pHit->mMouseOverLabelLeft );
2980  update( pHit->mMouseOverLabelRight );
2981  }
2982  update(mSelIndex);
2983  }
2984 }
2985 
2986 wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
2987 {
2988  bool firstLabel = true;
2989  wxString retVal;
2990 
2991  for (auto &labelStruct: mLabels) {
2992  if (labelStruct.getT0() >= t0 &&
2993  labelStruct.getT1() <= t1)
2994  {
2995  if (!firstLabel)
2996  retVal += '\t';
2997  firstLabel = false;
2998  retVal += labelStruct.title;
2999  }
3000  }
3001 
3002  return retVal;
3003 }
3004 
3006 {
3007  int i = -1;
3008 
3009  if (!mLabels.empty()) {
3010  int len = (int) mLabels.size();
3011  if (miLastLabel >= 0 && miLastLabel + 1 < len
3012  && currentRegion.t0() == mLabels[miLastLabel].getT0()
3013  && currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
3014  i = miLastLabel + 1;
3015  }
3016  else {
3017  i = 0;
3018  if (currentRegion.t0() < mLabels[len - 1].getT0()) {
3019  while (i < len &&
3020  mLabels[i].getT0() <= currentRegion.t0()) {
3021  i++;
3022  }
3023  }
3024  }
3025  }
3026 
3027  miLastLabel = i;
3028  return i;
3029 }
3030 
3031  int LabelTrack::FindPrevLabel(const SelectedRegion& currentRegion)
3032 {
3033  int i = -1;
3034 
3035  if (!mLabels.empty()) {
3036  int len = (int) mLabels.size();
3037  if (miLastLabel > 0 && miLastLabel < len
3038  && currentRegion.t0() == mLabels[miLastLabel].getT0()
3039  && currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
3040  i = miLastLabel - 1;
3041  }
3042  else {
3043  i = len - 1;
3044  if (currentRegion.t0() > mLabels[0].getT0()) {
3045  while (i >=0 &&
3046  mLabels[i].getT0() >= currentRegion.t0()) {
3047  i--;
3048  }
3049  }
3050  }
3051  }
3052 
3053  miLastLabel = i;
3054  return i;
3055 }
static int mIconHeight
Definition: LabelTrack.h:284
bool IsTextSelected()
Definition: LabelTrack.cpp:983
void getXPos(wxDC &dc, int *xPos1, int cursorPos) const
Definition: LabelTrack.cpp:712
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:160
void ChangeLabelsOnReverse(double b, double e)
Definition: LabelTrack.cpp:214
Creates and manages BlockFile objects.
Definition: DirManager.h:52
void SetOffset(double dOffset) override
Definition: LabelTrack.cpp:146
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)
R TypeSwitch(const Functions &...functions)
Definition: Track.h:623
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:913
void SetCurrentCursorPosition(int xPos)
Set the cursor position according to x position of mouse.
Definition: LabelTrack.cpp:960
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:785
static int DialogForLabelName(AudacityProject &project, const wxString &initialValue, wxString &value)
Definition: Menus.cpp:9010
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:724
void calculateFontHeight(wxDC &dc) const
Definition: LabelTrack.cpp:965
static wxBrush labelTextNormalBrush
Definition: AColor.h:126
void SetHeight(int h)
Definition: Track.cpp:179
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:462
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
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1195
bool updated
Pixel position of label.
Definition: LabelTrack.h:101
const std::shared_ptr< DirManager > mDirManager
Definition: Track.h:1572
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:1452
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:2757
double GetStartTime() const override
void Clear(double t0, double t1) override
Definition: LabelTrack.cpp:152
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:229
void SetMinimized(bool isMinimized)
Definition: Track.cpp:209
void DrawHighlight(wxDC &dc, int xPos1, int xPos2, int charHeight) const
Draws text-selected region within the label.
Definition: LabelTrack.cpp:700
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:393
static int mIconWidth
Definition: LabelTrack.h:285
static void ResetFont()
Definition: LabelTrack.cpp:297
LabelArray mLabels
Displacement of mouse cursor from the centre being dragged.
Definition: LabelTrack.h:282
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:94
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:371
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:238
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:175
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:286
void DrawTextBox(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:649
static bool IsGoodString(const wxString &str)
void ComputeTextPosition(const wxRect &r, int index) const
Definition: LabelTrack.cpp:318
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:257
LabelStruct(const SelectedRegion &region, const wxString &aTitle)
Definition: LabelTrack.cpp:529
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:190
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:562
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)
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 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:347
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
LabelTrack(const std::shared_ptr< DirManager > &projDirManager)
Definition: LabelTrack.cpp:105
virtual ~LabelTrack()
Definition: LabelTrack.cpp:142
void SetDefaultName(const wxString &n)
Definition: Track.h:372
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:492
static bool mbGlyphsReady
Definition: LabelTrack.h:287
void CalcHighlightXs(int *x1, int *x2) const
Definition: LabelTrack.cpp:741
void PushState(const wxString &desc, const wxString &shortDesc)
Definition: Project.cpp:4617
void SetName(const wxString &n)
Definition: Track.h:370
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent) override
double getT1() const
Definition: LabelTrack.h:62
void SortLabels(LabelTrackHit *pHit=nullptr)
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:312
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:309
bool setFrequencies(double f0, double f1)
void ShiftLabelsOnInsert(double length, double pt)
Definition: LabelTrack.cpp:201
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:362
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
TrackList * GetTracks()
Definition: Project.h:192
double getT0() const
Definition: LabelTrack.h:61
void DrawText(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:624
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:5337
int OverATextBox(int xx, int yy) const
void ResetFlags()
Definition: LabelTrack.cpp:269
std::unique_ptr< LabelTrack > NewLabelTrack()
Definition: LabelTrack.cpp:100
int AddLabel(const SelectedRegion &region, const wxString &title=wxT(""), int restoreFocus=-1)
void DrawLines(wxDC &dc, const wxRect &r) const
Definition: LabelTrack.cpp:563
void DrawGlyphs(wxDC &dc, const wxRect &r, int GlyphLeft, int GlyphRight) const
Definition: LabelTrack.cpp:601
void RestoreFlags(const Flags &flags)
Definition: LabelTrack.cpp:277
void DoEditLabels(AudacityProject &project, LabelTrack *lt=nullptr, int index=-1)
Definition: Menus.cpp:9291
static const int UndefinedFrequency
bool CutSelectedText()
Definition: LabelTrack.cpp:994
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.
MenuCommandHandler & GetMenuCommandHandler(AudacityProject &project)
Definition: Menus.cpp:152
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:205