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 bool bAvoidName = false;
443 const int nRows = wxMin((r.height / yRowHeight) + 1, MAX_NUM_ROWS);
444 if( nRows > 2 )
445 bAvoidName = gPrefs->ReadBool(wxT("/GUI/ShowTrackNameInWaveform"), false);
446 // Initially none of the rows have been used.
447 // So set a value that is less than any valid value.
448 {
449 // Bug 502: With dragging left of zeros, labels can be in
450 // negative space. So set least possible value as starting point.
451 const int xStart = INT_MIN;
452 for (auto &x : xUsed)
453 x = xStart;
454 }
455 int nRowsUsed=0;
456
457 const auto pTrack = FindLabelTrack();
458 const auto &mLabels = pTrack->GetLabels();
459
460 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
461 const int x = zoomInfo.TimeToPosition(labelStruct.getT0(), r.x);
462 const int x1 = zoomInfo.TimeToPosition(labelStruct.getT1(), r.x);
463 int y = r.y;
464
465 labelStruct.x=x;
466 labelStruct.x1=x1;
467 labelStruct.y=-1;// -ve indicates nothing doing.
468 iRow=0;
469 // Our first preference is a row that ends where we start.
470 // (This is to encourage merging of adjacent label boundaries).
471 while( (iRow<nRowsUsed) && (xUsed[iRow] != x ))
472 iRow++;
473
474 // IF we didn't find one THEN
475 // find any row that can take a span starting at x.
476 if( iRow >= nRowsUsed )
477 {
478 iRow=0;
479 while( (iRow<nRows) && (xUsed[iRow] > x ))
480 iRow++;
481 }
482 // IF we found such a row THEN record a valid position.
483 if( iRow<nRows )
484 {
485 // Logic to ameliorate case where first label is under the
486 // (on track) track name. For later labels it does not matter
487 // as we can scroll left or right and/or zoom.
488 // A possible alternative idea would be to (instead) increase the
489 // translucency of the track name, when the mouse is inside it.
490 if( (i==0 ) && (iRow==0) && bAvoidName ){
491 // reserve some space in first row.
492 // reserve max of 200px or t1, or text box right edge.
493 const int x2 = zoomInfo.TimeToPosition(0.0, r.x) + 200;
494 xUsed[iRow]=x+labelStruct.width+xExtra;
495 if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
496 if( xUsed[iRow] < x2 ) xUsed[iRow]=x2;
497 iRow=1;
498 }
499
500 // Possibly update the number of rows actually used.
501 if( iRow >= nRowsUsed )
502 nRowsUsed=iRow+1;
503 // Record the position for this label
504 y= r.y + iRow * yRowHeight +(yRowHeight/2)+1;
505 labelStruct.y=y;
506 // On this row we have used up to max of end marker and width.
507 // Plus also allow space to show the start icon and
508 // some space for the text frame.
509 xUsed[iRow]=x+labelStruct.width+xExtra;
510 if( xUsed[iRow] < x1 ) xUsed[iRow]=x1;
511 ComputeTextPosition( r, i );
512 }
513 }}
514}
515
521 wxDC & dc, const LabelStruct &ls, const wxRect & r)
522{
523 auto &x = ls.x;
524 auto &x1 = ls.x1;
525 auto &y = ls.y;
526
527 // Bug 2388 - Point label and range label can appear identical
528 // If the start and end times are not actually the same, but they
529 // would appear so when drawn as lines at current zoom, be sure to draw
530 // two lines - i.e. displace the second line slightly.
531 if (ls.getT0() != ls.getT1()) {
532 if (x == x1)
533 x1++;
534 }
535
536 // How far out from the centre line should the vertical lines
537 // start, i.e. what is the y position of the icon?
538 // We adjust this so that the line encroaches on the icon
539 // slightly (there is white space in the design).
540 const int yIconStart = y - (mIconHeight /2)+1+(mTextHeight+3)/2;
541 const int yIconEnd = yIconStart + mIconHeight-2;
542
543 // If y is positive then it is the center line for the
544 // Label.
545 if( y >= 0 )
546 {
547 if((x >= r.x) && (x <= (r.x+r.width)))
548 {
549 // Draw line above and below left dragging widget.
550 AColor::Line(dc, x, r.y, x, yIconStart - 1);
551 AColor::Line(dc, x, yIconEnd, x, r.y + r.height);
552 }
553 if((x1 >= r.x) && (x1 <= (r.x+r.width)))
554 {
555 // Draw line above and below right dragging widget.
556 AColor::Line(dc, x1, r.y, x1, yIconStart - 1);
557 AColor::Line(dc, x1, yIconEnd, x1, r.y + r.height);
558 }
559 }
560 else
561 {
562 // Draw the line, even though the widget is off screen
563 AColor::Line(dc, x, r.y, x, r.y + r.height);
564 AColor::Line(dc, x1, r.y, x1, r.y + r.height);
565 }
566}
567
572 wxDC & dc, const LabelStruct &ls, const wxRect & r,
573 int GlyphLeft, int GlyphRight)
574{
575 auto &y = ls.y;
576
577 const int xHalfWidth=mIconWidth/2;
578 const int yStart=y-mIconHeight/2+(mTextHeight+3)/2;
579
580 // If y == -1, nothing to draw
581 if( y == -1 )
582 return;
583
584 auto &x = ls.x;
585 auto &x1 = ls.x1;
586
587 if((x >= r.x) && (x <= (r.x+r.width)))
588 dc.DrawBitmap(GetGlyph(GlyphLeft), x-xHalfWidth,yStart, true);
589 // The extra test commented out here would suppress right hand markers
590 // when they overlap the left hand marker (e.g. zoomed out) or to the left.
591 if((x1 >= r.x) && (x1 <= (r.x+r.width)) /*&& (x1>x+mIconWidth)*/)
592 dc.DrawBitmap(GetGlyph(GlyphRight), x1-xHalfWidth,yStart, true);
593}
594
596{
597 return mTextHeight + TextFramePadding * 2;
598}
599
606void LabelTrackView::DrawText(wxDC & dc, const LabelStruct &ls, const wxRect & r)
607{
608 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
609 //If y is positive then it is the center line for the
610 //text we are about to draw.
611 //if it isn't, nothing to draw.
612
613 auto &y = ls.y;
614 if( y == -1 )
615 return;
616
617 // Draw frame for the text...
618 // We draw it half an icon width left of the text itself.
619 {
620 auto &xText = ls.xText;
621 const int xStart=wxMax(r.x,xText-mIconWidth/2);
622 const int xEnd=wxMin(r.x+r.width,xText+ls.width+mIconWidth/2);
623 const int xWidth = xEnd-xStart;
624
625 if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
626 {
627 // Now draw the text itself.
628 auto pos = y - LabelBarHeight - yFrameHeight + TextFrameYOffset +
629 (yFrameHeight - mFontHeight) / 2 + dc.GetFontMetrics().ascent;
630 dc.DrawText(ls.title, xText, pos);
631 }
632 }
633
634}
635
637 wxDC & dc, const LabelStruct &ls, const wxRect & r)
638{
639 // In drawing the bar and the frame, we compute the clipping
640 // to the viewport ourselves. Under Win98 the GDI does its
641 // calculations in 16 bit arithmetic, and so gets it completely
642 // wrong at higher zooms where the bar can easily be
643 // more than 65536 pixels wide.
644
645 // Draw bar for label extent...
646 // We don't quite draw from x to x1 because we allow
647 // half an icon width at each end.
648 const auto textFrameHeight = GetTextFrameHeight();
649 auto& xText = ls.xText;
650 const int xStart = wxMax(r.x, xText - mIconWidth / 2);
651 const int xEnd = wxMin(r.x + r.width, xText + ls.width + mIconWidth / 2);
652 const int xWidth = xEnd - xStart;
653
654 if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
655 {
656 wxRect frame(
657 xStart, ls.y - (textFrameHeight + LabelBarHeight) / 2 + TextFrameYOffset,
658 xWidth, textFrameHeight);
659 dc.DrawRectangle(frame);
660 }
661}
662
663void LabelTrackView::DrawBar(wxDC& dc, const LabelStruct& ls, const wxRect& r)
664{
665 //If y is positive then it is the center line for the
666 //text we are about to draw.
667 const int xBarShorten = mIconWidth + 4;
668 auto& y = ls.y;
669 if (y == -1)
670 return;
671
672 auto& x = ls.x;
673 auto& x1 = ls.x1;
674 const int xStart = wxMax(r.x, x + xBarShorten / 2);
675 const int xEnd = wxMin(r.x + r.width, x1 - xBarShorten / 2);
676 const int xWidth = xEnd - xStart;
677
678 if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
679 {
680 wxRect bar(xStart, y - (LabelBarHeight - GetTextFrameHeight()) / 2,
681 xWidth, LabelBarHeight);
682 if (x1 > x + xBarShorten)
683 dc.DrawRectangle(bar);
684 }
685}
686
689 int xPos1, int xPos2, int charHeight)
690{
691 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
692
693 dc.SetPen(*wxTRANSPARENT_PEN);
694 wxBrush curBrush = dc.GetBrush();
695 curBrush.SetColour(wxString(wxT("BLUE")));
696 auto top = ls.y + TextFrameYOffset - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - charHeight) / 2;
697 if (xPos1 < xPos2)
698 dc.DrawRectangle(xPos1-1, top, xPos2-xPos1+1, charHeight);
699 else
700 dc.DrawRectangle(xPos2-1, top, xPos1-xPos2+1, charHeight);
701}
702
703namespace {
704void getXPos( const LabelStruct &ls, wxDC & dc, int * xPos1, int cursorPos)
705{
706 *xPos1 = ls.xText;
707 if( cursorPos > 0)
708 {
709 int partWidth;
710 // Calculate the width of the substring and add it to Xpos
711 dc.GetTextExtent(ls.title.Left(cursorPos), &partWidth, NULL);
712 *xPos1 += partWidth;
713 }
714}
715}
716
718{
720 wxMemoryDC dc;
721
722 if (msFont.Ok()) {
723 dc.SetFont(msFont);
724 }
725
726 const auto pTrack = FindLabelTrack();
727 const auto &mLabels = pTrack->GetLabels();
728
729 getXPos(mLabels[mTextEditIndex], dc, x, mCurrentCursorPos);
730 *x += mIconWidth / 2;
731 return true;
732 }
733
734 return false;
735}
736
737void LabelTrackView::CalcHighlightXs(int *x1, int *x2) const
738{
739 wxMemoryDC dc;
740
741 if (msFont.Ok()) {
742 dc.SetFont(msFont);
743 }
744
745 int pos1 = mInitialCursorPos, pos2 = mCurrentCursorPos;
746 if (pos1 > pos2)
747 std::swap(pos1, pos2);
748
749 const auto pTrack = FindLabelTrack();
750 const auto &mLabels = pTrack->GetLabels();
751 const auto &labelStruct = mLabels[mTextEditIndex];
752
753 // find the left X pos of highlighted area
754 getXPos(labelStruct, dc, x1, pos1);
755 // find the right X pos of highlighted area
756 getXPos(labelStruct, dc, x2, pos2);
757}
758
759#include "LabelGlyphHandle.h"
760namespace {
762 {
763 if (! pPanel )
764 return nullptr;
765
766 // Fetch the highlighting state
767 auto target = pPanel->Target();
768 if (target) {
769 auto handle = dynamic_cast<LabelGlyphHandle*>( target.get() );
770 if (handle)
771 return &*handle->mpHit;
772 }
773 return nullptr;
774 }
775}
776
777#include "../../../TrackPanelDrawingContext.h"
778#include "LabelTextHandle.h"
779
783void LabelTrackView::Draw(TrackPanelDrawingContext &context, const wxRect & r)
784const
785{
786 auto &dc = context.dc;
787 const auto artist = TrackArtist::Get( context );
788 const auto &zoomInfo = *artist->pZoomInfo;
789 const auto &pendingTracks = *artist->pPendingTracks;
790
791 auto pHit = findHit( artist->parent );
792
793 if(msFont.Ok())
794 dc.SetFont(msFont);
795
796 if (mFontHeight == -1)
798
799 if (!FindChannel())
800 return;
801 const auto &track = static_cast<const LabelTrack&>(
802 pendingTracks.SubstitutePendingChangedChannel(*FindChannel()));
803 const auto &mLabels = track.GetLabels();
804
808
809 wxCoord textWidth, textHeight;
810
811 // Get the text widths.
812 // TODO: Make more efficient by only re-computing when a
813 // text label title changes.
814 for (const auto &labelStruct : mLabels) {
815 dc.GetTextExtent(labelStruct.title, &textWidth, &textHeight);
816 labelStruct.width = textWidth;
817 }
818
819 // TODO: And this only needs to be done once, but we
820 // do need the dc to do it.
821 // We need to set mTextHeight to something sensible,
822 // guarding against the case where there are no
823 // labels or all are empty strings, which for example
824 // happens with a NEW label track.
825 mTextHeight = dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent;
826 const int yFrameHeight = mTextHeight + TextFramePadding * 2;
827
828 ComputeLayout( r, zoomInfo );
829 dc.SetTextForeground(theTheme.Colour( clrLabelTrackText));
830 dc.SetBackgroundMode(wxTRANSPARENT);
831 dc.SetBrush(AColor::labelTextNormalBrush);
832 dc.SetPen(AColor::labelSurroundPen);
833 int GlyphLeft;
834 int GlyphRight;
835 // Now we draw the various items in this order,
836 // so that the correct things overpaint each other.
837
838 // Draw vertical lines that show where the end positions are.
839 for (const auto &labelStruct : mLabels)
840 DrawLines( dc, labelStruct, r );
841
842 // Draw the end glyphs.
843 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
844 GlyphLeft=0;
845 GlyphRight=1;
846 if( pHit && i == pHit->mMouseOverLabelLeft )
847 GlyphLeft = (pHit->mEdge & 4) ? 6:9;
848 if( pHit && i == pHit->mMouseOverLabelRight )
849 GlyphRight = (pHit->mEdge & 4) ? 7:4;
850 DrawGlyphs( dc, labelStruct, r, GlyphLeft, GlyphRight );
851 }}
852
853 auto &project = *artist->parent->GetProject();
854
855 // Draw the label boxes.
856 {
857#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
858 bool highlightTrack = false;
859 auto target = dynamic_cast<LabelTextHandle*>(context.target.get());
860 highlightTrack = target &&
861 target->FindTrack().get() == FindTrack().get();
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 CommonChannelView::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{
1008 return mTextEditIndex;
1009 return -1;
1010}
1012{
1013 mTextEditIndex = -1;
1016}
1018{
1019 mNavigationIndex = index;
1020}
1022{
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
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
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{
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
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),
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 Viewport::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();
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:
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.
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 Viewport::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)
1794 }
1795 break;
1796 default:
1797 if (!IsGoodLabelFirstKey(event)) {
1798 event.Skip();
1799 }
1800 break;
1801 }
1802 }
1803
1804 return updated;
1805}
1806
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();
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);
1856 .PushState(XO("Added label"), XO("Label"));
1857 return false;
1858 }
1859 else {
1860 pTrack->SetSelected(true);
1861 AddLabel( selectedRegion );
1863 .PushState(XO("Added label"), XO("Label"));
1864 }
1865 }
1866
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
1938 menu.Enable(OnDeleteSelectedLabelID, true);
1939 menu.Enable(OnEditSelectedLabelID, true);
1940
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 {
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
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
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
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 (counting
2088// tracks, not channels)
2090 const wxString &title, int restoreFocus)
2091{
2092 const auto pTrack = FindLabelTrack();
2093 mRestoreFocus = restoreFocus;
2094 auto pos = pTrack->AddLabel( selectedRegion, title );
2095 return pos;
2096}
2097
2099{
2100 if (e.mpTrack.lock() != FindLabelTrack())
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 if (e.mpTrack.lock() != FindLabelTrack())
2122 return;
2123
2124 auto index = e.mFormerPosition;
2125
2126 // IF we've deleted the selected label
2127 // THEN set no label selected.
2128 if (mTextEditIndex == index)
2130
2131 // IF we removed a label before the selected label
2132 // THEN the NEW selected label number is one less.
2133 else if( index < mTextEditIndex)
2134 --mTextEditIndex;//NB: Keep cursor selection region
2135}
2136
2138{
2139 if (e.mpTrack.lock() != FindLabelTrack())
2140 return;
2141
2142 auto former = e.mFormerPosition;
2143 auto present = e.mPresentPosition;
2144
2145 auto fix = [&](Index& index) {
2146 if (index == former)
2147 index = present;
2148 else if (former < index && index <= present)
2149 --index;
2150 else if (former > index && index >= present)
2151 ++index;
2152 };
2153 fix(mNavigationIndex);
2154 fix(mTextEditIndex);
2155}
2156
2158{
2159 if (e.mpTrack.lock() != FindLabelTrack())
2160 return;
2161
2162 if (!FindLabelTrack()->GetSelected())
2163 {
2166 }
2167}
2168
2170{
2171 return theTheme.Bitmap( i + bmpLabelGlyph0);
2172}
2173
2174// This one XPM spec is used to generate a number of
2175// different wxIcons.
2176/* XPM */
2177static const char *const GlyphXpmRegionSpec[] = {
2178/* columns rows colors chars-per-pixel */
2179"15 23 7 1",
2180/* Default colors, with first color transparent */
2181". c none",
2182"2 c black",
2183"3 c black",
2184"4 c black",
2185"5 c #BEBEF0",
2186"6 c #BEBEF0",
2187"7 c #BEBEF0",
2188/* pixels */
2189"...............",
2190"...............",
2191"...............",
2192"....333.444....",
2193"...3553.4774...",
2194"...3553.4774...",
2195"..35553.47774..",
2196"..35522222774..",
2197".3552666662774.",
2198".3526666666274.",
2199"355266666662774",
2200"355266666662774",
2201"355266666662774",
2202".3526666666274.",
2203".3552666662774.",
2204"..35522222774..",
2205"..35553.47774..",
2206"...3553.4774...",
2207"...3553.4774...",
2208"....333.444....",
2209"...............",
2210"...............",
2211"..............."
2212};
2213
2230{
2231 int iConfig;
2232 int iHighlight;
2233 int index;
2234 const int nSpecRows =
2235 sizeof( GlyphXpmRegionSpec )/sizeof( GlyphXpmRegionSpec[0]);
2236 const char *XmpBmp[nSpecRows];
2237
2238 // The glyphs are declared static wxIcon; so we only need
2239 // to create them once, no matter how many LabelTracks.
2240 if( mbGlyphsReady )
2241 return;
2242
2243 // We're about to tweak the basic color spec to get 12 variations.
2244 for( iConfig=0;iConfig<NUM_GLYPH_CONFIGS;iConfig++)
2245 {
2246 for( iHighlight=0;iHighlight<NUM_GLYPH_HIGHLIGHTS;iHighlight++)
2247 {
2248 index = iConfig + NUM_GLYPH_CONFIGS * iHighlight;
2249 // Copy the basic spec...
2250 memcpy( XmpBmp, GlyphXpmRegionSpec, sizeof( GlyphXpmRegionSpec ));
2251 // The highlighted region (if any) is white...
2252 if( iHighlight==1 ) XmpBmp[5]="5 c #FFFFFF";
2253 if( iHighlight==2 ) XmpBmp[6]="6 c #FFFFFF";
2254 if( iHighlight==3 ) XmpBmp[7]="7 c #FFFFFF";
2255 // For left or right arrow the other side of the glyph
2256 // is the transparent color.
2257 if( iConfig==0) { XmpBmp[3]="3 c none"; XmpBmp[5]="5 c none"; }
2258 if( iConfig==1) { XmpBmp[4]="4 c none"; XmpBmp[7]="7 c none"; }
2259 // Create the icon from the tweaked spec.
2260 mBoundaryGlyphs[index] = wxBitmap(XmpBmp);
2261 // Create the mask
2262 // SetMask takes ownership
2263 mBoundaryGlyphs[index].SetMask(safenew wxMask(mBoundaryGlyphs[index], wxColour(192, 192, 192)));
2264 }
2265 }
2266
2267 mIconWidth = mBoundaryGlyphs[0].GetWidth();
2268 mIconHeight = mBoundaryGlyphs[0].GetHeight();
2269 mTextHeight = mIconHeight; // until proved otherwise...
2270 // The icon should have an odd width so that the
2271 // line goes exactly down the middle.
2272 wxASSERT( (mIconWidth %2)==1);
2273
2274 mbGlyphsReady=true;
2275}
2276
2277#include "../../../LabelDialog.h"
2278
2280(AudacityProject &project, LabelTrack *lt, int index)
2281{
2282 const auto &formats = ProjectNumericFormats::Get( project );
2283 auto format = formats.GetSelectionFormat(),
2284 freqFormat = formats.GetFrequencySelectionFormatName();
2285 auto &tracks = TrackList::Get( project );
2286 auto &viewInfo = ViewInfo::Get( project );
2287 auto &window = GetProjectFrame(project);
2288
2289 LabelDialog dlg(&window, project, &tracks,
2290 lt, index,
2291 viewInfo,
2292 format, freqFormat);
2293#ifdef __WXGTK__
2294 dlg.Raise();
2295#endif
2296
2297 if (dlg.ShowModal() == wxID_OK) {
2299 .PushState(XO("Edited labels"), XO("Label"));
2300 }
2301}
2302
2305 const SelectedRegion& region, const wxString& initialValue, wxString& value)
2306{
2307 auto &trackFocus = TrackFocus::Get( project );
2308 auto &trackPanel = TrackPanel::Get( project );
2309 auto &viewInfo = ViewInfo::Get( project );
2310
2311 wxPoint position =
2312 trackPanel.FindTrackRect( trackFocus.Get() ).GetBottomLeft();
2313 // The start of the text in the text box will be roughly in line with the label's position
2314 // if it's a point label, or the start of its region if it's a region label.
2315 position.x += viewInfo.GetLeftOffset()
2316 + std::max(0, static_cast<int>(viewInfo.TimeToPosition(region.t0())))
2317 - 39;
2318 position.y += 2; // just below the bottom of the track
2319 position = trackPanel.ClientToScreen(position);
2320 auto &window = GetProjectFrame(project);
2321 AudacityTextEntryDialog dialog{ &window,
2322 XO("Name:"),
2323 XO("New label"),
2324 initialValue,
2325 wxOK | wxCANCEL,
2326 position };
2327
2328 // keep the dialog within Audacity's window, so that the dialog is always fully visible
2329 wxRect dialogScreenRect = dialog.GetScreenRect();
2330 wxRect projScreenRect = window.GetScreenRect();
2331 wxPoint max = projScreenRect.GetBottomRight() + wxPoint{ -dialogScreenRect.width, -dialogScreenRect.height };
2332 if (dialogScreenRect.x > max.x) {
2333 position.x = max.x;
2334 dialog.Move(position);
2335 }
2336 if (dialogScreenRect.y > max.y) {
2337 position.y = max.y;
2338 dialog.Move(position);
2339 }
2340
2341 dialog.SetInsertionPointEnd(); // because, by default, initial text is selected
2342 int status = dialog.ShowModal();
2343 if (status != wxID_CANCEL) {
2344 value = dialog.GetValue();
2345 value.Trim(true).Trim(false);
2346 }
2347
2348 return status;
2349}
2350
2353 return [](LabelTrack &track, size_t) {
2354 return std::make_shared<LabelTrackView>(
2355 track.SharedPointer<LabelTrack>());
2356 };
2357}
2358
2359std::shared_ptr<ChannelVRulerControls> LabelTrackView::DoGetVRulerControls()
2360{
2361 return
2362 std::make_shared<LabelTrackVRulerControls>( shared_from_this() );
2363}
2364
2368 return [](auto &) { return SyncLockPolicy::EndSeparator; };
2369}
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.
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetLabelTrackView)
@ OnEditSelectedLabelID
@ OnDeleteSelectedLabelID
@ OnPasteSelectedTextID
@ OnCopySelectedTextID
@ OnCutSelectedTextID
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:9
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:122
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static wxBrush uglyBrush
Definition: AColor.h:142
static wxPen labelSurroundPen
Definition: AColor.h:127
static wxBrush labelTextNormalBrush
Definition: AColor.h:119
static wxBrush labelTextEditBrush
Definition: AColor.h:120
static wxBrush labelUnselectedBrush
Definition: AColor.h:121
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:320
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:37
double getT1() const
Definition: LabelTrack.h:48
int x1
Pixel position of left hand glyph.
Definition: LabelTrack.h:83
int x
width of the text in pixels.
Definition: LabelTrack.h:82
double getT0() const
Definition: LabelTrack.h:47
wxString title
Definition: LabelTrack.h:78
int width
Text of the label.
Definition: LabelTrack.h:79
int xText
Pixel position of right hand glyph.
Definition: LabelTrack.h:84
int y
Pixel position of left hand side of text box.
Definition: LabelTrack.h:85
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:95
const LabelArray & GetLabels() const
Definition: LabelTrack.h:156
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:69
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:215
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
bool ReadBool(const wxString &key, bool defaultValue) const
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:375
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Channel &channel, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:651
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:628
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:237
wxString mTitle
Definition: LabelTrack.h:240
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.