Audacity 3.2.0
LabelTrackView.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5LabelTrackView.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "LabelTrackView.h"
13
15#include "LabelGlyphHandle.h"
16#include "LabelTextHandle.h"
17
18#include "../../../LabelTrack.h"
19
20#include "AColor.h"
21#include "../../../widgets/BasicMenu.h"
22#include "AllThemeResources.h"
23#include "../../../HitTestResult.h"
24#include "Project.h"
25#include "ProjectHistory.h"
26#include "ProjectRate.h"
27#include "../../../ProjectSettings.h"
28#include "../../../ProjectWindow.h"
29#include "../../../ProjectWindows.h"
30#include "../../../RefreshCode.h"
31#include "../../../SyncLock.h"
32#include "Theme.h"
33#include "../../../TrackArt.h"
34#include "../../../TrackArtist.h"
35#include "../../../TrackPanelAx.h"
36#include "../../../TrackPanel.h"
37#include "../../../TrackPanelMouseEvent.h"
38#include "UndoManager.h"
39#include "ViewInfo.h"
40#include "../../../widgets/AudacityTextEntryDialog.h"
41#include "../../../widgets/wxWidgetsWindowPlacement.h"
42
43#include <wx/clipbrd.h>
44#include <wx/dcclient.h>
45#include <wx/font.h>
46#include <wx/frame.h>
47#include <wx/menu.h>
48
50: mIndex(-1),
51 mModified(false)
52{
53}
54
56: mIndex(index),
57 mModified(false)
58{
59}
60
62{
63 if (index != mIndex) {
64 mModified = false;
65 }
66 mIndex = index;
67 return *this;
68}
69
71{
72 mModified = false;
73 mIndex += 1;
74 return *this;
75}
76
78{
79 mModified = false;
80 mIndex -= 1;
81 return *this;
82}
83
84LabelTrackView::Index::operator int() const
85{
86 return mIndex;
87}
88
90{
91 return mModified;
92}
93
95{
96 mModified = modified;
97}
98
99LabelTrackView::LabelTrackView( const std::shared_ptr<Track> &pTrack )
100 : CommonTrackView{ pTrack }
101{
102 ResetFont();
104 ResetFlags();
105
106 // Events will be emitted by the track
107 const auto pLabelTrack = FindLabelTrack();
108 BindTo( pLabelTrack.get() );
109}
110
112{
113}
114
115void LabelTrackView::Reparent( const std::shared_ptr<Track> &parent )
116{
117 auto oldParent = FindLabelTrack();
118 auto newParent = track_cast<LabelTrack*>(parent.get());
119 if (oldParent.get() != newParent) {
120 UnbindFrom( oldParent.get() );
121 BindTo( newParent );
122 }
124}
125
127{
128 pParent->Bind(
129 EVT_LABELTRACK_ADDITION, &LabelTrackView::OnLabelAdded, this );
130 pParent->Bind(
131 EVT_LABELTRACK_DELETION, &LabelTrackView::OnLabelDeleted, this );
132 pParent->Bind(
133 EVT_LABELTRACK_PERMUTED, &LabelTrackView::OnLabelPermuted, this );
134 pParent->Bind(
135 EVT_LABELTRACK_SELECTION, &LabelTrackView::OnSelectionChange, this );
136}
137
139{
140 pParent->Unbind(
141 EVT_LABELTRACK_ADDITION, &LabelTrackView::OnLabelAdded, this );
142 pParent->Unbind(
143 EVT_LABELTRACK_DELETION, &LabelTrackView::OnLabelDeleted, this );
144 pParent->Unbind(
145 EVT_LABELTRACK_PERMUTED, &LabelTrackView::OnLabelPermuted, this );
146 pParent->Unbind(
147 EVT_LABELTRACK_SELECTION, &LabelTrackView::OnSelectionChange, this );
148}
149
150void LabelTrackView::CopyTo( Track &track ) const
151{
152 TrackView::CopyTo( track );
153 auto &other = TrackView::Get( track );
154
155 if ( const auto pOther = dynamic_cast< const LabelTrackView* >( &other ) ) {
156 pOther->mNavigationIndex = mNavigationIndex;
157 pOther->mInitialCursorPos = mInitialCursorPos;
158 pOther->mCurrentCursorPos = mCurrentCursorPos;
159 pOther->mTextEditIndex = mTextEditIndex;
160 pOther->mUndoLabel = mUndoLabel;
161 }
162}
163
165{
166 return static_cast< LabelTrackView& >( TrackView::Get( track ) );
167}
168
170{
171 return static_cast< const LabelTrackView& >( TrackView::Get( track ) );
172}
173
174std::shared_ptr<LabelTrack> LabelTrackView::FindLabelTrack()
175{
176 return std::static_pointer_cast<LabelTrack>( FindTrack() );
177}
178
179std::shared_ptr<const LabelTrack> LabelTrackView::FindLabelTrack() const
180{
181 return const_cast<LabelTrackView*>(this)->FindLabelTrack();
182}
183
184std::vector<UIHandlePtr> LabelTrackView::DetailedHitTest
185(const TrackPanelMouseState &st,
186 const AudacityProject *WXUNUSED(pProject), int, bool)
187{
188 UIHandlePtr result;
189 std::vector<UIHandlePtr> results;
190 const wxMouseState &state = st.state;
191
192 const auto pTrack = FindLabelTrack();
194 mGlyphHandle, state, pTrack, st.rect);
195 if (result)
196 results.push_back(result);
197
199 mTextHandle, state, pTrack);
200 if (result)
201 results.push_back(result);
202
203 return results;
204}
205
206// static member variables.
208
210
220
222
224{
227 mTextEditIndex = -1;
228 mNavigationIndex = -1;
229}
230
232{
233 return {
236 };
237}
238
240{
245}
246
247wxFont LabelTrackView::GetFont(const wxString &faceName, int size)
248{
249 wxFontEncoding encoding;
250 if (faceName.empty())
251 encoding = wxFONTENCODING_DEFAULT;
252 else
253 encoding = wxFONTENCODING_SYSTEM;
254
255 auto fontInfo = size == 0 ? wxFontInfo() : wxFontInfo(size);
256 fontInfo
257 .Encoding(encoding)
258 .FaceName(faceName);
259
260 return wxFont(fontInfo);
261}
262
264{
265 mFontHeight = -1;
266 wxString facename = gPrefs->Read(wxT("/GUI/LabelFontFacename"), wxT(""));
267 int size = gPrefs->Read(wxT("/GUI/LabelFontSize"), DefaultFontSize);
268 msFont = GetFont(facename, size);
269}
270
284void LabelTrackView::ComputeTextPosition(const wxRect & r, int index) const
285{
286 const auto pTrack = FindLabelTrack();
287 const auto &mLabels = pTrack->GetLabels();
288
289 const auto &labelStruct = mLabels[index];
290
291 // xExtra is extra space
292 // between the text and the endpoints.
293 const int xExtra=mIconWidth;
294 int x = labelStruct.x; // left endpoint
295 int x1 = labelStruct.x1; // right endpoint.
296 int width = labelStruct.width;
297
298 int xText; // This is where the text will end up.
299
300 // Will the text all fit at this zoom?
301 bool bTooWideForScreen = width > (r.width-2*xExtra);
302// bool bSimpleCentering = !bTooWideForScreen;
303 bool bSimpleCentering = false;
304
305 //TODO (possibly):
306 // Add configurable options as to when to use simple
307 // and when complex centering.
308 //
309 // Simple centering does its best to keep the text
310 // centered between the label limits.
311 //
312 // Complex centering positions the text proportionally
313 // to how far we are through the label.
314 //
315 // If we add preferences for this, we want to be able to
316 // choose separately whether:
317 // a) Wide text labels centered simple/complex.
318 // b) Other text labels centered simple/complex.
319 //
320
321 if( bSimpleCentering )
322 {
323 // Center text between the two end points.
324 xText = (x+x1-width)/2;
325 }
326 else
327 {
328 // Calculate xText position to make text line
329 // scroll sideways evenly as r moves right.
330
331 // xText is a linear function of r.x.
332 // These variables are used to compute that function.
333 int rx0,rx1,xText0,xText1;
334
335 // Since we will be using a linear function,
336 // we should blend smoothly between left and right
337 // aligned text as r, the 'viewport' moves.
338 if( bTooWideForScreen )
339 {
340 rx0=x; // when viewport at label start.
341 xText0=x+xExtra; // text aligned left.
342 rx1=x1-r.width; // when viewport end at label end
343 xText1=x1-(width+xExtra); // text aligned right.
344 }
345 else
346 {
347 // when label start + width + extra spacing at viewport end..
348 rx0=x-r.width+width+2*xExtra;
349 // ..text aligned left.
350 xText0=x+xExtra;
351 // when viewport start + width + extra spacing at label end..
352 rx1=x1-(width+2*xExtra);
353 // ..text aligned right.
354 xText1=x1-(width+xExtra);
355 }
356
357 if( rx1 > rx0 ) // Avoid divide by zero case.
358 {
359 // Compute the blend between left and right aligned.
360
361 // Don't use:
362 //
363 // xText = xText0 + ((xText1-xText0)*(r.x-rx0))/(rx1-rx0);
364 //
365 // The problem with the above is that it integer-oveflows at
366 // high zoom.
367
368 // Instead use:
369 xText = xText0 + (int)((xText1-xText0)*(((float)(r.x-rx0))/(rx1-rx0)));
370 }
371 else
372 {
373 // Avoid divide by zero by reverting to
374 // simple centering.
375 //
376 // We could also fall into this case if x and x1
377 // are swapped, in which case we'll end up
378 // left aligned anyway because code later on
379 // will catch that.
380 xText = (x+x1-width)/2;
381 }
382 }
383
384 // Is the text now appearing partly outside r?
385 bool bOffLeft = xText < r.x+xExtra;
386 bool bOffRight = xText > r.x+r.width-width-xExtra;
387
388 // IF any part of the text is offscreen
389 // THEN we may bring it back.
390 if( bOffLeft == bOffRight )
391 {
392 //IF both sides on screen, THEN nothing to do.
393 //IF both sides off screen THEN don't do
394 //anything about it.
395 //(because if we did, you'd never get to read
396 //all the text by scrolling).
397 }
398 else if( bOffLeft != bTooWideForScreen)
399 {
400 // IF we're off on the left, OR we're
401 // too wide for the screen and off on the right
402 // (only) THEN align left.
403 xText = r.x+xExtra;
404 }
405 else
406 {
407 // We're off on the right, OR we're
408 // too wide and off on the left (only)
409 // SO align right.
410 xText =r.x+r.width-width-xExtra;
411 }
412
413 // But if we've taken the text out from its endpoints
414 // we must move it back so that it's between the endpoints.
415
416 // We test the left end point last because the
417 // text might not even fit between the endpoints (at this
418 // zoom factor), and in that case we'd like to position
419 // the text at the left end point.
420 if( xText > (x1-width-xExtra))
421 xText=(x1-width-xExtra);
422 if( xText < x+xExtra )
423 xText=x+xExtra;
424
425 labelStruct.xText = xText;
426}
427
431void LabelTrackView::ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) const
432{
433 int xUsed[MAX_NUM_ROWS];
434
435 int iRow;
436 // Rows are the 'same' height as icons or as the text,
437 // whichever is taller.
438 const int yRowHeight = wxMax(mTextHeight,mIconHeight)+3;// pixels.
439 // Extra space at end of rows.
440 // We allow space for one half icon at the start and two
441 // half icon widths for extra x for the text frame.
442 // [we don't allow half a width space for the end icon since it is
443 // allowed to be obscured by the text].
444 const int xExtra= (3 * mIconWidth)/2;
445
446 bool bAvoidName = false;
447 const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS);
448 if( nRows > 2 )
449 bAvoidName = gPrefs->ReadBool(wxT("/GUI/ShowTrackNameInWaveform"), false);
450 // Initially none of the rows have been used.
451 // So set a value that is less than any valid value.
452 {
453 // Bug 502: With dragging left of zeros, labels can be in
454 // negative space. So set least possible value as starting point.
455 const int xStart = INT_MIN;
456 for (auto &x : xUsed)
457 x = xStart;
458 }
459 int nRowsUsed=0;
460
461 const auto pTrack = FindLabelTrack();
462 const auto &mLabels = pTrack->GetLabels();
463
464 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
465 const int x = zoomInfo.TimeToPosition(labelStruct.getT0(), r.x);
466 const int x1 = zoomInfo.TimeToPosition(labelStruct.getT1(), r.x);
467 int y = r.y;
468
469 labelStruct.x=x;
470 labelStruct.x1=x1;
471 labelStruct.y=-1;// -ve indicates nothing doing.
472 iRow=0;
473 // Our first preference is a row that ends where we start.
474 // (This is to encourage merging of adjacent label boundaries).
475 while( (iRow<nRowsUsed) && (xUsed[iRow] != x ))
476 iRow++;
477
478 // IF we didn't find one THEN
479 // find any row that can take a span starting at x.
480 if( iRow >= nRowsUsed )
481 {
482 iRow=0;
483 while( (iRow<nRows) && (xUsed[iRow] > x ))
484 iRow++;
485 }
486 // IF we found such a row THEN record a valid position.
487 if( iRow<nRows )
488 {
489 // Logic to ameliorate case where first label is under the
490 // (on track) track name. For later labels it does not matter
491 // as we can scroll left or right and/or zoom.
492 // A possible alternative idea would be to (instead) increase the
493 // translucency of the track name, when the mouse is inside it.
494 if( (i==0 ) && (iRow==0) && bAvoidName ){
495 // reserve some space in first row.
496 // reserve max of 200px or t1, or text box right edge.
497 const int x2 = zoomInfo.TimeToPosition(0.0, r.x) + 200;
498 xUsed[iRow]=x+labelStruct.width+xExtra;
499 if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
500 if( xUsed[iRow] < x2 ) xUsed[iRow]=x2;
501 iRow=1;
502 }
503
504 // Possibly update the number of rows actually used.
505 if( iRow >= nRowsUsed )
506 nRowsUsed=iRow+1;
507 // Record the position for this label
508 y= r.y + iRow * yRowHeight +(yRowHeight/2)+1;
509 labelStruct.y=y;
510 // On this row we have used up to max of end marker and width.
511 // Plus also allow space to show the start icon and
512 // some space for the text frame.
513 xUsed[iRow]=x+labelStruct.width+xExtra;
514 if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
515 ComputeTextPosition( r, i );
516 }
517 }}
518}
519
525 wxDC & dc, const LabelStruct &ls, const wxRect & r)
526{
527 auto &x = ls.x;
528 auto &x1 = ls.x1;
529 auto &y = ls.y;
530
531 // Bug 2388 - Point label and range label can appear identical
532 // If the start and end times are not actually the same, but they
533 // would appear so when drawn as lines at current zoom, be sure to draw
534 // two lines - i.e. displace the second line slightly.
535 if (ls.getT0() != ls.getT1()) {
536 if (x == x1)
537 x1++;
538 }
539
540 // How far out from the centre line should the vertical lines
541 // start, i.e. what is the y position of the icon?
542 // We adjust this so that the line encroaches on the icon
543 // slightly (there is white space in the design).
544 const int yIconStart = y - (mIconHeight /2)+1+(mTextHeight+3)/2;
545 const int yIconEnd = yIconStart + mIconHeight-2;
546
547 // If y is positive then it is the center line for the
548 // Label.
549 if( y >= 0 )
550 {
551 if((x >= r.x) && (x <= (r.x+r.width)))
552 {
553 // Draw line above and below left dragging widget.
554 AColor::Line(dc, x, r.y, x, yIconStart - 1);
555 AColor::Line(dc, x, yIconEnd, x, r.y + r.height);
556 }
557 if((x1 >= r.x) && (x1 <= (r.x+r.width)))
558 {
559 // Draw line above and below right dragging widget.
560 AColor::Line(dc, x1, r.y, x1, yIconStart - 1);
561 AColor::Line(dc, x1, yIconEnd, x1, r.y + r.height);
562 }
563 }
564 else
565 {
566 // Draw the line, even though the widget is off screen
567 AColor::Line(dc, x, r.y, x, r.y + r.height);
568 AColor::Line(dc, x1, r.y, x1, r.y + r.height);
569 }
570}
571
576 wxDC & dc, const LabelStruct &ls, const wxRect & r,
577 int GlyphLeft, int GlyphRight)
578{
579 auto &y = ls.y;
580
581 const int xHalfWidth=mIconWidth/2;
582 const int yStart=y-mIconHeight/2+(mTextHeight+3)/2;
583
584 // If y == -1, nothing to draw
585 if( y == -1 )
586 return;
587
588 auto &x = ls.x;
589 auto &x1 = ls.x1;
590
591 if((x >= r.x) && (x <= (r.x+r.width)))
592 dc.DrawBitmap(GetGlyph(GlyphLeft), x-xHalfWidth,yStart, true);
593 // The extra test commented out here would suppress right hand markers
594 // when they overlap the left hand marker (e.g. zoomed out) or to the left.
595 if((x1 >= r.x) && (x1 <= (r.x+r.width)) /*&& (x1>x+mIconWidth)*/)
596 dc.DrawBitmap(GetGlyph(GlyphRight), x1-xHalfWidth,yStart, true);
597}
598
600{
601 return mTextHeight + TextFramePadding * 2;
602}
603
610void LabelTrackView::DrawText(wxDC & dc, const LabelStruct &ls, const wxRect & r)
611{
612 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
613 //If y is positive then it is the center line for the
614 //text we are about to draw.
615 //if it isn't, nothing to draw.
616
617 auto &y = ls.y;
618 if( y == -1 )
619 return;
620
621 // Draw frame for the text...
622 // We draw it half an icon width left of the text itself.
623 {
624 auto &xText = ls.xText;
625 const int xStart=wxMax(r.x,xText-mIconWidth/2);
626 const int xEnd=wxMin(r.x+r.width,xText+ls.width+mIconWidth/2);
627 const int xWidth = xEnd-xStart;
628
629 if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
630 {
631 // Now draw the text itself.
632 auto pos = y - LabelBarHeight - yFrameHeight + TextFrameYOffset +
633 (yFrameHeight - mFontHeight) / 2 + dc.GetFontMetrics().ascent;
634 dc.DrawText(ls.title, xText, pos);
635 }
636 }
637
638}
639
641 wxDC & dc, const LabelStruct &ls, const wxRect & r)
642{
643 // In drawing the bar and the frame, we compute the clipping
644 // to the viewport ourselves. Under Win98 the GDI does its
645 // calculations in 16 bit arithmetic, and so gets it completely
646 // wrong at higher zooms where the bar can easily be
647 // more than 65536 pixels wide.
648
649 // Draw bar for label extent...
650 // We don't quite draw from x to x1 because we allow
651 // half an icon width at each end.
652 const auto textFrameHeight = GetTextFrameHeight();
653 auto& xText = ls.xText;
654 const int xStart = wxMax(r.x, xText - mIconWidth / 2);
655 const int xEnd = wxMin(r.x + r.width, xText + ls.width + mIconWidth / 2);
656 const int xWidth = xEnd - xStart;
657
658 if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
659 {
660 wxRect frame(
661 xStart, ls.y - (textFrameHeight + LabelBarHeight) / 2 + TextFrameYOffset,
662 xWidth, textFrameHeight);
663 dc.DrawRectangle(frame);
664 }
665}
666
667void LabelTrackView::DrawBar(wxDC& dc, const LabelStruct& ls, const wxRect& r)
668{
669 //If y is positive then it is the center line for the
670 //text we are about to draw.
671 const int xBarShorten = mIconWidth + 4;
672 auto& y = ls.y;
673 if (y == -1)
674 return;
675
676 auto& x = ls.x;
677 auto& x1 = ls.x1;
678 const int xStart = wxMax(r.x, x + xBarShorten / 2);
679 const int xEnd = wxMin(r.x + r.width, x1 - xBarShorten / 2);
680 const int xWidth = xEnd - xStart;
681
682 if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
683 {
684 wxRect bar(xStart, y - (LabelBarHeight - GetTextFrameHeight()) / 2,
685 xWidth, LabelBarHeight);
686 if (x1 > x + xBarShorten)
687 dc.DrawRectangle(bar);
688 }
689}
690
693 int xPos1, int xPos2, int charHeight)
694{
695 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
696
697 dc.SetPen(*wxTRANSPARENT_PEN);
698 wxBrush curBrush = dc.GetBrush();
699 curBrush.SetColour(wxString(wxT("BLUE")));
700 auto top = ls.y + TextFrameYOffset - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - charHeight) / 2;
701 if (xPos1 < xPos2)
702 dc.DrawRectangle(xPos1-1, top, xPos2-xPos1+1, charHeight);
703 else
704 dc.DrawRectangle(xPos2-1, top, xPos1-xPos2+1, charHeight);
705}
706
707namespace {
708void getXPos( const LabelStruct &ls, wxDC & dc, int * xPos1, int cursorPos)
709{
710 *xPos1 = ls.xText;
711 if( cursorPos > 0)
712 {
713 int partWidth;
714 // Calculate the width of the substring and add it to Xpos
715 dc.GetTextExtent(ls.title.Left(cursorPos), &partWidth, NULL);
716 *xPos1 += partWidth;
717 }
718}
719}
720
721bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const
722{
723 if (IsValidIndex(mTextEditIndex, project)) {
724 wxMemoryDC dc;
725
726 if (msFont.Ok()) {
727 dc.SetFont(msFont);
728 }
729
730 const auto pTrack = FindLabelTrack();
731 const auto &mLabels = pTrack->GetLabels();
732
733 getXPos(mLabels[mTextEditIndex], dc, x, mCurrentCursorPos);
734 *x += mIconWidth / 2;
735 return true;
736 }
737
738 return false;
739}
740
741void LabelTrackView::CalcHighlightXs(int *x1, int *x2) const
742{
743 wxMemoryDC dc;
744
745 if (msFont.Ok()) {
746 dc.SetFont(msFont);
747 }
748
749 int pos1 = mInitialCursorPos, pos2 = mCurrentCursorPos;
750 if (pos1 > pos2)
751 std::swap(pos1, pos2);
752
753 const auto pTrack = FindLabelTrack();
754 const auto &mLabels = pTrack->GetLabels();
755 const auto &labelStruct = mLabels[mTextEditIndex];
756
757 // find the left X pos of highlighted area
758 getXPos(labelStruct, dc, x1, pos1);
759 // find the right X pos of highlighted area
760 getXPos(labelStruct, dc, x2, pos2);
761}
762
763#include "LabelGlyphHandle.h"
764namespace {
766 {
767 if (! pPanel )
768 return nullptr;
769
770 // Fetch the highlighting state
771 auto target = pPanel->Target();
772 if (target) {
773 auto handle = dynamic_cast<LabelGlyphHandle*>( target.get() );
774 if (handle)
775 return &*handle->mpHit;
776 }
777 return nullptr;
778 }
779}
780
781#include "../../../TrackPanelDrawingContext.h"
782#include "LabelTextHandle.h"
783
788( TrackPanelDrawingContext &context, const wxRect & r ) const
789{
790 auto &dc = context.dc;
791 const auto artist = TrackArtist::Get( context );
792 const auto &zoomInfo = *artist->pZoomInfo;
793
794 auto pHit = findHit( artist->parent );
795
796 if(msFont.Ok())
797 dc.SetFont(msFont);
798
799 if (mFontHeight == -1)
801
802 const auto pTrack = std::static_pointer_cast< const LabelTrack >(
803 FindTrack()->SubstitutePendingChangedTrack());
804 const auto &mLabels = pTrack->GetLabels();
805
806 TrackArt::DrawBackgroundWithSelection( context, r, pTrack.get(),
809
810 wxCoord textWidth, textHeight;
811
812 // Get the text widths.
813 // TODO: Make more efficient by only re-computing when a
814 // text label title changes.
815 for (const auto &labelStruct : mLabels) {
816 dc.GetTextExtent(labelStruct.title, &textWidth, &textHeight);
817 labelStruct.width = textWidth;
818 }
819
820 // TODO: And this only needs to be done once, but we
821 // do need the dc to do it.
822 // We need to set mTextHeight to something sensible,
823 // guarding against the case where there are no
824 // labels or all are empty strings, which for example
825 // happens with a NEW label track.
826 mTextHeight = dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent;
827 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
828
829 ComputeLayout( r, zoomInfo );
830 dc.SetTextForeground(theTheme.Colour( clrLabelTrackText));
831 dc.SetBackgroundMode(wxTRANSPARENT);
832 dc.SetBrush(AColor::labelTextNormalBrush);
833 dc.SetPen(AColor::labelSurroundPen);
834 int GlyphLeft;
835 int GlyphRight;
836 // Now we draw the various items in this order,
837 // so that the correct things overpaint each other.
838
839 // Draw vertical lines that show where the end positions are.
840 for (const auto &labelStruct : mLabels)
841 DrawLines( dc, labelStruct, r );
842
843 // Draw the end glyphs.
844 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
845 GlyphLeft=0;
846 GlyphRight=1;
847 if( pHit && i == pHit->mMouseOverLabelLeft )
848 GlyphLeft = (pHit->mEdge & 4) ? 6:9;
849 if( pHit && i == pHit->mMouseOverLabelRight )
850 GlyphRight = (pHit->mEdge & 4) ? 7:4;
851 DrawGlyphs( dc, labelStruct, r, GlyphLeft, GlyphRight );
852 }}
853
854 auto &project = *artist->parent->GetProject();
855
856 // Draw the label boxes.
857 {
858#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
859 bool highlightTrack = false;
860 auto target = dynamic_cast<LabelTextHandle*>(context.target.get());
861 highlightTrack = target && target->GetTrack().get() == this;
862#endif
863 int i = -1; for (const auto &labelStruct : mLabels) { ++i;
864 bool highlight = false;
865#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
866 highlight = highlightTrack && target->GetLabelNum() == i;
867#endif
868
869 dc.SetBrush(mNavigationIndex == i || (pHit && pHit->mMouseOverLabel == i)
871 DrawBar(dc, labelStruct, r);
872
873 bool selected = mTextEditIndex == i;
874
875 if (selected)
876 dc.SetBrush(AColor::labelTextEditBrush);
877 else if (highlight)
878 dc.SetBrush(AColor::uglyBrush);
879 else
880 dc.SetBrush(AColor::labelTextNormalBrush);
881 DrawTextBox(dc, labelStruct, r);
882
883 dc.SetBrush(AColor::labelTextNormalBrush);
884 }
885 }
886
887 // Draw highlights
889 {
890 int xpos1, xpos2;
891 CalcHighlightXs(&xpos1, &xpos2);
892 DrawHighlight(dc, mLabels[mTextEditIndex],
893 xpos1, xpos2, dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent);
894 }
895
896 // Draw the text and the label boxes.
897 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
898 if(mTextEditIndex == i )
899 dc.SetBrush(AColor::labelTextEditBrush);
900 DrawText( dc, labelStruct, r );
901 if(mTextEditIndex == i )
902 dc.SetBrush(AColor::labelTextNormalBrush);
903 }}
904
905 // Draw the cursor, if there is one.
907 {
908 const auto &labelStruct = mLabels[mTextEditIndex];
909 int xPos = labelStruct.xText;
910
911 if( mCurrentCursorPos > 0)
912 {
913 // Calculate the width of the substring and add it to Xpos
914 int partWidth;
915 dc.GetTextExtent(labelStruct.title.Left(mCurrentCursorPos), &partWidth, NULL);
916 xPos += partWidth;
917 }
918
919 wxPen currentPen = dc.GetPen();
920 const int CursorWidth=2;
921 currentPen.SetWidth(CursorWidth);
922 const auto top = labelStruct.y - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - mFontHeight) / 2 + TextFrameYOffset;
923 AColor::Line(dc,
924 xPos-1, top,
925 xPos-1, top + mFontHeight);
926 currentPen.SetWidth(1);
927 }
928}
929
932 const wxRect &rect, unsigned iPass )
933{
934 if ( iPass == TrackArtist::PassTracks )
935 Draw( context, rect );
936 CommonTrackView::Draw( context, rect, iPass );
937}
938
941int LabelTrackView::FindCursorPosition(int labelIndex, wxCoord xPos)
942{
943 int result = -1;
944 wxMemoryDC dc;
945 if(msFont.Ok())
946 dc.SetFont(msFont);
947
948 // A bool indicator to see if set the cursor position or not
949 bool finished = false;
950 int charIndex = 1;
951 int partWidth;
952 int oneWidth;
953 double bound;
954 wxString subString;
955
956 const auto pTrack = FindLabelTrack();
957 const auto &mLabels = pTrack->GetLabels();
958 const auto &labelStruct = mLabels[labelIndex];
959 const auto &title = labelStruct.title;
960 const int length = title.length();
961 while (!finished && (charIndex < length + 1))
962 {
963 int unichar = (int)title.at( charIndex-1 );
964 if( (0xDC00 <= unichar) && (unichar <= 0xDFFF)){
965 charIndex++;
966 continue;
967 }
968 subString = title.Left(charIndex);
969 // Get the width of substring
970 dc.GetTextExtent(subString, &partWidth, NULL);
971
972 // Get the width of the last character
973 dc.GetTextExtent(subString.Right(1), &oneWidth, NULL);
974 bound = labelStruct.xText + partWidth - oneWidth * 0.5;
975
976 if (xPos <= bound)
977 {
978 // Found
979 result = charIndex - 1;
980 finished = true;
981 }
982 else
983 {
984 // Advance
985 charIndex++;
986 }
987 }
988 if (!finished)
989 // Cursor should be in the last position
990 result = length;
991
992 return result;
993}
994
996{
997 mCurrentCursorPos = pos;
998}
999void LabelTrackView::SetTextSelection(int labelIndex, int start, int end)
1000{
1001 mTextEditIndex = labelIndex;
1002 mInitialCursorPos = start;
1004}
1006{
1007 if (IsValidIndex(mTextEditIndex, project))
1008 return mTextEditIndex;
1009 return -1;
1010}
1012{
1013 mTextEditIndex = -1;
1016}
1018{
1019 mNavigationIndex = index;
1020}
1022{
1023 if (IsValidIndex(mNavigationIndex, project))
1024 return mNavigationIndex;
1025 return -1;
1026}
1027
1029{
1030 int charDescent;
1031 int charLeading;
1032
1033 // Calculate the width of the substring and add it to Xpos
1034 dc.GetTextExtent(wxT("(Test String)|[yp]"), NULL, &mFontHeight, &charDescent, &charLeading);
1035
1036 // The cursor will have height charHeight. We don't include the descender as
1037 // part of the height because for phonetic fonts this leads to cursors which are
1038 // too tall. We don't include leading either - it is usually 0.
1039 // To make up for ignoring the descender height, we add one pixel above and below
1040 // using CursorExtraHeight so that the cursor is just a little taller than the
1041 // body of the characters.
1042 const int CursorExtraHeight=2;
1043 mFontHeight += CursorExtraHeight - (charLeading+charDescent);
1044}
1045
1046bool LabelTrackView::IsValidIndex(const Index& index, AudacityProject& project) const
1047{
1048 if (index == -1)
1049 return false;
1050 // may make delayed update of mutable mSelIndex after track selection change
1051 auto track = FindLabelTrack();
1052 if (track->GetSelected() || (TrackFocus::Get(project).Get() == track.get()))
1053 return index >= 0 && index < static_cast<int>(track->GetLabels().size());
1054 return false;
1055}
1056
1058{
1060}
1061
1065{
1066 if (!IsTextSelected( project ))
1067 return false;
1068
1069 const auto pTrack = FindLabelTrack();
1070 const auto &mLabels = pTrack->GetLabels();
1071
1072 wxString left, right;
1073 auto labelStruct = mLabels[mTextEditIndex];
1074 auto &text = labelStruct.title;
1075
1076 if (!mTextEditIndex.IsModified()) {
1077 mUndoLabel = text;
1078 }
1079
1080 int init = mInitialCursorPos;
1081 int cur = mCurrentCursorPos;
1082 if (init > cur)
1083 std::swap(init, cur);
1084
1085 // data for cutting
1086 wxString data = text.Mid(init, cur - init);
1087
1088 // get left-remaining text
1089 if (init > 0)
1090 left = text.Left(init);
1091
1092 // get right-remaining text
1093 if (cur < (int)text.length())
1094 right = text.Mid(cur);
1095
1096 // set title to the combination of the two remainders
1097 text = left + right;
1098
1099 pTrack->SetLabel( mTextEditIndex, labelStruct );
1100
1101 // copy data onto clipboard
1102 if (wxTheClipboard->Open()) {
1103 // Clipboard owns the data you give it
1104 wxTheClipboard->SetData(safenew wxTextDataObject(data));
1105 wxTheClipboard->Close();
1106 }
1107
1108 // set cursor positions
1109 mInitialCursorPos = mCurrentCursorPos = left.length();
1110
1112 return true;
1113}
1114
1118{
1119 if (!IsTextSelected(project))
1120 return false;
1121
1122 const auto pTrack = FindLabelTrack();
1123 const auto &mLabels = pTrack->GetLabels();
1124
1125 const auto &labelStruct = mLabels[mTextEditIndex];
1126
1127 int init = mInitialCursorPos;
1128 int cur = mCurrentCursorPos;
1129 if (init > cur)
1130 std::swap(init, cur);
1131
1132 if (init == cur)
1133 return false;
1134
1135 // data for copying
1136 wxString data = labelStruct.title.Mid(init, cur-init);
1137
1138 // copy the data on clipboard
1139 if (wxTheClipboard->Open()) {
1140 // Clipboard owns the data you give it
1141 wxTheClipboard->SetData(safenew wxTextDataObject(data));
1142 wxTheClipboard->Close();
1143 }
1144
1145 return true;
1146}
1147
1148// PRL: should this set other fields of the label selection?
1152 AudacityProject &project, double sel0, double sel1 )
1153{
1154 const auto pTrack = FindLabelTrack();
1155
1156 if (!IsValidIndex(mTextEditIndex, project))
1158
1159 wxString text, left, right;
1160
1161 // if text data is available
1162 if (IsTextClipSupported()) {
1163 if (wxTheClipboard->Open()) {
1164 wxTextDataObject data;
1165 wxTheClipboard->GetData(data);
1166 wxTheClipboard->Close();
1167 text = data.GetText();
1168 }
1169
1170 if (!mTextEditIndex.IsModified()) {
1171 mUndoLabel = text;
1172 }
1173
1174 // Convert control characters to blanks
1175 for (int i = 0; i < (int)text.length(); i++) {
1176 if (wxIscntrl(text[i])) {
1177 text[i] = wxT(' ');
1178 }
1179 }
1180 }
1181
1182 const auto &mLabels = pTrack->GetLabels();
1183 auto labelStruct = mLabels[mTextEditIndex];
1184 auto &title = labelStruct.title;
1185 int cur = mCurrentCursorPos, init = mInitialCursorPos;
1186 if (init > cur)
1187 std::swap(init, cur);
1188 left = title.Left(init);
1189 if (cur < (int)title.length())
1190 right = title.Mid(cur);
1191
1192 title = left + text + right;
1193
1194 pTrack->SetLabel(mTextEditIndex, labelStruct );
1195
1196 mInitialCursorPos = mCurrentCursorPos = left.length() + text.length();
1197
1199 return true;
1200}
1201
1203{
1204 if (!IsValidIndex(mTextEditIndex, project))
1205 return false;
1206
1207 const auto pTrack = FindLabelTrack();
1208
1209 const auto& mLabels = pTrack->GetLabels();
1210 auto labelStruct = mLabels[mTextEditIndex];
1211 auto& title = labelStruct.title;
1212
1214 mCurrentCursorPos = title.Length();
1215
1216 return true;
1217}
1218
1221{
1222 return wxTheClipboard->IsSupported(wxDF_UNICODETEXT);
1223}
1224
1229 const LabelTrack &track, LabelTrackHit &hit, int x, int y)
1230{
1231 //Determine the NEW selection.
1232 int result=0;
1233 const int d1=10; //distance in pixels, used for have we hit drag handle.
1234 const int d2=5; //distance in pixels, used for have we hit drag handle center.
1235
1236 //If not over a label, reset it
1237 hit.mMouseOverLabelLeft = -1;
1238 hit.mMouseOverLabelRight = -1;
1239 hit.mMouseOverLabel = -1;
1240 hit.mEdge = 0;
1241
1242 const auto pTrack = &track;
1243 const auto &mLabels = pTrack->GetLabels();
1244 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
1245 // give text box better priority for selecting
1246 // reset selection state
1247 if (OverTextBox(&labelStruct, x, y))
1248 {
1249 result = 0;
1250 hit.mMouseOverLabel = -1;
1251 hit.mMouseOverLabelLeft = -1;
1252 hit.mMouseOverLabelRight = -1;
1253 break;
1254 }
1255
1256 //over left or right selection bound
1257 //Check right bound first, since it is drawn after left bound,
1258 //so give it precedence for matching/highlighting.
1259 if( abs(labelStruct.y - (y - (mTextHeight+3)/2)) < d1 &&
1260 abs(labelStruct.x1 - d2 -x) < d1)
1261 {
1262 hit.mMouseOverLabelRight = i;
1263 if(abs(labelStruct.x1 - x) < d2 )
1264 {
1265 result |= 4;
1266 // If left and right co-incident at this resolution, then we drag both.
1267 // We were more stringent about co-incidence here in the past.
1268 if( abs(labelStruct.x1-labelStruct.x) < 5.0 )
1269 {
1270 result |=1;
1271 hit.mMouseOverLabelLeft = i;
1272 }
1273 }
1274 result |= 2;
1275 }
1276 // Use else-if here rather than else to avoid detecting left and right
1277 // of the same label.
1278 else if( abs(labelStruct.y - (y - (mTextHeight+3)/2)) < d1 &&
1279 abs(labelStruct.x + d2 - x) < d1 )
1280 {
1281 hit.mMouseOverLabelLeft = i;
1282 if(abs(labelStruct.x - x) < d2 )
1283 result |= 4;
1284 result |= 1;
1285 }
1286 else if (x >= labelStruct.x && x <= labelStruct.x1 &&
1287 abs(y - (labelStruct.y + mTextHeight / 2)) < d1)
1288 {
1289 hit.mMouseOverLabel = i;
1290 result = 3;
1291 }
1292 }}
1293 hit.mEdge = result;
1294}
1295
1296int LabelTrackView::OverATextBox( const LabelTrack &track, int xx, int yy )
1297{
1298 const auto pTrack = &track;
1299 const auto &mLabels = pTrack->GetLabels();
1300 for (int nn = (int)mLabels.size(); nn--;) {
1301 const auto &labelStruct = mLabels[nn];
1302 if ( OverTextBox( &labelStruct, xx, yy ) )
1303 return nn;
1304 }
1305
1306 return -1;
1307}
1308
1309// return true if the mouse is over text box, false otherwise
1310bool LabelTrackView::OverTextBox(const LabelStruct *pLabel, int x, int y)
1311{
1312 if( (pLabel->xText-(mIconWidth/2) < x) &&
1313 (x<pLabel->xText+pLabel->width+(mIconWidth/2)) &&
1314 (abs(pLabel->y-y)<mIconHeight/2))
1315 {
1316 return true;
1317 }
1318 return false;
1319}
1320
1322static bool IsGoodLabelFirstKey(const wxKeyEvent & evt)
1323{
1324 int keyCode = evt.GetKeyCode();
1325 return (keyCode < WXK_START
1326 && keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) ||
1327 (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
1328 (keyCode >= WXK_NUMPAD_EQUAL && keyCode <= WXK_NUMPAD_DIVIDE) ||
1329#if defined(__WXMAC__)
1330 (keyCode > WXK_RAW_CONTROL) ||
1331#endif
1332 (keyCode > WXK_WINDOWS_MENU);
1333}
1334
1336static bool IsGoodLabelEditKey(const wxKeyEvent & evt)
1337{
1338 int keyCode = evt.GetKeyCode();
1339
1340 // Accept everything outside of WXK_START through WXK_COMMAND, plus the keys
1341 // within that range that are usually printable, plus the ones we use for
1342 // keyboard navigation.
1343 return keyCode < WXK_START ||
1344 (keyCode >= WXK_END && keyCode < WXK_UP) ||
1345 (keyCode == WXK_RIGHT) ||
1346 (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
1347 (keyCode >= WXK_NUMPAD_SPACE && keyCode <= WXK_NUMPAD_ENTER) ||
1348 (keyCode >= WXK_NUMPAD_HOME && keyCode <= WXK_NUMPAD_END) ||
1349 (keyCode >= WXK_NUMPAD_DELETE && keyCode <= WXK_NUMPAD_DIVIDE) ||
1350#if defined(__WXMAC__)
1351 (keyCode > WXK_RAW_CONTROL) ||
1352#endif
1353 (keyCode > WXK_WINDOWS_MENU);
1354}
1355
1356// Check for keys that we will process
1358 AudacityProject &project, wxKeyEvent & event )
1359{
1360 int mods = event.GetModifiers();
1361 auto code = event.GetKeyCode();
1362 const auto pTrack = FindLabelTrack();
1363 const auto& mLabels = pTrack->GetLabels();
1364
1365 // Allow hardcoded Ctrl+F2 for renaming the selected label,
1366 // if we have any labels
1367 if (code == WXK_F2 && mods == wxMOD_CONTROL && !mLabels.empty()) {
1368 return true;
1369 }
1370
1371 // Check for modifiers and only allow shift
1372 if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1373 return false;
1374 }
1375
1376 // Always capture the navigation keys, if we have any labels
1377 if ((code == WXK_TAB || code == WXK_NUMPAD_TAB) &&
1378 !mLabels.empty())
1379 return true;
1380
1381 if (IsValidIndex(mTextEditIndex, project)) {
1382 if (IsGoodLabelEditKey(event)) {
1383 return true;
1384 }
1385 }
1386 else {
1387 bool typeToCreateLabel;
1388 gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, false);
1389 if (IsGoodLabelFirstKey(event) && typeToCreateLabel) {
1390
1391
1392// The commented out code can prevent label creation, causing bug 1551
1393// We should only be in DoCaptureKey IF this label track has focus,
1394// and in that case creating a Label is the expected/intended thing.
1395#if 0
1396 // If we're playing, don't capture if the selection is the same as the
1397 // playback region (this helps prevent label track creation from
1398 // stealing unmodified kbd. shortcuts)
1399 auto gAudioIO = AudioIOBase::Get();
1400 if (pProj->GetAudioIOToken() > 0 &&
1401 gAudioIO->IsStreamActive(pProj->GetAudioIOToken()))
1402 {
1403 double t0, t1;
1404 pProj->GetPlayRegion(&t0, &t1);
1405 if (pProj->mViewInfo.selectedRegion.t0() == t0 &&
1406 pProj->mViewInfo.selectedRegion.t1() == t1) {
1407 return false;
1408 }
1409 }
1410#endif
1411
1412 // If there's a label there already don't capture
1413 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1414 if( GetLabelIndex(selectedRegion.t0(),
1415 selectedRegion.t1()) != wxNOT_FOUND ) {
1416 return false;
1417 }
1418
1419 return true;
1420 }
1421 }
1422
1423 return false;
1424}
1425
1427 wxKeyEvent & event, ViewInfo &, wxWindow *, AudacityProject *project )
1428{
1429 event.Skip(!DoCaptureKey( *project, event ));
1431}
1432
1434 wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *WXUNUSED(pParent),
1435 AudacityProject *project)
1436{
1437 double bkpSel0 = viewInfo.selectedRegion.t0(),
1438 bkpSel1 = viewInfo.selectedRegion.t1();
1439
1441 const auto pTrack = FindLabelTrack();
1442 const auto &mLabels = pTrack->GetLabels();
1443 auto labelStruct = mLabels[mTextEditIndex];
1444 auto &title = labelStruct.title;
1445 mUndoLabel = title;
1446 }
1447
1448 // Pass keystroke to labeltrack's handler and add to history if any
1449 // updates were done
1450 if (DoKeyDown( *project, viewInfo.selectedRegion, event )) {
1451 ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
1452 XO("Label Edit"),
1454
1456 }
1457
1458 if (!mTextEditIndex.IsModified()) {
1459 mUndoLabel.clear();
1460 }
1461
1462 // Make sure caret is in view
1463 int x;
1464 if (CalcCursorX( *project, &x ))
1465 ProjectWindow::Get( *project ).ScrollIntoView(x);
1466
1467 // If selection modified, refresh
1468 // Otherwise, refresh track display if the keystroke was handled
1469 if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1470 bkpSel1 != viewInfo.selectedRegion.t1())
1472 else if (!event.GetSkipped())
1474
1476}
1477
1479 wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *, AudacityProject *project)
1480{
1481 double bkpSel0 = viewInfo.selectedRegion.t0(),
1482 bkpSel1 = viewInfo.selectedRegion.t1();
1483 // Pass keystroke to labeltrack's handler and add to history if any
1484 // updates were done
1485
1487 const auto pTrack = FindLabelTrack();
1488 const auto &mLabels = pTrack->GetLabels();
1489 auto labelStruct = mLabels[mTextEditIndex];
1490 auto &title = labelStruct.title;
1491 mUndoLabel = title;
1492 }
1493
1494 if (DoChar( *project, viewInfo.selectedRegion, event )) {
1495 ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
1496 XO("Label Edit"),
1498
1500 }
1501
1502 if (!mTextEditIndex.IsModified()) {
1503 mUndoLabel.clear();
1504 }
1505
1506 // If selection modified, refresh
1507 // Otherwise, refresh track display if the keystroke was handled
1508 if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1509 bkpSel1 != viewInfo.selectedRegion.t1())
1511 else if (!event.GetSkipped())
1513
1515}
1516
1519 AudacityProject &project, NotifyingSelectedRegion &newSel, wxKeyEvent & event)
1520{
1521 // Only track true changes to the label
1522 bool updated = false;
1523
1524 // Cache the keycode
1525 int keyCode = event.GetKeyCode();
1526 const int mods = event.GetModifiers();
1527
1528 // Check for modifiers and only allow shift
1529 // except in the case of Ctrl + F2, so hardcoded Ctrl+F2 can
1530 // be used for renaming a label
1531 if ((keyCode != WXK_F2 && mods != wxMOD_NONE && mods != wxMOD_SHIFT)
1532 || (keyCode == WXK_F2 && mods != wxMOD_CONTROL)) {
1533 event.Skip();
1534 return updated;
1535 }
1536
1537 // All editing keys are only active if we're currently editing a label
1538 const auto pTrack = FindLabelTrack();
1539 const auto &mLabels = pTrack->GetLabels();
1540 if (IsValidIndex(mTextEditIndex, project)) {
1541 // Do label text changes
1542 auto labelStruct = mLabels[mTextEditIndex];
1543 auto &title = labelStruct.title;
1544 wxUniChar wchar;
1545 bool more=true;
1546
1547 switch (keyCode) {
1548
1549 case WXK_BACK:
1550 {
1551 int len = title.length();
1552
1553 //IF the label is not blank THEN get rid of a letter or letters according to cursor position
1554 if (len > 0)
1555 {
1556 // IF there are some highlighted letters, THEN DELETE them
1559 else
1560 {
1561 // DELETE one codepoint leftwards
1562 while ((mCurrentCursorPos > 0) && more) {
1563 wchar = title.at( mCurrentCursorPos-1 );
1564 title.erase(mCurrentCursorPos-1, 1);
1566 if( ((int)wchar > 0xDFFF) || ((int)wchar <0xDC00)){
1567 pTrack->SetLabel(mTextEditIndex, labelStruct);
1568 more = false;
1569 }
1570 }
1571 }
1572 }
1573 else
1574 {
1575 // ELSE no text in text box, so DELETE whole label.
1576 pTrack->DeleteLabel(mTextEditIndex);
1578 }
1580 updated = true;
1581 }
1582 break;
1583
1584 case WXK_DELETE:
1585 case WXK_NUMPAD_DELETE:
1586 {
1587 int len = title.length();
1588
1589 //If the label is not blank get rid of a letter according to cursor position
1590 if (len > 0)
1591 {
1592 // if there are some highlighted letters, DELETE them
1595 else
1596 {
1597 // DELETE one codepoint rightwards
1598 while ((mCurrentCursorPos < len) && more) {
1599 wchar = title.at( mCurrentCursorPos );
1600 title.erase(mCurrentCursorPos, 1);
1601 if( ((int)wchar > 0xDBFF) || ((int)wchar <0xD800)){
1602 pTrack->SetLabel(mTextEditIndex, labelStruct);
1603 more = false;
1604 }
1605 }
1606 }
1607 }
1608 else
1609 {
1610 // DELETE whole label if no text in text box
1611 pTrack->DeleteLabel(mTextEditIndex);
1613 }
1615 updated = true;
1616 }
1617 break;
1618
1619 case WXK_HOME:
1620 case WXK_NUMPAD_HOME:
1621 // Move cursor to beginning of label
1623 if (mods == wxMOD_SHIFT)
1624 ;
1625 else
1627 break;
1628
1629 case WXK_END:
1630 case WXK_NUMPAD_END:
1631 // Move cursor to end of label
1632 mCurrentCursorPos = (int)title.length();
1633 if (mods == wxMOD_SHIFT)
1634 ;
1635 else
1637 break;
1638
1639 case WXK_LEFT:
1640 case WXK_NUMPAD_LEFT:
1641 // Moving cursor left
1642 if (mods != wxMOD_SHIFT && mCurrentCursorPos != mInitialCursorPos)
1643 //put cursor to the left edge of selection
1646 else
1647 {
1648 while ((mCurrentCursorPos > 0) && more) {
1649 wchar = title.at(mCurrentCursorPos - 1);
1650 more = !(((int)wchar > 0xDFFF) || ((int)wchar < 0xDC00));
1651
1653 }
1654 if (mods != wxMOD_SHIFT)
1656 }
1657
1658 break;
1659
1660 case WXK_RIGHT:
1661 case WXK_NUMPAD_RIGHT:
1662 // Moving cursor right
1663 if(mods != wxMOD_SHIFT && mCurrentCursorPos != mInitialCursorPos)
1664 //put cursor to the right edge of selection
1667 else
1668 {
1669 while ((mCurrentCursorPos < (int)title.length()) && more) {
1670 wchar = title.at(mCurrentCursorPos);
1671 more = !(((int)wchar > 0xDBFF) || ((int)wchar < 0xD800));
1672
1674 }
1675 if (mods != wxMOD_SHIFT)
1677 }
1678 break;
1679
1680 case WXK_ESCAPE:
1681 if (mTextEditIndex.IsModified()) {
1682 title = mUndoLabel;
1683 pTrack->SetLabel(mTextEditIndex, labelStruct);
1684
1685 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
1686 XO("Label Edit"),
1688 }
1689
1690 case WXK_RETURN:
1691 case WXK_NUMPAD_ENTER:
1692 case WXK_TAB:
1693 if (mRestoreFocus >= 0) {
1694 auto track = *TrackList::Get( project ).Any()
1695 .begin().advance(mRestoreFocus);
1696 if (track)
1697 TrackFocus::Get( project ).Set(track);
1698 mRestoreFocus = -2;
1699 }
1702 break;
1703 case '\x10': // OSX
1704 case WXK_MENU:
1705 case WXK_WINDOWS_MENU:
1706 ShowContextMenu( project );
1707 break;
1708
1709 default:
1710 if (!IsGoodLabelEditKey(event)) {
1711 event.Skip();
1712 }
1713 break;
1714 }
1715 }
1716 else
1717 {
1718 // Do navigation
1719 switch (keyCode) {
1720
1721 case WXK_ESCAPE:
1722 mNavigationIndex = -1;
1723 break;
1724 case WXK_TAB:
1725 case WXK_NUMPAD_TAB:
1726 if (!mLabels.empty()) {
1727 int len = (int) mLabels.size();
1728 // The case where the start of selection is the same as the
1729 // start of a label is handled separately so that if some labels
1730 // have the same start time, all labels are navigated.
1731 if (IsValidIndex(mNavigationIndex, project)
1732 && mLabels[mNavigationIndex].getT0() == newSel.t0())
1733 {
1734 if (event.ShiftDown()) {
1736 }
1737 else {
1739 }
1740 mNavigationIndex = (mNavigationIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
1741 }
1742 else
1743 {
1744 if (event.ShiftDown()) {
1745 //search for the first label starting from the end (and before selection)
1746 mNavigationIndex = len - 1;
1747 if (newSel.t0() > mLabels[0].getT0()) {
1748 while (mNavigationIndex >= 0 &&
1749 mLabels[mNavigationIndex].getT0() > newSel.t0()) {
1751 }
1752 }
1753 }
1754 else {
1755 //search for the first label starting from the beginning (and after selection)
1756 mNavigationIndex = 0;
1757 if (newSel.t0() < mLabels[len - 1].getT0()) {
1758 while (mNavigationIndex < len &&
1759 mLabels[mNavigationIndex].getT0() < newSel.t0()) {
1761 }
1762 }
1763 }
1764 }
1765
1766 if (mNavigationIndex >= 0 && mNavigationIndex < len) {
1767 const auto &labelStruct = mLabels[mNavigationIndex];
1768 mCurrentCursorPos = labelStruct.title.length();
1770 //Set the selection region to be equal to the selection bounds of the tabbed-to label.
1771 newSel = labelStruct.selectedRegion;
1772 ProjectWindow::Get(project).ScrollIntoView(labelStruct.selectedRegion.t0());
1773 // message for screen reader
1774 /* i18n-hint:
1775 String is replaced by the name of a label,
1776 first number gives the position of that label in a sequence
1777 of labels,
1778 and the last number is the total number of labels in the sequence.
1779 */
1780 auto message = XO("%s %d of %d")
1781 .Format(labelStruct.title, mNavigationIndex + 1, pTrack->GetNumLabels());
1782 TrackFocus::Get(project).MessageForScreenReader(message);
1783 }
1784 else {
1785 mNavigationIndex = -1;
1786 }
1787 }
1788 break;
1789 case WXK_F2: // Must be Ctrl + F2 to have reached here
1790 // Hardcoded Ctrl+F2 activates editing of the label
1791 // pointed to by mNavigationIndex (if valid)
1792 if (IsValidIndex(mNavigationIndex, project)) {
1794 }
1795 break;
1796 default:
1797 if (!IsGoodLabelFirstKey(event)) {
1798 event.Skip();
1799 }
1800 break;
1801 }
1802 }
1803
1804 return updated;
1805}
1806
1810 AudacityProject &project, NotifyingSelectedRegion &WXUNUSED(newSel),
1811 wxKeyEvent & event)
1812{
1813 // Check for modifiers and only allow shift.
1814 //
1815 // We still need to check this or we will eat the top level menu accelerators
1816 // on Windows if our capture or key down handlers skipped the event.
1817 const int mods = event.GetModifiers();
1818 if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1819 event.Skip();
1820 return false;
1821 }
1822
1823 // Only track true changes to the label
1824 //bool updated = false;
1825
1826 // Cache the character
1827 wxChar charCode = event.GetUnicodeKey();
1828
1829 // Skip if it's not a valid unicode character or a control character
1830 if (charCode == 0 || wxIscntrl(charCode)) {
1831 event.Skip();
1832 return false;
1833 }
1834
1835 // If we've reached this point and aren't currently editing, add NEW label
1836 const auto pTrack = FindLabelTrack();
1837 if (!IsValidIndex(mTextEditIndex, project)) {
1838 // Don't create a NEW label for a space
1839 if (wxIsspace(charCode)) {
1840 event.Skip();
1841 return false;
1842 }
1843 bool useDialog;
1844 gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false);
1845 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1846 if (useDialog) {
1847 wxString title;
1849 project, selectedRegion, charCode, title) ==
1850 wxID_CANCEL) {
1851 return false;
1852 }
1853 pTrack->SetSelected(true);
1854 pTrack->AddLabel(selectedRegion, title);
1855 ProjectHistory::Get( project )
1856 .PushState(XO("Added label"), XO("Label"));
1857 return false;
1858 }
1859 else {
1860 pTrack->SetSelected(true);
1861 AddLabel( selectedRegion );
1862 ProjectHistory::Get( project )
1863 .PushState(XO("Added label"), XO("Label"));
1864 }
1865 }
1866
1867 if (!IsValidIndex(mTextEditIndex, project))
1868 return false;
1869
1870 //
1871 // Now we are definitely in a label; append the incoming character
1872 //
1873
1874 // Test if cursor is in the end of string or not
1877
1878 const auto& mLabels = pTrack->GetLabels();
1879 auto labelStruct = mLabels[mTextEditIndex];
1880 auto& title = labelStruct.title;
1881
1882 if (mCurrentCursorPos < (int)title.length()) {
1883 // Get substring on the righthand side of cursor
1884 wxString rightPart = title.Mid(mCurrentCursorPos);
1885 // Set title to substring on the lefthand side of cursor
1887 //append charcode
1888 title += charCode;
1889 //append the right part substring
1890 title += rightPart;
1891 }
1892 else
1893 //append charCode
1894 title += charCode;
1895
1896 pTrack->SetLabel(mTextEditIndex, labelStruct );
1897
1898 //moving cursor position forward
1900
1901 return true;
1902}
1903
1904enum
1905{
1906 OnCutSelectedTextID = 1, // OSX doesn't like a 0 menu id
1911};
1912
1914{
1915 wxWindow *parent = wxWindow::FindFocus();
1916
1917 // Bug 2044. parent can be nullptr after a context switch.
1918 if( !parent )
1919 parent = &GetProjectFrame( project );
1920
1921 if( parent )
1922 {
1923 wxMenu menu;
1924 menu.Bind(wxEVT_MENU,
1925 [this, &project]( wxCommandEvent &event ){
1926 OnContextMenu( project, event ); }
1927 );
1928
1929 menu.Append(OnCutSelectedTextID, _("Cu&t Label text"));
1930 menu.Append(OnCopySelectedTextID, _("&Copy Label text"));
1931 menu.Append(OnPasteSelectedTextID, _("&Paste"));
1932 menu.Append(OnDeleteSelectedLabelID, _("&Delete Label"));
1933 menu.Append(OnEditSelectedLabelID, _("&Edit Label..."));
1934
1935 menu.Enable(OnCutSelectedTextID, IsTextSelected( project ));
1936 menu.Enable(OnCopySelectedTextID, IsTextSelected( project ));
1938 menu.Enable(OnDeleteSelectedLabelID, true);
1939 menu.Enable(OnEditSelectedLabelID, true);
1940
1941 if(!IsValidIndex(mTextEditIndex, project)) {
1942 return;
1943 }
1944
1945 const auto pTrack = FindLabelTrack();
1946 const LabelStruct *ls = pTrack->GetLabel(mTextEditIndex);
1947
1948 wxClientDC dc(parent);
1949
1950 if (msFont.Ok())
1951 {
1952 dc.SetFont(msFont);
1953 }
1954
1955 int x = 0;
1956 bool success = CalcCursorX( project, &x );
1957 wxASSERT(success);
1958 static_cast<void>(success); // Suppress unused variable warning if debug mode is disabled
1959
1960 // Bug #2571: Hackage alert! For some reason wxGTK does not like
1961 // displaying the LabelDialog from within the PopupMenu "context".
1962 // So, workaround it by editing the label AFTER the popup menu is
1963 // closed. It's really ugly, but it works. :-(
1964 mEditIndex = -1;
1965 BasicMenu::Handle{ &menu }.Popup(
1966 wxWidgetsWindowPlacement{ parent },
1967 { x, ls->y + (mIconHeight / 2) - 1 }
1968 );
1969 if (mEditIndex >= 0)
1970 {
1971 DoEditLabels( project, FindLabelTrack().get(), mEditIndex );
1972 }
1973 }
1974}
1975
1977 AudacityProject &project, wxCommandEvent & evt )
1978{
1979 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1980
1981 switch (evt.GetId())
1982 {
1985 if (CutSelectedText( project ))
1986 {
1987 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
1988 XO("Label Edit"),
1990 }
1991 break;
1992
1995 CopySelectedText( project );
1996 break;
1997
2001 project, selectedRegion.t0(), selectedRegion.t1() ))
2002 {
2003 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
2004 XO("Label Edit"),
2006 }
2007 break;
2008
2011 if (IsValidIndex(mTextEditIndex, project))
2012 {
2013 const auto pTrack = FindLabelTrack();
2014 pTrack->DeleteLabel(mTextEditIndex);
2015 ProjectHistory::Get( project ).PushState(XO("Deleted Label"),
2016 XO("Label Edit"),
2018 }
2019 }
2020 break;
2021
2022 case OnEditSelectedLabelID: {
2023 // Bug #2571: See above
2024 if (IsValidIndex(mTextEditIndex, project))
2026 }
2027 break;
2028 }
2029}
2030
2032{
2033 wxString left, right;
2034
2035 int init = mInitialCursorPos;
2036 int cur = mCurrentCursorPos;
2037 if (init > cur)
2038 std::swap(init, cur);
2039
2040 const auto pTrack = FindLabelTrack();
2041 const auto &mLabels = pTrack->GetLabels();
2042 auto labelStruct = mLabels[mTextEditIndex];
2043 auto &title = labelStruct.title;
2044
2045 if (init > 0)
2046 left = title.Left(init);
2047
2048 if (cur < (int)title.length())
2049 right = title.Mid(cur);
2050
2051 title = left + right;
2052 pTrack->SetLabel( mTextEditIndex, labelStruct );
2053 mInitialCursorPos = mCurrentCursorPos = left.length();
2054}
2055/*
2056bool LabelTrackView::HasSelectedLabel( AudacityProject &project ) const
2057{
2058 const auto selIndex = GetSelectionIndex( project );
2059 return (selIndex >= 0 &&
2060 selIndex < (int)FindLabelTrack()->GetLabels().size());
2061}*/
2062
2063int LabelTrackView::GetLabelIndex(double t, double t1)
2064{
2065 //We'd have liked to have times in terms of samples,
2066 //because then we're doing an intrger comparison.
2067 //Never mind. Instead we look for near enough.
2068 //This level of (in)accuracy is only a problem if we
2069 //deal with sounds in the MHz range.
2070 const double delta = 1.0e-7;
2071 const auto pTrack = FindLabelTrack();
2072 const auto &mLabels = pTrack->GetLabels();
2073 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
2074 if( fabs( labelStruct.getT0() - t ) > delta )
2075 continue;
2076 if( fabs( labelStruct.getT1() - t1 ) > delta )
2077 continue;
2078 return i;
2079 }}
2080
2081 return wxNOT_FOUND;
2082}
2083
2084
2085// restoreFocus of -1 is the default, and sets the focus to this label.
2086// restoreFocus of -2 or other value leaves the focus unchanged.
2087// restoreFocus >= 0 will later cause focus to move to that track.
2089 const wxString &title, int restoreFocus)
2090{
2091 const auto pTrack = FindLabelTrack();
2092 mRestoreFocus = restoreFocus;
2093 auto pos = pTrack->AddLabel( selectedRegion, title );
2094 return pos;
2095}
2096
2098{
2099 e.Skip();
2100 if ( e.mpTrack.lock() != FindTrack() )
2101 return;
2102
2103 const auto &title = e.mTitle;
2104 const auto pos = e.mPresentPosition;
2105
2107
2108 // restoreFocus is -2 e.g. from Nyquist label creation, when we should not
2109 // even lose the focus and open the label to edit in the first place.
2110 // -1 means we don't need to restore it to anywhere.
2111 // 0 or above is the track to restore to after editing the label is complete.
2112 if( mRestoreFocus >= -1 )
2113 mTextEditIndex = pos;
2114
2115 if( mRestoreFocus < 0 )
2116 mRestoreFocus = -2;
2117}
2118
2120{
2121 e.Skip();
2122 if ( e.mpTrack.lock() != FindTrack() )
2123 return;
2124
2125 auto index = e.mFormerPosition;
2126
2127 // IF we've deleted the selected label
2128 // THEN set no label selected.
2129 if (mTextEditIndex == index)
2131
2132 // IF we removed a label before the selected label
2133 // THEN the NEW selected label number is one less.
2134 else if( index < mTextEditIndex)
2135 --mTextEditIndex;//NB: Keep cursor selection region
2136}
2137
2139{
2140 e.Skip();
2141 if ( e.mpTrack.lock() != FindTrack() )
2142 return;
2143
2144 auto former = e.mFormerPosition;
2145 auto present = e.mPresentPosition;
2146
2147 auto fix = [&](Index& index) {
2148 if (index == former)
2149 index = present;
2150 else if (former < index && index <= present)
2151 --index;
2152 else if (former > index && index >= present)
2153 ++index;
2154 };
2155 fix(mNavigationIndex);
2156 fix(mTextEditIndex);
2157}
2158
2160{
2161 e.Skip();
2162 if ( e.mpTrack.lock() != FindTrack() )
2163 return;
2164
2165 if (!FindTrack()->GetSelected())
2166 {
2169 }
2170}
2171
2173{
2174 return theTheme.Bitmap( i + bmpLabelGlyph0);
2175}
2176
2177// This one XPM spec is used to generate a number of
2178// different wxIcons.
2179/* XPM */
2180static const char *const GlyphXpmRegionSpec[] = {
2181/* columns rows colors chars-per-pixel */
2182"15 23 7 1",
2183/* Default colors, with first color transparent */
2184". c none",
2185"2 c black",
2186"3 c black",
2187"4 c black",
2188"5 c #BEBEF0",
2189"6 c #BEBEF0",
2190"7 c #BEBEF0",
2191/* pixels */
2192"...............",
2193"...............",
2194"...............",
2195"....333.444....",
2196"...3553.4774...",
2197"...3553.4774...",
2198"..35553.47774..",
2199"..35522222774..",
2200".3552666662774.",
2201".3526666666274.",
2202"355266666662774",
2203"355266666662774",
2204"355266666662774",
2205".3526666666274.",
2206".3552666662774.",
2207"..35522222774..",
2208"..35553.47774..",
2209"...3553.4774...",
2210"...3553.4774...",
2211"....333.444....",
2212"...............",
2213"...............",
2214"..............."
2215};
2216
2233{
2234 int iConfig;
2235 int iHighlight;
2236 int index;
2237 const int nSpecRows =
2238 sizeof( GlyphXpmRegionSpec )/sizeof( GlyphXpmRegionSpec[0]);
2239 const char *XmpBmp[nSpecRows];
2240
2241 // The glyphs are declared static wxIcon; so we only need
2242 // to create them once, no matter how many LabelTracks.
2243 if( mbGlyphsReady )
2244 return;
2245
2246 // We're about to tweak the basic color spec to get 12 variations.
2247 for( iConfig=0;iConfig<NUM_GLYPH_CONFIGS;iConfig++)
2248 {
2249 for( iHighlight=0;iHighlight<NUM_GLYPH_HIGHLIGHTS;iHighlight++)
2250 {
2251 index = iConfig + NUM_GLYPH_CONFIGS * iHighlight;
2252 // Copy the basic spec...
2253 memcpy( XmpBmp, GlyphXpmRegionSpec, sizeof( GlyphXpmRegionSpec ));
2254 // The highlighted region (if any) is white...
2255 if( iHighlight==1 ) XmpBmp[5]="5 c #FFFFFF";
2256 if( iHighlight==2 ) XmpBmp[6]="6 c #FFFFFF";
2257 if( iHighlight==3 ) XmpBmp[7]="7 c #FFFFFF";
2258 // For left or right arrow the other side of the glyph
2259 // is the transparent color.
2260 if( iConfig==0) { XmpBmp[3]="3 c none"; XmpBmp[5]="5 c none"; }
2261 if( iConfig==1) { XmpBmp[4]="4 c none"; XmpBmp[7]="7 c none"; }
2262 // Create the icon from the tweaked spec.
2263 mBoundaryGlyphs[index] = wxBitmap(XmpBmp);
2264 // Create the mask
2265 // SetMask takes ownership
2266 mBoundaryGlyphs[index].SetMask(safenew wxMask(mBoundaryGlyphs[index], wxColour(192, 192, 192)));
2267 }
2268 }
2269
2270 mIconWidth = mBoundaryGlyphs[0].GetWidth();
2271 mIconHeight = mBoundaryGlyphs[0].GetHeight();
2272 mTextHeight = mIconHeight; // until proved otherwise...
2273 // The icon should have an odd width so that the
2274 // line goes exactly down the middle.
2275 wxASSERT( (mIconWidth %2)==1);
2276
2277 mbGlyphsReady=true;
2278}
2279
2280#include "../../../LabelDialog.h"
2281
2283(AudacityProject &project, LabelTrack *lt, int index)
2284{
2285 const auto &settings = ProjectSettings::Get( project );
2286 auto format = settings.GetSelectionFormat(),
2287 freqFormat = settings.GetFrequencySelectionFormatName();
2288 auto &tracks = TrackList::Get( project );
2289 auto rate = ProjectRate::Get( project ).GetRate();
2290 auto &viewInfo = ViewInfo::Get( project );
2291 auto &window = ProjectWindow::Get( project );
2292
2293 LabelDialog dlg(&window, project, &tracks,
2294 lt, index,
2295 viewInfo, rate,
2296 format, freqFormat);
2297#ifdef __WXGTK__
2298 dlg.Raise();
2299#endif
2300
2301 if (dlg.ShowModal() == wxID_OK) {
2302 ProjectHistory::Get( project )
2303 .PushState(XO("Edited labels"), XO("Label"));
2304 }
2305}
2306
2308 AudacityProject &project,
2309 const SelectedRegion& region, const wxString& initialValue, wxString& value)
2310{
2311 auto &trackFocus = TrackFocus::Get( project );
2312 auto &trackPanel = TrackPanel::Get( project );
2313 auto &viewInfo = ViewInfo::Get( project );
2314
2315 wxPoint position =
2316 trackPanel.FindTrackRect( trackFocus.Get() ).GetBottomLeft();
2317 // The start of the text in the text box will be roughly in line with the label's position
2318 // if it's a point label, or the start of its region if it's a region label.
2319 position.x += viewInfo.GetLeftOffset()
2320 + std::max(0, static_cast<int>(viewInfo.TimeToPosition(region.t0())))
2321 - 39;
2322 position.y += 2; // just below the bottom of the track
2323 position = trackPanel.ClientToScreen(position);
2324 auto &window = GetProjectFrame( project );
2325 AudacityTextEntryDialog dialog{ &window,
2326 XO("Name:"),
2327 XO("New label"),
2328 initialValue,
2329 wxOK | wxCANCEL,
2330 position };
2331
2332 // keep the dialog within Audacity's window, so that the dialog is always fully visible
2333 wxRect dialogScreenRect = dialog.GetScreenRect();
2334 wxRect projScreenRect = window.GetScreenRect();
2335 wxPoint max = projScreenRect.GetBottomRight() + wxPoint{ -dialogScreenRect.width, -dialogScreenRect.height };
2336 if (dialogScreenRect.x > max.x) {
2337 position.x = max.x;
2338 dialog.Move(position);
2339 }
2340 if (dialogScreenRect.y > max.y) {
2341 position.y = max.y;
2342 dialog.Move(position);
2343 }
2344
2345 dialog.SetInsertionPointEnd(); // because, by default, initial text is selected
2346 int status = dialog.ShowModal();
2347 if (status != wxID_CANCEL) {
2348 value = dialog.GetValue();
2349 value.Trim(true).Trim(false);
2350 }
2351
2352 return status;
2353}
2354
2357 return [](LabelTrack &track) {
2358 return std::make_shared<LabelTrackView>( track.SharedPointer() );
2359 };
2360}
2361
2362std::shared_ptr<TrackVRulerControls> LabelTrackView::DoGetVRulerControls()
2363{
2364 return
2365 std::make_shared<LabelTrackVRulerControls>( shared_from_this() );
2366}
2367
2371 return [](auto &) { return SyncLockPolicy::EndSeparator; };
2372}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
int format
Definition: ExportPCM.cpp:56
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
static bool IsGoodLabelEditKey(const wxKeyEvent &evt)
This returns true for keys we capture for label editing.
@ OnEditSelectedLabelID
@ OnDeleteSelectedLabelID
@ OnPasteSelectedTextID
@ OnCopySelectedTextID
@ OnCutSelectedTextID
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetLabelTrackView)
static const char *const GlyphXpmRegionSpec[]
static bool IsGoodLabelFirstKey(const wxKeyEvent &evt)
Returns true for keys we capture to start a label.
constexpr int NUM_GLYPH_HIGHLIGHTS
constexpr int MAX_NUM_ROWS
constexpr int NUM_GLYPH_CONFIGS
#define safenew
Definition: MemoryX.h:10
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
an object holding per-project preferred sample rate
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
@ EndSeparator
Delimits the end of a group (of which it is a part)
THEME_API Theme theTheme
Definition: Theme.cpp:82
static Settings & settings()
Definition: TrackInfo.cpp:87
static wxBrush labelSelectedBrush
Definition: AColor.h:117
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static wxBrush uglyBrush
Definition: AColor.h:137
static wxPen labelSurroundPen
Definition: AColor.h:122
static wxBrush labelTextNormalBrush
Definition: AColor.h:114
static wxBrush labelTextEditBrush
Definition: AColor.h:115
static wxBrush labelUnselectedBrush
Definition: AColor.h:116
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
Wrap wxTextEntryDialog so that caption IS translatable.
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:89
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:207
UIHandlePtr Target()
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:309
void Reparent(const std::shared_ptr< Track > &parent) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
std::shared_ptr< Track > FindTrack()
Dialog for editing labels.
Definition: LabelDialog.h:36
std::shared_ptr< LabelTrackHit > mpHit
static UIHandlePtr HitTest(std::weak_ptr< LabelGlyphHandle > &holder, const wxMouseState &state, const std::shared_ptr< LabelTrack > &pLT, const wxRect &rect)
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:31
double getT1() const
Definition: LabelTrack.h:42
int x1
Pixel position of left hand glyph.
Definition: LabelTrack.h:77
int x
width of the text in pixels.
Definition: LabelTrack.h:76
double getT0() const
Definition: LabelTrack.h:41
wxString title
Definition: LabelTrack.h:72
int width
Text of the label.
Definition: LabelTrack.h:73
int xText
Pixel position of right hand glyph.
Definition: LabelTrack.h:78
int y
Pixel position of left hand side of text box.
Definition: LabelTrack.h:79
static UIHandlePtr HitTest(std::weak_ptr< LabelTextHandle > &holder, const wxMouseState &state, const std::shared_ptr< LabelTrack > &pLT)
std::shared_ptr< LabelTrack > GetTrack() const
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:89
const LabelArray & GetLabels() const
Definition: LabelTrack.h:148
void CalcHighlightXs(int *x1, int *x2) const
std::shared_ptr< LabelTrack > FindLabelTrack()
static LabelTrackView & Get(LabelTrack &)
unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
void OnSelectionChange(LabelTrackEvent &)
bool DoCaptureKey(AudacityProject &project, wxKeyEvent &event)
static constexpr int TextFramePadding
std::shared_ptr< TrackVRulerControls > DoGetVRulerControls() override
static constexpr int LabelBarHeight
static void DrawLines(wxDC &dc, const LabelStruct &ls, const wxRect &r)
void Reparent(const std::shared_ptr< Track > &parent) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
static void DrawHighlight(wxDC &dc, const LabelStruct &ls, int xPos1, int xPos2, int charHeight)
Draws text-selected region within the label.
static int mTextHeight
bool CopySelectedText(AudacityProject &project)
static void calculateFontHeight(wxDC &dc)
static wxBitmap mBoundaryGlyphs[NUM_GLYPH_CONFIGS *NUM_GLYPH_HIGHLIGHTS]
static wxFont msFont
~LabelTrackView() override
static int mFontHeight
void OnLabelAdded(LabelTrackEvent &)
static void DrawGlyphs(wxDC &dc, const LabelStruct &ls, const wxRect &r, int GlyphLeft, int GlyphRight)
static int DialogForLabelName(AudacityProject &project, const SelectedRegion &region, const wxString &initialValue, wxString &value)
Flags SaveFlags() const
int AddLabel(const SelectedRegion &region, const wxString &title={}, int restoreFocus=-1)
bool IsValidIndex(const Index &index, AudacityProject &project) const
void BindTo(LabelTrack *pParent)
static bool mbGlyphsReady
void ComputeLayout(const wxRect &r, const ZoomInfo &zoomInfo) const
int mRestoreFocus
initial cursor position
static void DrawBar(wxDC &dc, const LabelStruct &ls, const wxRect &r)
static wxFont GetFont(const wxString &faceName, int size=DefaultFontSize)
static void ResetFont()
void ShowContextMenu(AudacityProject &project)
bool CalcCursorX(AudacityProject &project, int *x) const
int GetLabelIndex(double t, double t1)
void UnbindFrom(LabelTrack *pParent)
void ComputeTextPosition(const wxRect &r, int index) const
int mInitialCursorPos
current cursor position
void OnContextMenu(AudacityProject &project, wxCommandEvent &evt)
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
LabelTrackView(const LabelTrackView &)=delete
Index mTextEditIndex
Index of the current label text being edited.
bool DoChar(AudacityProject &project, NotifyingSelectedRegion &sel, wxKeyEvent &event)
void SetTextSelection(int labelIndex, int start=1, int end=1)
bool PasteSelectedText(AudacityProject &project, double sel0, double sel1)
static void DoEditLabels(AudacityProject &project, LabelTrack *lt=nullptr, int index=-1)
bool SelectAllText(AudacityProject &project)
std::weak_ptr< LabelTextHandle > mTextHandle
void CopyTo(Track &track) const override
Copy state, for undo/redo purposes.
bool DoKeyDown(AudacityProject &project, NotifyingSelectedRegion &sel, wxKeyEvent &event)
KeyEvent is called for every keypress when over the label track.
static void DrawText(wxDC &dc, const LabelStruct &ls, const wxRect &r)
static wxBitmap & GetGlyph(int i)
int FindCursorPosition(int labelIndex, wxCoord xPos)
convert pixel coordinate to character position in text box
static int GetTextFrameHeight()
static int mIconWidth
std::weak_ptr< LabelGlyphHandle > mGlyphHandle
void Draw(TrackPanelDrawingContext &context, const wxRect &r) const
void OnLabelDeleted(LabelTrackEvent &)
void OnLabelPermuted(LabelTrackEvent &)
void RestoreFlags(const Flags &flags)
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
bool CutSelectedText(AudacityProject &project)
bool IsTextSelected(AudacityProject &project) const
static bool OverTextBox(const LabelStruct *pLabel, int x, int y)
static int mIconHeight
static bool IsTextClipSupported()
int GetTextEditIndex(AudacityProject &project) const
void SetNavigationIndex(int index)
wxString mUndoLabel
static void OverGlyph(const LabelTrack &track, LabelTrackHit &hit, int x, int y)
static void DrawTextBox(wxDC &dc, const LabelStruct &ls, const wxRect &r)
static int OverATextBox(const LabelTrack &track, int xx, int yy)
std::vector< UIHandlePtr > DetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool) override
static constexpr int TextFrameYOffset
void SetCurrentCursorPosition(int pos)
int GetNavigationIndex(AudacityProject &project) const
double t1() const
Definition: ViewInfo.h:35
double t0() const
Definition: ViewInfo.h:34
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
static ProjectSettings & Get(AudacityProject &project)
static ProjectWindow & Get(AudacityProject &project)
void ScrollIntoView(double pos)
Defines a selected portion of a project.
double t0() const
static bool IsSelectedOrSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:73
wxColour & Colour(int iIndex)
wxBitmap & Bitmap(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1429
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:467
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
The TrackPanel class coordinates updates and operations on the main part of the screen which contains...
Definition: TrackPanel.h:65
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:229
void CopyTo(Track &track) const override
Copy state, for undo/redo purposes.
Definition: TrackView.cpp:52
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
int GetLeftOffset() const
Definition: ZoomInfo.h:103
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:51
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:419
void getXPos(const LabelStruct &ls, wxDC &dc, int *xPos1, int cursorPos)
LabelTrackHit * findHit(TrackPanel *pPanel)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753
For defining overrides of the method.
const std::weak_ptr< Track > mpTrack
Definition: LabelTrack.h:225
wxString mTitle
Definition: LabelTrack.h:228
int mMouseOverLabelRight
Keeps track of which left label the mouse is currently over.
int mMouseOverLabelLeft
Keeps track of which (ranged) label the mouse is currently over.
void SetModified(bool modified)
Index & operator=(int index)
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.