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 "../../../ProjectWindow.h"
29#include "../../../ProjectWindows.h"
30#include "../../../RefreshCode.h"
31#include "SyncLock.h"
32#include "Theme.h"
33#include "../../../TrackArt.h"
34#include "../../../TrackArtist.h"
35#include "../../../TrackPanelAx.h"
36#include "../../../TrackPanel.h"
37#include "../../../TrackPanelMouseEvent.h"
38#include "UndoManager.h"
39#include "ViewInfo.h"
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 && target->GetTrack().get() == this;
854#endif
855 int i = -1; for (const auto &labelStruct : mLabels) { ++i;
856 bool highlight = false;
857#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
858 highlight = highlightTrack && target->GetLabelNum() == i;
859#endif
860
861 dc.SetBrush(mNavigationIndex == i || (pHit && pHit->mMouseOverLabel == i)
863 DrawBar(dc, labelStruct, r);
864
865 bool selected = mTextEditIndex == i;
866
867 if (selected)
868 dc.SetBrush(AColor::labelTextEditBrush);
869 else if (highlight)
870 dc.SetBrush(AColor::uglyBrush);
871 else
872 dc.SetBrush(AColor::labelTextNormalBrush);
873 DrawTextBox(dc, labelStruct, r);
874
875 dc.SetBrush(AColor::labelTextNormalBrush);
876 }
877 }
878
879 // Draw highlights
881 {
882 int xpos1, xpos2;
883 CalcHighlightXs(&xpos1, &xpos2);
884 DrawHighlight(dc, mLabels[mTextEditIndex],
885 xpos1, xpos2, dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent);
886 }
887
888 // Draw the text and the label boxes.
889 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
890 if(mTextEditIndex == i )
891 dc.SetBrush(AColor::labelTextEditBrush);
892 DrawText( dc, labelStruct, r );
893 if(mTextEditIndex == i )
894 dc.SetBrush(AColor::labelTextNormalBrush);
895 }}
896
897 // Draw the cursor, if there is one.
899 {
900 const auto &labelStruct = mLabels[mTextEditIndex];
901 int xPos = labelStruct.xText;
902
903 if( mCurrentCursorPos > 0)
904 {
905 // Calculate the width of the substring and add it to Xpos
906 int partWidth;
907 dc.GetTextExtent(labelStruct.title.Left(mCurrentCursorPos), &partWidth, NULL);
908 xPos += partWidth;
909 }
910
911 wxPen currentPen = dc.GetPen();
912 const int CursorWidth=2;
913 currentPen.SetWidth(CursorWidth);
914 const auto top = labelStruct.y - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - mFontHeight) / 2 + TextFrameYOffset;
915 AColor::Line(dc,
916 xPos-1, top,
917 xPos-1, top + mFontHeight);
918 currentPen.SetWidth(1);
919 }
920}
921
924 const wxRect &rect, unsigned iPass )
925{
926 if ( iPass == TrackArtist::PassTracks )
927 Draw( context, rect );
928 CommonChannelView::Draw(context, rect, iPass);
929}
930
933int LabelTrackView::FindCursorPosition(int labelIndex, wxCoord xPos)
934{
935 int result = -1;
936 wxMemoryDC dc;
937 if(msFont.Ok())
938 dc.SetFont(msFont);
939
940 // A bool indicator to see if set the cursor position or not
941 bool finished = false;
942 int charIndex = 1;
943 int partWidth;
944 int oneWidth;
945 double bound;
946 wxString subString;
947
948 const auto pTrack = FindLabelTrack();
949 const auto &mLabels = pTrack->GetLabels();
950 const auto &labelStruct = mLabels[labelIndex];
951 const auto &title = labelStruct.title;
952 const int length = title.length();
953 while (!finished && (charIndex < length + 1))
954 {
955 int unichar = (int)title.at( charIndex-1 );
956 if( (0xDC00 <= unichar) && (unichar <= 0xDFFF)){
957 charIndex++;
958 continue;
959 }
960 subString = title.Left(charIndex);
961 // Get the width of substring
962 dc.GetTextExtent(subString, &partWidth, NULL);
963
964 // Get the width of the last character
965 dc.GetTextExtent(subString.Right(1), &oneWidth, NULL);
966 bound = labelStruct.xText + partWidth - oneWidth * 0.5;
967
968 if (xPos <= bound)
969 {
970 // Found
971 result = charIndex - 1;
972 finished = true;
973 }
974 else
975 {
976 // Advance
977 charIndex++;
978 }
979 }
980 if (!finished)
981 // Cursor should be in the last position
982 result = length;
983
984 return result;
985}
986
988{
989 mCurrentCursorPos = pos;
990}
991void LabelTrackView::SetTextSelection(int labelIndex, int start, int end)
992{
993 mTextEditIndex = labelIndex;
994 mInitialCursorPos = start;
996}
998{
1000 return mTextEditIndex;
1001 return -1;
1002}
1004{
1005 mTextEditIndex = -1;
1008}
1010{
1011 mNavigationIndex = index;
1012}
1014{
1016 return mNavigationIndex;
1017 return -1;
1018}
1019
1021{
1022 int charDescent;
1023 int charLeading;
1024
1025 // Calculate the width of the substring and add it to Xpos
1026 dc.GetTextExtent(wxT("(Test String)|[yp]"), NULL, &mFontHeight, &charDescent, &charLeading);
1027
1028 // The cursor will have height charHeight. We don't include the descender as
1029 // part of the height because for phonetic fonts this leads to cursors which are
1030 // too tall. We don't include leading either - it is usually 0.
1031 // To make up for ignoring the descender height, we add one pixel above and below
1032 // using CursorExtraHeight so that the cursor is just a little taller than the
1033 // body of the characters.
1034 const int CursorExtraHeight=2;
1035 mFontHeight += CursorExtraHeight - (charLeading+charDescent);
1036}
1037
1039{
1040 if (index == -1)
1041 return false;
1042 // may make delayed update of mutable mSelIndex after track selection change
1043 auto track = FindLabelTrack();
1044 if (track->GetSelected() || (TrackFocus::Get(project).Get() == track.get()))
1045 return index >= 0 && index < static_cast<int>(track->GetLabels().size());
1046 return false;
1047}
1048
1050{
1052}
1053
1057{
1058 if (!IsTextSelected( project ))
1059 return false;
1060
1061 const auto pTrack = FindLabelTrack();
1062 const auto &mLabels = pTrack->GetLabels();
1063
1064 wxString left, right;
1065 auto labelStruct = mLabels[mTextEditIndex];
1066 auto &text = labelStruct.title;
1067
1068 if (!mTextEditIndex.IsModified()) {
1069 mUndoLabel = text;
1070 }
1071
1072 int init = mInitialCursorPos;
1073 int cur = mCurrentCursorPos;
1074 if (init > cur)
1075 std::swap(init, cur);
1076
1077 // data for cutting
1078 wxString data = text.Mid(init, cur - init);
1079
1080 // get left-remaining text
1081 if (init > 0)
1082 left = text.Left(init);
1083
1084 // get right-remaining text
1085 if (cur < (int)text.length())
1086 right = text.Mid(cur);
1087
1088 // set title to the combination of the two remainders
1089 text = left + right;
1090
1091 pTrack->SetLabel( mTextEditIndex, labelStruct );
1092
1093 // copy data onto clipboard
1094 if (wxTheClipboard->Open()) {
1095 // Clipboard owns the data you give it
1096 wxTheClipboard->SetData(safenew wxTextDataObject(data));
1097 wxTheClipboard->Close();
1098 }
1099
1100 // set cursor positions
1101 mInitialCursorPos = mCurrentCursorPos = left.length();
1102
1104 return true;
1105}
1106
1110{
1111 if (!IsTextSelected(project))
1112 return false;
1113
1114 const auto pTrack = FindLabelTrack();
1115 const auto &mLabels = pTrack->GetLabels();
1116
1117 const auto &labelStruct = mLabels[mTextEditIndex];
1118
1119 int init = mInitialCursorPos;
1120 int cur = mCurrentCursorPos;
1121 if (init > cur)
1122 std::swap(init, cur);
1123
1124 if (init == cur)
1125 return false;
1126
1127 // data for copying
1128 wxString data = labelStruct.title.Mid(init, cur-init);
1129
1130 // copy the data on clipboard
1131 if (wxTheClipboard->Open()) {
1132 // Clipboard owns the data you give it
1133 wxTheClipboard->SetData(safenew wxTextDataObject(data));
1134 wxTheClipboard->Close();
1135 }
1136
1137 return true;
1138}
1139
1140// PRL: should this set other fields of the label selection?
1144 AudacityProject &project, double sel0, double sel1 )
1145{
1146 const auto pTrack = FindLabelTrack();
1147
1150
1151 wxString text, left, right;
1152
1153 // if text data is available
1154 if (IsTextClipSupported()) {
1155 if (wxTheClipboard->Open()) {
1156 wxTextDataObject data;
1157 wxTheClipboard->GetData(data);
1158 wxTheClipboard->Close();
1159 text = data.GetText();
1160 }
1161
1162 if (!mTextEditIndex.IsModified()) {
1163 mUndoLabel = text;
1164 }
1165
1166 // Convert control characters to blanks
1167 for (int i = 0; i < (int)text.length(); i++) {
1168 if (wxIscntrl(text[i])) {
1169 text[i] = wxT(' ');
1170 }
1171 }
1172 }
1173
1174 const auto &mLabels = pTrack->GetLabels();
1175 auto labelStruct = mLabels[mTextEditIndex];
1176 auto &title = labelStruct.title;
1177 int cur = mCurrentCursorPos, init = mInitialCursorPos;
1178 if (init > cur)
1179 std::swap(init, cur);
1180 left = title.Left(init);
1181 if (cur < (int)title.length())
1182 right = title.Mid(cur);
1183
1184 title = left + text + right;
1185
1186 pTrack->SetLabel(mTextEditIndex, labelStruct );
1187
1188 mInitialCursorPos = mCurrentCursorPos = left.length() + text.length();
1189
1191 return true;
1192}
1193
1195{
1197 return false;
1198
1199 const auto pTrack = FindLabelTrack();
1200
1201 const auto& mLabels = pTrack->GetLabels();
1202 auto labelStruct = mLabels[mTextEditIndex];
1203 auto& title = labelStruct.title;
1204
1206 mCurrentCursorPos = title.Length();
1207
1208 return true;
1209}
1210
1213{
1214 return wxTheClipboard->IsSupported(wxDF_UNICODETEXT);
1215}
1216
1221 const LabelTrack &track, LabelTrackHit &hit, int x, int y)
1222{
1223 //Determine the NEW selection.
1224 int result=0;
1225 const int d1=10; //distance in pixels, used for have we hit drag handle.
1226 const int d2=5; //distance in pixels, used for have we hit drag handle center.
1227
1228 //If not over a label, reset it
1229 hit.mMouseOverLabelLeft = -1;
1230 hit.mMouseOverLabelRight = -1;
1231 hit.mMouseOverLabel = -1;
1232 hit.mEdge = 0;
1233
1234 const auto pTrack = &track;
1235 const auto &mLabels = pTrack->GetLabels();
1236 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
1237 // give text box better priority for selecting
1238 // reset selection state
1239 if (OverTextBox(&labelStruct, x, y))
1240 {
1241 result = 0;
1242 hit.mMouseOverLabel = -1;
1243 hit.mMouseOverLabelLeft = -1;
1244 hit.mMouseOverLabelRight = -1;
1245 break;
1246 }
1247
1248 //over left or right selection bound
1249 //Check right bound first, since it is drawn after left bound,
1250 //so give it precedence for matching/highlighting.
1251 if( abs(labelStruct.y - (y - (mTextHeight+3)/2)) < d1 &&
1252 abs(labelStruct.x1 - d2 -x) < d1)
1253 {
1254 hit.mMouseOverLabelRight = i;
1255 if(abs(labelStruct.x1 - x) < d2 )
1256 {
1257 result |= 4;
1258 // If left and right co-incident at this resolution, then we drag both.
1259 // We were more stringent about co-incidence here in the past.
1260 if( abs(labelStruct.x1-labelStruct.x) < 5.0 )
1261 {
1262 result |=1;
1263 hit.mMouseOverLabelLeft = i;
1264 }
1265 }
1266 result |= 2;
1267 }
1268 // Use else-if here rather than else to avoid detecting left and right
1269 // of the same label.
1270 else if( abs(labelStruct.y - (y - (mTextHeight+3)/2)) < d1 &&
1271 abs(labelStruct.x + d2 - x) < d1 )
1272 {
1273 hit.mMouseOverLabelLeft = i;
1274 if(abs(labelStruct.x - x) < d2 )
1275 result |= 4;
1276 result |= 1;
1277 }
1278 else if (x >= labelStruct.x && x <= labelStruct.x1 &&
1279 abs(y - (labelStruct.y + mTextHeight / 2)) < d1)
1280 {
1281 hit.mMouseOverLabel = i;
1282 result = 3;
1283 }
1284 }}
1285 hit.mEdge = result;
1286}
1287
1288int LabelTrackView::OverATextBox( const LabelTrack &track, int xx, int yy )
1289{
1290 const auto pTrack = &track;
1291 const auto &mLabels = pTrack->GetLabels();
1292 for (int nn = (int)mLabels.size(); nn--;) {
1293 const auto &labelStruct = mLabels[nn];
1294 if ( OverTextBox( &labelStruct, xx, yy ) )
1295 return nn;
1296 }
1297
1298 return -1;
1299}
1300
1301// return true if the mouse is over text box, false otherwise
1302bool LabelTrackView::OverTextBox(const LabelStruct *pLabel, int x, int y)
1303{
1304 if( (pLabel->xText-(mIconWidth/2) < x) &&
1305 (x<pLabel->xText+pLabel->width+(mIconWidth/2)) &&
1306 (abs(pLabel->y-y)<mIconHeight/2))
1307 {
1308 return true;
1309 }
1310 return false;
1311}
1312
1314static bool IsGoodLabelFirstKey(const wxKeyEvent & evt)
1315{
1316 int keyCode = evt.GetKeyCode();
1317 return (keyCode < WXK_START
1318 && keyCode != WXK_SPACE && keyCode != WXK_DELETE && keyCode != WXK_RETURN) ||
1319 (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
1320 (keyCode >= WXK_NUMPAD_EQUAL && keyCode <= WXK_NUMPAD_DIVIDE) ||
1321#if defined(__WXMAC__)
1322 (keyCode > WXK_RAW_CONTROL) ||
1323#endif
1324 (keyCode > WXK_WINDOWS_MENU);
1325}
1326
1328static bool IsGoodLabelEditKey(const wxKeyEvent & evt)
1329{
1330 int keyCode = evt.GetKeyCode();
1331
1332 // Accept everything outside of WXK_START through WXK_COMMAND, plus the keys
1333 // within that range that are usually printable, plus the ones we use for
1334 // keyboard navigation.
1335 return keyCode < WXK_START ||
1336 (keyCode >= WXK_END && keyCode < WXK_UP) ||
1337 (keyCode == WXK_RIGHT) ||
1338 (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_DIVIDE) ||
1339 (keyCode >= WXK_NUMPAD_SPACE && keyCode <= WXK_NUMPAD_ENTER) ||
1340 (keyCode >= WXK_NUMPAD_HOME && keyCode <= WXK_NUMPAD_END) ||
1341 (keyCode >= WXK_NUMPAD_DELETE && keyCode <= WXK_NUMPAD_DIVIDE) ||
1342#if defined(__WXMAC__)
1343 (keyCode > WXK_RAW_CONTROL) ||
1344#endif
1345 (keyCode > WXK_WINDOWS_MENU);
1346}
1347
1348// Check for keys that we will process
1350 AudacityProject &project, wxKeyEvent & event )
1351{
1352 int mods = event.GetModifiers();
1353 auto code = event.GetKeyCode();
1354 const auto pTrack = FindLabelTrack();
1355 const auto& mLabels = pTrack->GetLabels();
1356
1357 // Allow hardcoded Ctrl+F2 for renaming the selected label,
1358 // if we have any labels
1359 if (code == WXK_F2 && mods == wxMOD_CONTROL && !mLabels.empty()) {
1360 return true;
1361 }
1362
1363 // Check for modifiers and only allow shift
1364 if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1365 return false;
1366 }
1367
1368 // Always capture the navigation keys, if we have any labels
1369 if ((code == WXK_TAB || code == WXK_NUMPAD_TAB) &&
1370 !mLabels.empty())
1371 return true;
1372
1374 if (IsGoodLabelEditKey(event)) {
1375 return true;
1376 }
1377 }
1378 else {
1379 bool typeToCreateLabel;
1380 gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, false);
1381 if (IsGoodLabelFirstKey(event) && typeToCreateLabel) {
1382
1383
1384// The commented out code can prevent label creation, causing bug 1551
1385// We should only be in DoCaptureKey IF this label track has focus,
1386// and in that case creating a Label is the expected/intended thing.
1387#if 0
1388 // If we're playing, don't capture if the selection is the same as the
1389 // playback region (this helps prevent label track creation from
1390 // stealing unmodified kbd. shortcuts)
1391 auto gAudioIO = AudioIOBase::Get();
1392 if (pProj->GetAudioIOToken() > 0 &&
1393 gAudioIO->IsStreamActive(pProj->GetAudioIOToken()))
1394 {
1395 double t0, t1;
1396 pProj->GetPlayRegion(&t0, &t1);
1397 if (pProj->mViewInfo.selectedRegion.t0() == t0 &&
1398 pProj->mViewInfo.selectedRegion.t1() == t1) {
1399 return false;
1400 }
1401 }
1402#endif
1403
1404 // If there's a label there already don't capture
1405 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1406 if( GetLabelIndex(selectedRegion.t0(),
1407 selectedRegion.t1()) != wxNOT_FOUND ) {
1408 return false;
1409 }
1410
1411 return true;
1412 }
1413 }
1414
1415 return false;
1416}
1417
1419 wxKeyEvent & event, ViewInfo &, wxWindow *, AudacityProject *project )
1420{
1421 event.Skip(!DoCaptureKey( *project, event ));
1423}
1424
1426 wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *WXUNUSED(pParent),
1428{
1429 double bkpSel0 = viewInfo.selectedRegion.t0(),
1430 bkpSel1 = viewInfo.selectedRegion.t1();
1431
1433 const auto pTrack = FindLabelTrack();
1434 const auto &mLabels = pTrack->GetLabels();
1435 auto labelStruct = mLabels[mTextEditIndex];
1436 auto &title = labelStruct.title;
1437 mUndoLabel = title;
1438 }
1439
1440 // Pass keystroke to labeltrack's handler and add to history if any
1441 // updates were done
1442 if (DoKeyDown( *project, viewInfo.selectedRegion, event )) {
1443 ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
1444 XO("Label Edit"),
1446
1448 }
1449
1450 if (!mTextEditIndex.IsModified()) {
1451 mUndoLabel.clear();
1452 }
1453
1454 // Make sure caret is in view
1455 int x;
1456 if (CalcCursorX( *project, &x ))
1457 ProjectWindow::Get( *project ).ScrollIntoView(x);
1458
1459 // If selection modified, refresh
1460 // Otherwise, refresh track display if the keystroke was handled
1461 if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1462 bkpSel1 != viewInfo.selectedRegion.t1())
1464 else if (!event.GetSkipped())
1466
1468}
1469
1471 wxKeyEvent & event, ViewInfo &viewInfo, wxWindow *, AudacityProject *project)
1472{
1473 double bkpSel0 = viewInfo.selectedRegion.t0(),
1474 bkpSel1 = viewInfo.selectedRegion.t1();
1475 // Pass keystroke to labeltrack's handler and add to history if any
1476 // updates were done
1477
1479 const auto pTrack = FindLabelTrack();
1480 const auto &mLabels = pTrack->GetLabels();
1481 auto labelStruct = mLabels[mTextEditIndex];
1482 auto &title = labelStruct.title;
1483 mUndoLabel = title;
1484 }
1485
1486 if (DoChar( *project, viewInfo.selectedRegion, event )) {
1487 ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
1488 XO("Label Edit"),
1490
1492 }
1493
1494 if (!mTextEditIndex.IsModified()) {
1495 mUndoLabel.clear();
1496 }
1497
1498 // If selection modified, refresh
1499 // Otherwise, refresh track display if the keystroke was handled
1500 if (bkpSel0 != viewInfo.selectedRegion.t0() ||
1501 bkpSel1 != viewInfo.selectedRegion.t1())
1503 else if (!event.GetSkipped())
1505
1507}
1508
1511 AudacityProject &project, NotifyingSelectedRegion &newSel, wxKeyEvent & event)
1512{
1513 // Only track true changes to the label
1514 bool updated = false;
1515
1516 // Cache the keycode
1517 int keyCode = event.GetKeyCode();
1518 const int mods = event.GetModifiers();
1519
1520 // Check for modifiers and only allow shift
1521 // except in the case of Ctrl + F2, so hardcoded Ctrl+F2 can
1522 // be used for renaming a label
1523 if ((keyCode != WXK_F2 && mods != wxMOD_NONE && mods != wxMOD_SHIFT)
1524 || (keyCode == WXK_F2 && mods != wxMOD_CONTROL)) {
1525 event.Skip();
1526 return updated;
1527 }
1528
1529 // All editing keys are only active if we're currently editing a label
1530 const auto pTrack = FindLabelTrack();
1531 const auto &mLabels = pTrack->GetLabels();
1533 // Do label text changes
1534 auto labelStruct = mLabels[mTextEditIndex];
1535 auto &title = labelStruct.title;
1536 wxUniChar wchar;
1537 bool more=true;
1538
1539 switch (keyCode) {
1540
1541 case WXK_BACK:
1542 {
1543 int len = title.length();
1544
1545 //IF the label is not blank THEN get rid of a letter or letters according to cursor position
1546 if (len > 0)
1547 {
1548 // IF there are some highlighted letters, THEN DELETE them
1551 else
1552 {
1553 // DELETE one codepoint leftwards
1554 while ((mCurrentCursorPos > 0) && more) {
1555 wchar = title.at( mCurrentCursorPos-1 );
1556 title.erase(mCurrentCursorPos-1, 1);
1558 if( ((int)wchar > 0xDFFF) || ((int)wchar <0xDC00)){
1559 pTrack->SetLabel(mTextEditIndex, labelStruct);
1560 more = false;
1561 }
1562 }
1563 }
1564 }
1565 else
1566 {
1567 // ELSE no text in text box, so DELETE whole label.
1568 pTrack->DeleteLabel(mTextEditIndex);
1570 }
1572 updated = true;
1573 }
1574 break;
1575
1576 case WXK_DELETE:
1577 case WXK_NUMPAD_DELETE:
1578 {
1579 int len = title.length();
1580
1581 //If the label is not blank get rid of a letter according to cursor position
1582 if (len > 0)
1583 {
1584 // if there are some highlighted letters, DELETE them
1587 else
1588 {
1589 // DELETE one codepoint rightwards
1590 while ((mCurrentCursorPos < len) && more) {
1591 wchar = title.at( mCurrentCursorPos );
1592 title.erase(mCurrentCursorPos, 1);
1593 if( ((int)wchar > 0xDBFF) || ((int)wchar <0xD800)){
1594 pTrack->SetLabel(mTextEditIndex, labelStruct);
1595 more = false;
1596 }
1597 }
1598 }
1599 }
1600 else
1601 {
1602 // DELETE whole label if no text in text box
1603 pTrack->DeleteLabel(mTextEditIndex);
1605 }
1607 updated = true;
1608 }
1609 break;
1610
1611 case WXK_HOME:
1612 case WXK_NUMPAD_HOME:
1613 // Move cursor to beginning of label
1615 if (mods == wxMOD_SHIFT)
1616 ;
1617 else
1619 break;
1620
1621 case WXK_END:
1622 case WXK_NUMPAD_END:
1623 // Move cursor to end of label
1624 mCurrentCursorPos = (int)title.length();
1625 if (mods == wxMOD_SHIFT)
1626 ;
1627 else
1629 break;
1630
1631 case WXK_LEFT:
1632 case WXK_NUMPAD_LEFT:
1633 // Moving cursor left
1634 if (mods != wxMOD_SHIFT && mCurrentCursorPos != mInitialCursorPos)
1635 //put cursor to the left edge of selection
1638 else
1639 {
1640 while ((mCurrentCursorPos > 0) && more) {
1641 wchar = title.at(mCurrentCursorPos - 1);
1642 more = !(((int)wchar > 0xDFFF) || ((int)wchar < 0xDC00));
1643
1645 }
1646 if (mods != wxMOD_SHIFT)
1648 }
1649
1650 break;
1651
1652 case WXK_RIGHT:
1653 case WXK_NUMPAD_RIGHT:
1654 // Moving cursor right
1655 if(mods != wxMOD_SHIFT && mCurrentCursorPos != mInitialCursorPos)
1656 //put cursor to the right edge of selection
1659 else
1660 {
1661 while ((mCurrentCursorPos < (int)title.length()) && more) {
1662 wchar = title.at(mCurrentCursorPos);
1663 more = !(((int)wchar > 0xDBFF) || ((int)wchar < 0xD800));
1664
1666 }
1667 if (mods != wxMOD_SHIFT)
1669 }
1670 break;
1671
1672 case WXK_ESCAPE:
1673 if (mTextEditIndex.IsModified()) {
1674 title = mUndoLabel;
1675 pTrack->SetLabel(mTextEditIndex, labelStruct);
1676
1677 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
1678 XO("Label Edit"),
1680 }
1681
1682 case WXK_RETURN:
1683 case WXK_NUMPAD_ENTER:
1684 case WXK_TAB:
1685 if (mRestoreFocus >= 0) {
1686 auto track = *TrackList::Get(project).Any()
1687 .begin().advance(mRestoreFocus);
1688 if (track)
1689 TrackFocus::Get( project ).Set(track);
1690 mRestoreFocus = -2;
1691 }
1694 break;
1695 case '\x10': // OSX
1696 case WXK_MENU:
1697 case WXK_WINDOWS_MENU:
1699 break;
1700
1701 default:
1702 if (!IsGoodLabelEditKey(event)) {
1703 event.Skip();
1704 }
1705 break;
1706 }
1707 }
1708 else
1709 {
1710 // Do navigation
1711 switch (keyCode) {
1712
1713 case WXK_ESCAPE:
1714 mNavigationIndex = -1;
1715 break;
1716 case WXK_TAB:
1717 case WXK_NUMPAD_TAB:
1718 if (!mLabels.empty()) {
1719 int len = (int) mLabels.size();
1720 // The case where the start of selection is the same as the
1721 // start of a label is handled separately so that if some labels
1722 // have the same start time, all labels are navigated.
1724 && mLabels[mNavigationIndex].getT0() == newSel.t0())
1725 {
1726 if (event.ShiftDown()) {
1728 }
1729 else {
1731 }
1732 mNavigationIndex = (mNavigationIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
1733 }
1734 else
1735 {
1736 if (event.ShiftDown()) {
1737 //search for the first label starting from the end (and before selection)
1738 mNavigationIndex = len - 1;
1739 if (newSel.t0() > mLabels[0].getT0()) {
1740 while (mNavigationIndex >= 0 &&
1741 mLabels[mNavigationIndex].getT0() > newSel.t0()) {
1743 }
1744 }
1745 }
1746 else {
1747 //search for the first label starting from the beginning (and after selection)
1748 mNavigationIndex = 0;
1749 if (newSel.t0() < mLabels[len - 1].getT0()) {
1750 while (mNavigationIndex < len &&
1751 mLabels[mNavigationIndex].getT0() < newSel.t0()) {
1753 }
1754 }
1755 }
1756 }
1757
1758 if (mNavigationIndex >= 0 && mNavigationIndex < len) {
1759 const auto &labelStruct = mLabels[mNavigationIndex];
1760 mCurrentCursorPos = labelStruct.title.length();
1762 //Set the selection region to be equal to the selection bounds of the tabbed-to label.
1763 newSel = labelStruct.selectedRegion;
1764 ProjectWindow::Get(project).ScrollIntoView(labelStruct.selectedRegion.t0());
1765 // message for screen reader
1766 /* i18n-hint:
1767 String is replaced by the name of a label,
1768 first number gives the position of that label in a sequence
1769 of labels,
1770 and the last number is the total number of labels in the sequence.
1771 */
1772 auto message = XO("%s %d of %d")
1773 .Format(labelStruct.title, mNavigationIndex + 1, pTrack->GetNumLabels());
1774 TrackFocus::Get(project).MessageForScreenReader(message);
1775 }
1776 else {
1777 mNavigationIndex = -1;
1778 }
1779 }
1780 break;
1781 case WXK_F2: // Must be Ctrl + F2 to have reached here
1782 // Hardcoded Ctrl+F2 activates editing of the label
1783 // pointed to by mNavigationIndex (if valid)
1786 }
1787 break;
1788 default:
1789 if (!IsGoodLabelFirstKey(event)) {
1790 event.Skip();
1791 }
1792 break;
1793 }
1794 }
1795
1796 return updated;
1797}
1798
1803 wxKeyEvent & event)
1804{
1805 // Check for modifiers and only allow shift.
1806 //
1807 // We still need to check this or we will eat the top level menu accelerators
1808 // on Windows if our capture or key down handlers skipped the event.
1809 const int mods = event.GetModifiers();
1810 if (mods != wxMOD_NONE && mods != wxMOD_SHIFT) {
1811 event.Skip();
1812 return false;
1813 }
1814
1815 // Only track true changes to the label
1816 //bool updated = false;
1817
1818 // Cache the character
1819 wxChar charCode = event.GetUnicodeKey();
1820
1821 // Skip if it's not a valid unicode character or a control character
1822 if (charCode == 0 || wxIscntrl(charCode)) {
1823 event.Skip();
1824 return false;
1825 }
1826
1827 // If we've reached this point and aren't currently editing, add NEW label
1828 const auto pTrack = FindLabelTrack();
1830 // Don't create a NEW label for a space
1831 if (wxIsspace(charCode)) {
1832 event.Skip();
1833 return false;
1834 }
1835 bool useDialog;
1836 gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false);
1837 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1838 if (useDialog) {
1839 wxString title;
1841 project, selectedRegion, charCode, title) ==
1842 wxID_CANCEL) {
1843 return false;
1844 }
1845 pTrack->SetSelected(true);
1846 pTrack->AddLabel(selectedRegion, title);
1848 .PushState(XO("Added label"), XO("Label"));
1849 return false;
1850 }
1851 else {
1852 pTrack->SetSelected(true);
1853 AddLabel( selectedRegion );
1855 .PushState(XO("Added label"), XO("Label"));
1856 }
1857 }
1858
1860 return false;
1861
1862 //
1863 // Now we are definitely in a label; append the incoming character
1864 //
1865
1866 // Test if cursor is in the end of string or not
1869
1870 const auto& mLabels = pTrack->GetLabels();
1871 auto labelStruct = mLabels[mTextEditIndex];
1872 auto& title = labelStruct.title;
1873
1874 if (mCurrentCursorPos < (int)title.length()) {
1875 // Get substring on the righthand side of cursor
1876 wxString rightPart = title.Mid(mCurrentCursorPos);
1877 // Set title to substring on the lefthand side of cursor
1879 //append charcode
1880 title += charCode;
1881 //append the right part substring
1882 title += rightPart;
1883 }
1884 else
1885 //append charCode
1886 title += charCode;
1887
1888 pTrack->SetLabel(mTextEditIndex, labelStruct );
1889
1890 //moving cursor position forward
1892
1893 return true;
1894}
1895
1896enum
1897{
1898 OnCutSelectedTextID = 1, // OSX doesn't like a 0 menu id
1903};
1904
1906{
1907 wxWindow *parent = wxWindow::FindFocus();
1908
1909 // Bug 2044. parent can be nullptr after a context switch.
1910 if( !parent )
1911 parent = &GetProjectFrame( project );
1912
1913 if( parent )
1914 {
1915 wxMenu menu;
1916 menu.Bind(wxEVT_MENU,
1917 [this, &project]( wxCommandEvent &event ){
1918 OnContextMenu( project, event ); }
1919 );
1920
1921 menu.Append(OnCutSelectedTextID, _("Cu&t Label text"));
1922 menu.Append(OnCopySelectedTextID, _("&Copy Label text"));
1923 menu.Append(OnPasteSelectedTextID, _("&Paste"));
1924 menu.Append(OnDeleteSelectedLabelID, _("&Delete Label"));
1925 menu.Append(OnEditSelectedLabelID, _("&Edit Label..."));
1926
1930 menu.Enable(OnDeleteSelectedLabelID, true);
1931 menu.Enable(OnEditSelectedLabelID, true);
1932
1934 return;
1935 }
1936
1937 const auto pTrack = FindLabelTrack();
1938 const LabelStruct *ls = pTrack->GetLabel(mTextEditIndex);
1939
1940 wxClientDC dc(parent);
1941
1942 if (msFont.Ok())
1943 {
1944 dc.SetFont(msFont);
1945 }
1946
1947 int x = 0;
1948 bool success = CalcCursorX( project, &x );
1949 wxASSERT(success);
1950 static_cast<void>(success); // Suppress unused variable warning if debug mode is disabled
1951
1952 // Bug #2571: Hackage alert! For some reason wxGTK does not like
1953 // displaying the LabelDialog from within the PopupMenu "context".
1954 // So, workaround it by editing the label AFTER the popup menu is
1955 // closed. It's really ugly, but it works. :-(
1956 mEditIndex = -1;
1957 BasicMenu::Handle{ &menu }.Popup(
1958 wxWidgetsWindowPlacement{ parent },
1959 { x, ls->y + (mIconHeight / 2) - 1 }
1960 );
1961 if (mEditIndex >= 0)
1962 {
1964 }
1965 }
1966}
1967
1969 AudacityProject &project, wxCommandEvent & evt )
1970{
1971 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1972
1973 switch (evt.GetId())
1974 {
1977 if (CutSelectedText( project ))
1978 {
1979 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
1980 XO("Label Edit"),
1982 }
1983 break;
1984
1988 break;
1989
1993 project, selectedRegion.t0(), selectedRegion.t1() ))
1994 {
1995 ProjectHistory::Get( project ).PushState(XO("Modified Label"),
1996 XO("Label Edit"),
1998 }
1999 break;
2000
2004 {
2005 const auto pTrack = FindLabelTrack();
2006 pTrack->DeleteLabel(mTextEditIndex);
2007 ProjectHistory::Get( project ).PushState(XO("Deleted Label"),
2008 XO("Label Edit"),
2010 }
2011 }
2012 break;
2013
2014 case OnEditSelectedLabelID: {
2015 // Bug #2571: See above
2018 }
2019 break;
2020 }
2021}
2022
2024{
2025 wxString left, right;
2026
2027 int init = mInitialCursorPos;
2028 int cur = mCurrentCursorPos;
2029 if (init > cur)
2030 std::swap(init, cur);
2031
2032 const auto pTrack = FindLabelTrack();
2033 const auto &mLabels = pTrack->GetLabels();
2034 auto labelStruct = mLabels[mTextEditIndex];
2035 auto &title = labelStruct.title;
2036
2037 if (init > 0)
2038 left = title.Left(init);
2039
2040 if (cur < (int)title.length())
2041 right = title.Mid(cur);
2042
2043 title = left + right;
2044 pTrack->SetLabel( mTextEditIndex, labelStruct );
2045 mInitialCursorPos = mCurrentCursorPos = left.length();
2046}
2047/*
2048bool LabelTrackView::HasSelectedLabel( AudacityProject &project ) const
2049{
2050 const auto selIndex = GetSelectionIndex( project );
2051 return (selIndex >= 0 &&
2052 selIndex < (int)FindLabelTrack()->GetLabels().size());
2053}*/
2054
2055int LabelTrackView::GetLabelIndex(double t, double t1)
2056{
2057 //We'd have liked to have times in terms of samples,
2058 //because then we're doing an intrger comparison.
2059 //Never mind. Instead we look for near enough.
2060 //This level of (in)accuracy is only a problem if we
2061 //deal with sounds in the MHz range.
2062 const double delta = 1.0e-7;
2063 const auto pTrack = FindLabelTrack();
2064 const auto &mLabels = pTrack->GetLabels();
2065 { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
2066 if( fabs( labelStruct.getT0() - t ) > delta )
2067 continue;
2068 if( fabs( labelStruct.getT1() - t1 ) > delta )
2069 continue;
2070 return i;
2071 }}
2072
2073 return wxNOT_FOUND;
2074}
2075
2076
2077// restoreFocus of -1 is the default, and sets the focus to this label.
2078// restoreFocus of -2 or other value leaves the focus unchanged.
2079// restoreFocus >= 0 will later cause focus to move to that track (counting
2080// tracks, not channels)
2082 const wxString &title, int restoreFocus)
2083{
2084 const auto pTrack = FindLabelTrack();
2085 mRestoreFocus = restoreFocus;
2086 auto pos = pTrack->AddLabel( selectedRegion, title );
2087 return pos;
2088}
2089
2091{
2092 if ( e.mpTrack.lock() != FindTrack() )
2093 return;
2094
2095 const auto &title = e.mTitle;
2096 const auto pos = e.mPresentPosition;
2097
2099
2100 // restoreFocus is -2 e.g. from Nyquist label creation, when we should not
2101 // even lose the focus and open the label to edit in the first place.
2102 // -1 means we don't need to restore it to anywhere.
2103 // 0 or above is the track to restore to after editing the label is complete.
2104 if( mRestoreFocus >= -1 )
2105 mTextEditIndex = pos;
2106
2107 if( mRestoreFocus < 0 )
2108 mRestoreFocus = -2;
2109}
2110
2112{
2113 if ( e.mpTrack.lock() != FindTrack() )
2114 return;
2115
2116 auto index = e.mFormerPosition;
2117
2118 // IF we've deleted the selected label
2119 // THEN set no label selected.
2120 if (mTextEditIndex == index)
2122
2123 // IF we removed a label before the selected label
2124 // THEN the NEW selected label number is one less.
2125 else if( index < mTextEditIndex)
2126 --mTextEditIndex;//NB: Keep cursor selection region
2127}
2128
2130{
2131 if ( e.mpTrack.lock() != FindTrack() )
2132 return;
2133
2134 auto former = e.mFormerPosition;
2135 auto present = e.mPresentPosition;
2136
2137 auto fix = [&](Index& index) {
2138 if (index == former)
2139 index = present;
2140 else if (former < index && index <= present)
2141 --index;
2142 else if (former > index && index >= present)
2143 ++index;
2144 };
2145 fix(mNavigationIndex);
2146 fix(mTextEditIndex);
2147}
2148
2150{
2151 if ( e.mpTrack.lock() != FindTrack() )
2152 return;
2153
2154 if (!FindTrack()->GetSelected())
2155 {
2158 }
2159}
2160
2162{
2163 return theTheme.Bitmap( i + bmpLabelGlyph0);
2164}
2165
2166// This one XPM spec is used to generate a number of
2167// different wxIcons.
2168/* XPM */
2169static const char *const GlyphXpmRegionSpec[] = {
2170/* columns rows colors chars-per-pixel */
2171"15 23 7 1",
2172/* Default colors, with first color transparent */
2173". c none",
2174"2 c black",
2175"3 c black",
2176"4 c black",
2177"5 c #BEBEF0",
2178"6 c #BEBEF0",
2179"7 c #BEBEF0",
2180/* pixels */
2181"...............",
2182"...............",
2183"...............",
2184"....333.444....",
2185"...3553.4774...",
2186"...3553.4774...",
2187"..35553.47774..",
2188"..35522222774..",
2189".3552666662774.",
2190".3526666666274.",
2191"355266666662774",
2192"355266666662774",
2193"355266666662774",
2194".3526666666274.",
2195".3552666662774.",
2196"..35522222774..",
2197"..35553.47774..",
2198"...3553.4774...",
2199"...3553.4774...",
2200"....333.444....",
2201"...............",
2202"...............",
2203"..............."
2204};
2205
2222{
2223 int iConfig;
2224 int iHighlight;
2225 int index;
2226 const int nSpecRows =
2227 sizeof( GlyphXpmRegionSpec )/sizeof( GlyphXpmRegionSpec[0]);
2228 const char *XmpBmp[nSpecRows];
2229
2230 // The glyphs are declared static wxIcon; so we only need
2231 // to create them once, no matter how many LabelTracks.
2232 if( mbGlyphsReady )
2233 return;
2234
2235 // We're about to tweak the basic color spec to get 12 variations.
2236 for( iConfig=0;iConfig<NUM_GLYPH_CONFIGS;iConfig++)
2237 {
2238 for( iHighlight=0;iHighlight<NUM_GLYPH_HIGHLIGHTS;iHighlight++)
2239 {
2240 index = iConfig + NUM_GLYPH_CONFIGS * iHighlight;
2241 // Copy the basic spec...
2242 memcpy( XmpBmp, GlyphXpmRegionSpec, sizeof( GlyphXpmRegionSpec ));
2243 // The highlighted region (if any) is white...
2244 if( iHighlight==1 ) XmpBmp[5]="5 c #FFFFFF";
2245 if( iHighlight==2 ) XmpBmp[6]="6 c #FFFFFF";
2246 if( iHighlight==3 ) XmpBmp[7]="7 c #FFFFFF";
2247 // For left or right arrow the other side of the glyph
2248 // is the transparent color.
2249 if( iConfig==0) { XmpBmp[3]="3 c none"; XmpBmp[5]="5 c none"; }
2250 if( iConfig==1) { XmpBmp[4]="4 c none"; XmpBmp[7]="7 c none"; }
2251 // Create the icon from the tweaked spec.
2252 mBoundaryGlyphs[index] = wxBitmap(XmpBmp);
2253 // Create the mask
2254 // SetMask takes ownership
2255 mBoundaryGlyphs[index].SetMask(safenew wxMask(mBoundaryGlyphs[index], wxColour(192, 192, 192)));
2256 }
2257 }
2258
2259 mIconWidth = mBoundaryGlyphs[0].GetWidth();
2260 mIconHeight = mBoundaryGlyphs[0].GetHeight();
2261 mTextHeight = mIconHeight; // until proved otherwise...
2262 // The icon should have an odd width so that the
2263 // line goes exactly down the middle.
2264 wxASSERT( (mIconWidth %2)==1);
2265
2266 mbGlyphsReady=true;
2267}
2268
2269#include "../../../LabelDialog.h"
2270
2272(AudacityProject &project, LabelTrack *lt, int index)
2273{
2274 const auto &formats = ProjectNumericFormats::Get( project );
2275 auto format = formats.GetSelectionFormat(),
2276 freqFormat = formats.GetFrequencySelectionFormatName();
2277 auto &tracks = TrackList::Get( project );
2278 auto &viewInfo = ViewInfo::Get( project );
2279 auto &window = ProjectWindow::Get( project );
2280
2281 LabelDialog dlg(&window, project, &tracks,
2282 lt, index,
2283 viewInfo,
2284 format, freqFormat);
2285#ifdef __WXGTK__
2286 dlg.Raise();
2287#endif
2288
2289 if (dlg.ShowModal() == wxID_OK) {
2291 .PushState(XO("Edited labels"), XO("Label"));
2292 }
2293}
2294
2297 const SelectedRegion& region, const wxString& initialValue, wxString& value)
2298{
2299 auto &trackFocus = TrackFocus::Get( project );
2300 auto &trackPanel = TrackPanel::Get( project );
2301 auto &viewInfo = ViewInfo::Get( project );
2302
2303 wxPoint position =
2304 trackPanel.FindTrackRect( trackFocus.Get() ).GetBottomLeft();
2305 // The start of the text in the text box will be roughly in line with the label's position
2306 // if it's a point label, or the start of its region if it's a region label.
2307 position.x += viewInfo.GetLeftOffset()
2308 + std::max(0, static_cast<int>(viewInfo.TimeToPosition(region.t0())))
2309 - 39;
2310 position.y += 2; // just below the bottom of the track
2311 position = trackPanel.ClientToScreen(position);
2312 auto &window = GetProjectFrame( project );
2313 AudacityTextEntryDialog dialog{ &window,
2314 XO("Name:"),
2315 XO("New label"),
2316 initialValue,
2317 wxOK | wxCANCEL,
2318 position };
2319
2320 // keep the dialog within Audacity's window, so that the dialog is always fully visible
2321 wxRect dialogScreenRect = dialog.GetScreenRect();
2322 wxRect projScreenRect = window.GetScreenRect();
2323 wxPoint max = projScreenRect.GetBottomRight() + wxPoint{ -dialogScreenRect.width, -dialogScreenRect.height };
2324 if (dialogScreenRect.x > max.x) {
2325 position.x = max.x;
2326 dialog.Move(position);
2327 }
2328 if (dialogScreenRect.y > max.y) {
2329 position.y = max.y;
2330 dialog.Move(position);
2331 }
2332
2333 dialog.SetInsertionPointEnd(); // because, by default, initial text is selected
2334 int status = dialog.ShowModal();
2335 if (status != wxID_CANCEL) {
2336 value = dialog.GetValue();
2337 value.Trim(true).Trim(false);
2338 }
2339
2340 return status;
2341}
2342
2345 return [](LabelTrack &track, size_t) {
2346 return std::make_shared<LabelTrackView>( track.SharedPointer() );
2347 };
2348}
2349
2350std::shared_ptr<ChannelVRulerControls> LabelTrackView::DoGetVRulerControls()
2351{
2352 return
2353 std::make_shared<LabelTrackVRulerControls>( shared_from_this() );
2354}
2355
2359 return [](auto &) { return SyncLockPolicy::EndSeparator; };
2360}
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
@ OnEditSelectedLabelID
@ OnDeleteSelectedLabelID
@ OnPasteSelectedTextID
@ OnCopySelectedTextID
@ OnCutSelectedTextID
static bool IsGoodLabelEditKey(const wxKeyEvent &evt)
This returns true for keys we capture for label editing.
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetLabelTrackView)
static const char *const GlyphXpmRegionSpec[]
static bool IsGoodLabelFirstKey(const wxKeyEvent &evt)
Returns true for keys we capture to start a label.
constexpr int NUM_GLYPH_HIGHLIGHTS
constexpr int MAX_NUM_ROWS
constexpr int NUM_GLYPH_CONFIGS
#define safenew
Definition: MemoryX.h:10
static const auto title
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
an object holding per-project preferred sample rate
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
@ EndSeparator
Delimits the end of a group (of which it is a part)
const auto tracks
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
static wxBrush labelSelectedBrush
Definition: AColor.h: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:93
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:309
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< LabelTrack > GetTrack() const
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)
static ProjectWindow & Get(AudacityProject &project)
void ScrollIntoView(double pos)
Defines a selected portion of a project.
double t0() const
static bool IsSelectedOrSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:112
wxColour & Colour(int iIndex)
wxBitmap & Bitmap(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:161
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1091
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
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:66
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:232
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:45
int GetLeftOffset() const
Definition: ZoomInfo.h:93
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:343
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:704
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:770
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.