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