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