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