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