Browse Source

Merge pull request #515 from akleja/patches-upstream

Backport track affordance and label patches from upstream Audacity.

When the fork was originally started, we ended up carrying over a couple
of features that were half- or unfinished to our fork. This merge is meant
to make up for that.

Signed-off-by: Panagiotis Vasilopoulos <hello@alwayslivid.com>
Reference-to: https://github.com/tenacityteam/tenacity/pull/515
pull/663/head
Panagiotis Vasilopoulos 4 months ago
committed by GitHub
parent
commit
4df39adc47
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/CMakeLists.txt
  2. 2
      src/LabelDialog.cpp
  3. 36
      src/LyricsWindow.cpp
  4. 3
      src/LyricsWindow.h
  5. 3
      src/Printing.cpp
  6. 2
      src/ProjectAudioManager.cpp
  7. 2
      src/ProjectFileManager.cpp
  8. 4
      src/Screenshot.cpp
  9. 183
      src/Track.cpp
  10. 60
      src/Track.h
  11. 4
      src/TrackInfo.cpp
  12. 179
      src/TrackPanel.cpp
  13. 30
      src/TrackPanel.h
  14. 27
      src/TrackPanelConstants.h
  15. 28
      src/TrackPanelResizeHandle.cpp
  16. 4
      src/TrackPanelResizeHandle.h
  17. 2
      src/TrackPanelResizerCell.cpp
  18. 8
      src/ViewInfo.h
  19. 66
      src/WaveTrack.cpp
  20. 2
      src/WaveTrack.h
  21. 6
      src/ZoomInfo.h
  22. 78
      src/commands/GetInfoCommand.cpp
  23. 2
      src/commands/GetInfoCommand.h
  24. 22
      src/commands/ScreenshotCommand.cpp
  25. 6
      src/commands/SetLabelCommand.cpp
  26. 2
      src/commands/SetTrackInfoCommand.cpp
  27. 2
      src/effects/Effect.cpp
  28. 2
      src/effects/StereoToMono.cpp
  29. 4
      src/menus/ClipMenus.cpp
  30. 3
      src/menus/EditMenus.cpp
  31. 2
      src/menus/LabelMenus.cpp
  32. 10
      src/menus/TrackMenus.cpp
  33. 2
      src/menus/ViewMenus.cpp
  34. 1
      src/tracks/labeltrack/ui/LabelDefaultClickHandle.cpp
  35. 119
      src/tracks/labeltrack/ui/LabelGlyphHandle.cpp
  36. 3
      src/tracks/labeltrack/ui/LabelGlyphHandle.h
  37. 98
      src/tracks/labeltrack/ui/LabelTextHandle.cpp
  38. 6
      src/tracks/labeltrack/ui/LabelTextHandle.h
  39. 2
      src/tracks/labeltrack/ui/LabelTrackShifter.cpp
  40. 529
      src/tracks/labeltrack/ui/LabelTrackView.cpp
  41. 48
      src/tracks/labeltrack/ui/LabelTrackView.h
  42. 8
      src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp
  43. 4
      src/tracks/playabletrack/notetrack/ui/NoteTrackView.h
  44. 26
      src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
  45. 1
      src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h
  46. 18
      src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
  47. 123
      src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp
  48. 22
      src/tracks/playabletrack/wavetrack/ui/WaveTrackView.h
  49. 12
      src/tracks/ui/BackgroundCell.cpp
  50. 4
      src/tracks/ui/EnvelopeHandle.cpp
  51. 3
      src/tracks/ui/EnvelopeHandle.h
  52. 2
      src/tracks/ui/TimeShiftHandle.cpp
  53. 25
      src/tracks/ui/TrackView.cpp
  54. 44
      src/tracks/ui/TrackView.h

1
src/CMakeLists.txt

@ -298,6 +298,7 @@ list( APPEND SOURCES PRIVATE
TrackPanelAx.h
TrackPanelCell.cpp
TrackPanelCell.h
TrackPanelConstants.h
TrackPanelDrawable.cpp
TrackPanelDrawable.h
TrackPanelDrawingContext.h

2
src/LabelDialog.cpp

@ -426,7 +426,7 @@ bool LabelDialog::TransferDataFromWindow()
// Add the label to it
lt->AddLabel(rd.selectedRegion, rd.title);
LabelTrackView::Get( *lt ).SetSelectedIndex( -1 );
LabelTrackView::Get( *lt ).ResetTextSelection();
}
return true;

36
src/LyricsWindow.cpp

@ -63,7 +63,8 @@ LyricsWindow::LyricsWindow(AudacityProject *parent)
// // WXMAC doesn't support wxFRAME_FLOAT_ON_PARENT, so we do
// SetWindowClass((WindowRef) MacGetWindowRef(), kFloatingWindowClass);
// #endif
mProject = parent;
auto pProject = parent->shared_from_this();
mProject = pProject;
SetWindowTitle();
auto titleChanged = [&](wxCommandEvent &evt)
@ -135,7 +136,7 @@ LyricsWindow::LyricsWindow(AudacityProject *parent)
//}
// Events from the project don't propagate directly to this other frame, so...
mProject->Bind(EVT_TRACK_PANEL_TIMER,
pProject->Bind(EVT_TRACK_PANEL_TIMER,
&LyricsWindow::OnTimer,
this);
Center();
@ -158,16 +159,18 @@ void LyricsWindow::OnStyle_Highlight(wxCommandEvent & WXUNUSED(event))
void LyricsWindow::OnTimer(wxCommandEvent &event)
{
if (ProjectAudioIO::Get( *mProject ).IsAudioActive())
{
auto gAudioIO = AudioIO::Get();
GetLyricsPanel()->Update(gAudioIO->GetStreamTime());
}
else
{
// Reset lyrics display.
const auto &selectedRegion = ViewInfo::Get( *mProject ).selectedRegion;
GetLyricsPanel()->Update(selectedRegion.t0());
if (auto pProject = mProject.lock()) {
if (ProjectAudioIO::Get( *pProject ).IsAudioActive())
{
auto gAudioIO = AudioIO::Get();
GetLyricsPanel()->Update(gAudioIO->GetStreamTime());
}
else
{
// Reset lyrics display.
const auto &selectedRegion = ViewInfo::Get( *pProject ).selectedRegion;
GetLyricsPanel()->Update(selectedRegion.t0());
}
}
// Let other listeners get the notification
@ -176,10 +179,11 @@ void LyricsWindow::OnTimer(wxCommandEvent &event)
void LyricsWindow::SetWindowTitle()
{
wxString name = mProject->GetProjectName();
if (!name.empty())
{
name.Prepend(wxT(" - "));
wxString name;
if (auto pProject = mProject.lock()) {
name = pProject->GetProjectName();
if (!name.empty())
name.Prepend(wxT(" - "));
}
SetTitle(AudacityKaraokeTitle.Format(name).Translation());

3
src/LyricsWindow.h

@ -13,6 +13,7 @@
#define __AUDACITY_LYRICS_WINDOW__
#include <wx/frame.h> // to inherit
#include <memory>
#include "Prefs.h"
@ -40,7 +41,7 @@ class LyricsWindow final : public wxFrame,
// PrefsListener implementation
void UpdatePrefs() override;
AudacityProject *mProject;
std::weak_ptr<AudacityProject> mProject;
LyricsPanel *mLyricsPanel;
public:

3
src/Printing.cpp

@ -102,6 +102,9 @@ bool AudacityPrintout::OnPrintPage(int WXUNUSED(page))
r.x = 0;
r.y = 0;
r.width = width;
// Note that the views as printed might not have the same proportional
// heights as displayed on the screen, because the fixed-sized separators
// are counted in those heights but not printed
auto trackHeight = (int)(view.GetHeight() * scale);
r.height = trackHeight;

2
src/ProjectAudioManager.cpp

@ -722,7 +722,7 @@ bool ProjectAudioManager::DoRecord(AudacityProject &project,
transportTracks.captureTracks.push_back(newTrack);
}
TrackList::Get( *p ).GroupChannels(*first, recordingChannels);
TrackList::Get( *p ).MakeMultiChannelTrack(*first, recordingChannels, true);
// Bug 1548. First of new tracks needs the focus.
TrackFocus::Get(*p).Set(first);
if (TrackList::Get(*p).back())

2
src/ProjectFileManager.cpp

@ -1113,7 +1113,7 @@ ProjectFileManager::AddImportedTracks(const FilePath &fileName,
auto newTrack = tracks.Add( uNewTrack );
results.push_back(newTrack->SharedPointer());
}
tracks.GroupChannels(*first, nChannels);
tracks.MakeMultiChannelTrack(*first, nChannels, true);
}
newTracks.clear();

4
src/Screenshot.cpp

@ -791,7 +791,7 @@ void ScreenshotBigDialog::SizeTracks(int h)
auto nChannels = channels.size();
auto height = nChannels == 1 ? 2 * h : h;
for (auto channel : channels)
TrackView::Get( *channel ).SetHeight(height);
TrackView::Get( *channel ).SetExpandedHeight(height);
}
ProjectWindow::Get( mContext.project ).RedrawProject();
}
@ -800,7 +800,7 @@ void ScreenshotBigDialog::OnShortTracks(wxCommandEvent & WXUNUSED(event))
{
for (auto t : TrackList::Get( mContext.project ).Any<WaveTrack>()) {
auto &view = TrackView::Get( *t );
view.SetHeight( view.GetMinimizedHeight() );
view.SetExpandedHeight( view.GetMinimizedHeight() );
}
ProjectWindow::Get( mContext.project ).RedrawProject();

183
src/Track.cpp

@ -50,7 +50,6 @@ Track::Track()
: vrulerSize(36,0)
{
mSelected = false;
mLinked = false;
mIndex = 0;
@ -76,7 +75,7 @@ void Track::Init(const Track &orig)
mName = orig.mName;
mSelected = orig.mSelected;
mLinked = orig.mLinked;
mLinkType = orig.mLinkType;
mChannel = orig.mChannel;
}
@ -172,18 +171,18 @@ void Track::SetIndex(int index)
mIndex = index;
}
void Track::SetLinked(bool l)
void Track::SetLinkType(LinkType linkType)
{
auto pList = mList.lock();
if (pList && !pList->mPendingUpdates.empty()) {
auto orig = pList->FindById( GetId() );
if (orig && orig != this) {
orig->SetLinked(l);
orig->SetLinkType(linkType);
return;
}
}
DoSetLinked(l);
DoSetLinkType(linkType);
if (pList) {
pList->RecalcPositions(mNode);
@ -191,19 +190,24 @@ void Track::SetLinked(bool l)
}
}
void Track::DoSetLinked(bool l)
void Track::DoSetLinkType(LinkType linkType) noexcept
{
mLinked = l;
mLinkType = linkType;
}
Track *Track::GetLink() const
void Track::SetChannel(ChannelType c) noexcept
{
mChannel = c;
}
Track *Track::GetLinkedTrack() const
{
auto pList = mList.lock();
if (!pList)
return nullptr;
if (!pList->isNull(mNode)) {
if (mLinked) {
if (HasLinkedTrack()) {
auto next = pList->getNext( mNode );
if ( !pList->isNull( next ) )
return next.first->get();
@ -213,7 +217,7 @@ Track *Track::GetLink() const
auto prev = pList->getPrev( mNode );
if ( !pList->isNull( prev ) ) {
auto track = prev.first->get();
if (track && track->GetLinked())
if (track && track->HasLinkedTrack())
return track;
}
}
@ -222,6 +226,11 @@ Track *Track::GetLink() const
return nullptr;
}
bool Track::HasLinkedTrack() const noexcept
{
return mLinkType != LinkType::None;
}
namespace {
inline bool IsSyncLockableNonLabelTrack( const Track *pTrack )
{
@ -368,7 +377,9 @@ bool Track::IsSelectedOrSyncLockSelected() const
{ return GetSelected() || IsSyncLockSelected(); }
bool Track::IsLeader() const
{ return !GetLink() || GetLinked(); }
{
return !GetLinkedTrack() || HasLinkedTrack();
}
bool Track::IsSelectedLeader() const
{ return IsSelected() && IsLeader(); }
@ -378,7 +389,7 @@ void Track::FinishCopy
{
if (dest) {
dest->SetChannel(n->GetChannel());
dest->SetLinked(n->GetLinked());
dest->SetLinkType(n->GetLinkType());
dest->SetName(n->GetName());
}
}
@ -389,32 +400,32 @@ bool Track::LinkConsistencyCheck()
// doesn't fix the problem, but it likely leaves us with orphaned
// sample blocks instead of much worse problems.
bool err = false;
if (GetLinked())
if (HasLinkedTrack())
{
Track *l = GetLink();
if (l)
auto link = GetLinkedTrack();
if (link)
{
// A linked track's partner should never itself be linked
if (l->GetLinked())
if (link->HasLinkedTrack())
{
wxLogWarning(
wxT("Left track %s had linked right track %s with extra right track link.\n Removing extra link from right track."),
GetName(), l->GetName());
GetName(), link->GetName());
err = true;
l->SetLinked(false);
link->SetLinkType(LinkType::None);
}
// Channels should be left and right
if ( !( (GetChannel() == Track::LeftChannel &&
l->GetChannel() == Track::RightChannel) ||
link->GetChannel() == Track::RightChannel) ||
(GetChannel() == Track::RightChannel &&
l->GetChannel() == Track::LeftChannel) ) )
link->GetChannel() == Track::LeftChannel) ) )
{
wxLogWarning(
wxT("Track %s and %s had left/right track links out of order. Setting tracks to not be linked."),
GetName(), l->GetName());
GetName(), link->GetName());
err = true;
SetLinked(false);
SetLinkType(LinkType::None);
}
}
else
@ -423,7 +434,7 @@ bool Track::LinkConsistencyCheck()
wxT("Track %s had link to NULL track. Setting it to not be linked."),
GetName());
err = true;
SetLinked(false);
SetLinkType(LinkType::None);
}
}
@ -704,57 +715,6 @@ Track *TrackList::DoAdd(const std::shared_ptr<Track> &t)
return back().get();
}
void TrackList::GroupChannels(
Track &track, size_t groupSize, bool resetChannels )
{
// If group size is exactly two, group as stereo, else mono (bug 2195).
auto list = track.mList.lock();
if ( groupSize > 0 && list.get() == this ) {
auto iter = track.mNode.first;
auto after = iter;
auto end = this->ListOfTracks::end();
auto count = groupSize;
for ( ; after != end && count; ++after, --count )
;
if ( count == 0 ) {
auto unlink = [&] ( Track &tr ) {
if ( tr.GetLinked() ) {
if ( resetChannels ) {
auto link = tr.GetLink();
if ( link )
link->SetChannel( Track::MonoChannel );
}
tr.SetLinked( false );
}
if ( resetChannels )
tr.SetChannel( Track::MonoChannel );
};
// Disassociate previous tracks -- at most one
auto pLeader = this->FindLeader( &track );
if ( *pLeader && *pLeader != &track )
unlink( **pLeader );
// First disassociate given and later tracks, then reassociate them
for ( auto iter2 = iter; iter2 != after; ++iter2 )
unlink( **iter2 );
if ( groupSize > 1 ) {
const auto channel = *iter++;
channel->SetLinked( groupSize == 2 );
channel->SetChannel( groupSize == 2? Track::LeftChannel : Track::MonoChannel );
(*iter++)->SetChannel( groupSize == 2? Track::RightChannel : Track::MonoChannel );
while (iter != after)
(*iter++)->SetChannel( Track::MonoChannel );
}
return;
}
}
// *this does not contain the track or sufficient following channels
// or group size is zero
THROW_INCONSISTENCY_EXCEPTION;
}
auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) ->
ListOfTracks::value_type
{
@ -777,6 +737,58 @@ auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) ->
return holder;
}
void TrackList::UnlinkChannels(Track& track)
{
auto list = track.mList.lock();
if (list.get() == this)
{
auto channels = TrackList::Channels(&track);
for (auto c : channels)
{
c->SetLinkType(Track::LinkType::None);
c->SetChannel(Track::ChannelType::MonoChannel);
}
}
else
THROW_INCONSISTENCY_EXCEPTION;
}
bool TrackList::MakeMultiChannelTrack(Track& track, int nChannels, bool aligned)
{
if (nChannels != 2)
return false;
auto list = track.mList.lock();
if (list.get() == this)
{
if (*list->FindLeader(&track) != &track)
return false;
auto first = list->Find(&track);
auto canLink = [&]() -> bool {
int count = nChannels;
for (auto it = first, end = TrackList::end(); it != end && count; ++it)
{
if ((*it)->HasLinkedTrack())
return false;
--count;
}
return count == 0;
}();
if (!canLink)
return false;
(*first)->SetLinkType(aligned ? Track::LinkType::Aligned : Track::LinkType::Group);
(*first)->SetChannel(Track::LeftChannel);
auto second = std::next(first);
(*second)->SetChannel(Track::RightChannel);
}
else
THROW_INCONSISTENCY_EXCEPTION;
return true;
}
TrackNodePointer TrackList::Remove(Track *t)
{
auto result = getEnd();
@ -826,7 +838,7 @@ Track *TrackList::GetNext(Track * t, bool linked) const
if (t) {
auto node = t->GetNode();
if ( !isNull( node ) ) {
if ( linked && t->GetLinked() )
if ( linked && t->HasLinkedTrack() )
node = getNext( node );
if ( !isNull( node ) )
@ -850,7 +862,7 @@ Track *TrackList::GetPrev(Track * t, bool linked) const
if (linked) {
prev = getPrev( node );
if( !isNull( prev ) &&
!t->GetLinked() && t->GetLink() )
!t->HasLinkedTrack() && t->GetLinkedTrack() )
// Make it the first
node = prev;
}
@ -864,7 +876,7 @@ Track *TrackList::GetPrev(Track * t, bool linked) const
if (linked) {
prev = getPrev( node );
if( !isNull( prev ) &&
!(*node.first)->GetLinked() && (*node.first)->GetLink() )
!(*node.first)->HasLinkedTrack() && (*node.first)->GetLinkedTrack() )
node = prev;
}
@ -1068,7 +1080,7 @@ void TrackList::UpdatePendingTracks()
if (pendingTrack && src) {
if (updater)
updater( *pendingTrack, *src );
pendingTrack->DoSetLinked(src->GetLinked());
pendingTrack->DoSetLinkType(src->GetLinkType());
}
++pUpdater;
}
@ -1296,3 +1308,18 @@ bool TrackList::HasPendingTracks() const
return true;
return false;
}
Track::LinkType Track::GetLinkType() const noexcept
{
return mLinkType;
}
bool Track::IsAlignedWithLeader() const
{
if (auto owner = GetOwner())
{
auto leader = *owner->FindLeader(this);
return leader != this && leader->GetLinkType() == Track::LinkType::Aligned;
}
return false;
}

60
src/Track.h

@ -236,10 +236,22 @@ class TENACITY_DLL_API Track /* not final */
, public AttachedTrackObjects
, public std::enable_shared_from_this<Track> // see SharedPointer()
{
public:
//! For two tracks describes the type of the linkage
enum class LinkType : int {
None = 0, //< No linkage
Group = 2, //< Tracks are grouped together
Aligned, //< Tracks are grouped and changes should be synchronized
};
private:
friend class TrackList;
private:
TrackId mId; //!< Identifies the track only in-session, not persistently
LinkType mLinkType{ LinkType::None };
protected:
std::weak_ptr<TrackList> mList; //!< Back pointer to owning TrackList
@ -253,9 +265,6 @@ class TENACITY_DLL_API Track /* not final */
private:
bool mSelected;
protected:
bool mLinked;
public:
//! Alias for my base type
@ -360,23 +369,28 @@ public:
static void FinishCopy (const Track *n, Track *dest);
// For use when loading a file. Return true if ok, else make repair
bool LinkConsistencyCheck();
virtual bool LinkConsistencyCheck();
bool HasOwner() const { return static_cast<bool>(GetOwner());}
std::shared_ptr<TrackList> GetOwner() const { return mList.lock(); }
private:
Track *GetLink() const;
bool GetLinked () const { return mLinked; }
LinkType GetLinkType() const noexcept;
//! Returns true if the leader track has link type LinkType::Aligned
bool IsAlignedWithLeader() const;
friend WaveTrack; // WaveTrack needs to call SetLinked when reloading project
void SetLinked (bool l);
void SetChannel(ChannelType c) { mChannel = c; }
protected:
void SetLinkType(LinkType linkType);
void DoSetLinkType(LinkType linkType) noexcept;
void SetChannel(ChannelType c) noexcept;
private:
// No need yet to make this virtual
void DoSetLinked(bool l);
Track* GetLinkedTrack() const;
//! Returns true for leaders of multichannel groups
bool HasLinkedTrack() const noexcept;
//! Retrieve mNode with debug checks
TrackNodePointer GetNode() const;
@ -1485,16 +1499,18 @@ public:
template<typename TrackKind>
TrackKind *Add( const std::shared_ptr< TrackKind > &t )
{ return static_cast< TrackKind* >( DoAdd( t ) ); }
/** \brief Define a group of channels starting at the given track
*
* @param track and (groupSize - 1) following tracks must be in this
* list. They will be disassociated from any groups they already belong to.
* @param groupSize must be at least 1.
* @param resetChannels if true, disassociated channels will be marked Mono.
//! Removes linkage if track belongs to a group
void UnlinkChannels(Track& track);
/** \brief Converts channels to a multichannel track.
* @param first and the following must be in this list. Tracks should
* not be a part of another group (not linked)
* @param nChannels number of channels, for now only 2 channels supported
* @param aligned if true, the link type will be set to Track::LinkType::Aligned,
* or Track::LinkType::Group otherwise
* @returns true on success, false if some prerequisites do not met
*/
void GroupChannels(
Track &track, size_t groupSize, bool resetChannels = true );
bool MakeMultiChannelTrack(Track& first, int nChannels, bool aligned);
/// Replace first track with second track, give back a holder
/// Give the replacement the same id as the replaced

4
src/TrackInfo.cpp

@ -202,7 +202,7 @@ unsigned TrackInfo::MinimumTrackHeight()
height += commonTrackTCPBottomLines.front().height;
// + 1 prevents the top item from disappearing for want of enough space,
// according to the rules in HideTopItem.
return height + kTopMargin + kBottomMargin + 1;
return height + kVerticalPadding + 1;
}
bool TrackInfo::HideTopItem( const wxRect &rect, const wxRect &subRect,
@ -566,7 +566,7 @@ void TrackInfo::SetTrackInfoFont(wxDC * dc)
unsigned TrackInfo::DefaultTrackHeight( const TCPLines &topLines )
{
int needed =
kTopMargin + kBottomMargin +
kVerticalPadding +
totalTCPLines( topLines, true ) +
totalTCPLines( commonTrackTCPBottomLines, false ) + 1;
return (unsigned) std::max( needed, (int) TrackView::DefaultHeight );

179
src/TrackPanel.cpp

@ -26,7 +26,7 @@
TrackInfo class to draw the controls area on the left of a track,
and the TrackArtist class to draw the actual waveforms.
Note that in some of the older code here, e.g., GetLabelWidth(),
Note that in some of the older code here,
"Label" means the TrackInfo plus the vertical ruler.
Confusing relative to LabelTrack labels.
@ -45,12 +45,14 @@ is time to refresh some aspect of the screen.
#include "TrackPanel.h"
#include "TrackPanelConstants.h"
#include <wx/setup.h> // for wxUSE_* macros
#include "AdornedRulerPanel.h"
#include "tracks/ui/CommonTrackPanelCell.h"
#include "KeyboardCapture.h"
#include "Project.h"
#include "ProjectAudioIO.h"
@ -91,6 +93,9 @@ is time to refresh some aspect of the screen.
#include <wx/dcclient.h>
#include <wx/graphics.h>
static_assert( kVerticalPadding == kTopMargin + kBottomMargin );
static_assert( kTrackInfoBtnSize == kAffordancesAreaHeight, "Drag bar is misaligned with the menu button");
/**
\class TrackPanel
@ -114,8 +119,8 @@ controls, and is a constant.
GetVRulerWidth() is variable -- all tracks have the same ruler width at any
time, but that width may be adjusted when tracks change their vertical scales.
GetLabelWidth() counts columns up to and including the VRuler.
GetLeftOffset() is yet one more -- it counts the "one pixel" column.
GetLeftOffset() counts columns up to and including the VRuler and one more,
the "one pixel" column.
Cell for label has a rectangle that OMITS left, top, and bottom
margins
@ -752,23 +757,30 @@ void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
if (!trk)
return;
// Always move to the first channel of the group, and use only
// the sum of channel heights, not the height of any channel alone!
trk = *GetTracks()->FindLeader(trk);
auto &view = TrackView::Get( *trk );
auto height =
TrackList::Channels(trk).sum( TrackView::GetTrackHeight )
- kTopInset - kShadowThickness;
TrackList::Channels(trk).sum( TrackView::GetTrackHeight );
// subtract insets and shadows from the rectangle, but not border
// Set rectangle top according to the scrolling position, `vpos`
// Subtract the inset (above) and shadow (below) from the height of the
// rectangle, but not the border
// This matters because some separators do paint over the border
wxRect rect(kLeftInset,
-mViewInfo->vpos + view.GetY() + kTopInset,
GetRect().GetWidth() - kLeftInset - kRightInset - kShadowThickness,
height);
const auto top =
-mViewInfo->vpos + view.GetCumulativeHeightBefore() + kTopInset;
height -= (kTopInset + kShadowThickness);
// Width also subtracts insets (left and right) plus shadow (right)
const auto left = kLeftInset;
const auto width = GetRect().GetWidth()
- (kLeftInset + kRightInset + kShadowThickness);
wxRect rect(left, top, width, height);
if( refreshbacking )
{
mRefreshBacking = true;
}
Refresh( false, &rect );
}
@ -851,16 +863,62 @@ void TrackPanel::DrawTracks(wxDC * dc)
}
void TrackPanel::SetBackgroundCell
(const std::shared_ptr< TrackPanelCell > &pCell)
(const std::shared_ptr< CommonTrackPanelCell > &pCell)
{
mpBackground = pCell;
}
std::shared_ptr< TrackPanelCell > TrackPanel::GetBackgroundCell()
std::shared_ptr< CommonTrackPanelCell > TrackPanel::GetBackgroundCell()
{
return mpBackground;
}
namespace {
std::vector<int> FindAdjustedChannelHeights( Track &t )
{
auto channels = TrackList::Channels(&t);
wxASSERT(!channels.empty());
// Collect heights, and count affordances
int nAffordances = 0;
int totalHeight = 0;
std::vector<int> oldHeights;
for (auto channel : channels) {
auto &view = TrackView::Get( *channel );
const auto height = view.GetHeight();
totalHeight += height;
oldHeights.push_back( height );
if (view.GetAffordanceControls())
++nAffordances;
}
// Allocate results
auto nChannels = static_cast<int>(oldHeights.size());
std::vector<int> results;
results.reserve(nChannels);
// Now reallocate the channel heights for the presence of affordances
// and separators
auto availableHeight = totalHeight
- nAffordances * kAffordancesAreaHeight
- (nChannels - 1) * kChannelSeparatorThickness
- kTrackSeparatorThickness;
int cumulativeOldHeight = 0;
int cumulativeNewHeight = 0;
for (const auto &oldHeight : oldHeights) {
// Preserve the porportions among the stored heights
cumulativeOldHeight += oldHeight;
const auto newHeight =
cumulativeOldHeight * availableHeight / totalHeight
- cumulativeNewHeight;
cumulativeNewHeight += newHeight;
results.push_back(newHeight);
}
return results;
}
}
void TrackPanel::UpdateVRulers()
{
for (auto t : GetTracks()->Any< WaveTrack >())
@ -883,15 +941,17 @@ void TrackPanel::UpdateTrackVRuler(Track *t)
if (!t)
return;
auto heights = FindAdjustedChannelHeights(*t);
wxRect rect(mViewInfo->GetVRulerOffset(),
0,
mViewInfo->GetVRulerWidth(),
0);
auto pHeight = heights.begin();
for (auto channel : TrackList::Channels(t)) {
auto &view = TrackView::Get( *channel );
const auto height = view.GetHeight() - (kTopMargin + kBottomMargin);
const auto height = *pHeight++;
rect.SetHeight( height );
const auto subViews = view.GetSubViews( rect );
if (subViews.empty())
@ -1069,7 +1129,11 @@ void DrawTrackName(
// Tracks more than kTranslucentHeight will have maximum translucency for shields.
const int kOpaqueHeight = 44;
const int kTranslucentHeight = 124;
// PRL: to do: reexamine this strange use of TrackView::GetHeight,
// ultimately to compute an opacity
int h = TrackView::Get( *t ).GetHeight();
// f codes the opacity as a number between 0.0 and 1.0
float f = wxClip((h-kOpaqueHeight)/(float)(kTranslucentHeight-kOpaqueHeight),0.0,1.0);
// kOpaque is the shield's alpha for tracks that are not tall
@ -1330,7 +1394,7 @@ struct HorizontalGroup final : TrackPanelGroup {
};
// optional affordance area, and n channels with vertical rulers,
// optional affordance areas, and n channels with vertical rulers,
// alternating with n - 1 resizers;
// each channel-ruler pair might be divided into multiple views
struct ChannelGroup final : TrackPanelGroup {
@ -1344,7 +1408,9 @@ struct ChannelGroup final : TrackPanelGroup {
const auto channels = TrackList::Channels( mpTrack.get() );
const auto pLast = *channels.rbegin();
wxCoord yy = rect.GetTop();
for ( auto channel : channels )
auto heights = FindAdjustedChannelHeights(*mpTrack);
auto pHeight = heights.begin();
for ( auto channel : channels )
{
auto &view = TrackView::Get( *channel );
if (auto affordance = view.GetAffordanceControls())
@ -1357,9 +1423,9 @@ struct ChannelGroup final : TrackPanelGroup {
yy += kAffordancesAreaHeight;
}
auto height = view.GetHeight();
auto height = *pHeight++;
rect.SetTop( yy );
rect.SetHeight( height - kSeparatorThickness );
rect.SetHeight( height - kChannelSeparatorThickness );
refinement.emplace_back( yy,
std::make_shared< VRulersAndChannels >(
channel->shared_from_this(),
@ -1368,7 +1434,7 @@ struct ChannelGroup final : TrackPanelGroup {
if ( channel != pLast ) {
yy += height;
refinement.emplace_back(
yy - kSeparatorThickness,
yy - kChannelSeparatorThickness,
TrackPanelResizerCell::Get( *channel ).shared_from_this() );
}
}
@ -1466,7 +1532,7 @@ struct ResizingChannelGroup final : TrackPanelGroup {
{ return { Axis::Y, Refinement{
{ rect.GetTop(),
std::make_shared< LabeledChannelGroup >( mpTrack, mLeftOffset ) },
{ rect.GetTop() + rect.GetHeight() - kSeparatorThickness,
{ rect.GetTop() + rect.GetHeight() - kTrackSeparatorThickness,
TrackPanelResizerCell::Get(
**TrackList::Channels( mpTrack.get() ).rbegin() ).shared_from_this()
}
@ -1494,9 +1560,6 @@ struct Subgroup final : TrackPanelGroup {
for ( auto channel : TrackList::Channels( leader ) ) {
auto &view = TrackView::Get( *channel );
height += view.GetHeight();
if (view.GetAffordanceControls())
height += kAffordancesAreaHeight;
}
refinement.emplace_back( yy,
std::make_shared< ResizingChannelGroup >(
@ -1542,7 +1605,7 @@ wxRect TrackPanel::FindTrackRect( const Track * target )
{
auto leader = *GetTracks()->FindLeader( target );
if (!leader) {
return { 0, 0, 0, 0 };
return {};
}
return CellularPanel::FindRect( [&] ( TrackPanelNode &node ) {
@ -1552,6 +1615,46 @@ wxRect TrackPanel::FindTrackRect( const Track * target )
} );
}
wxRect TrackPanel::FindFocusedTrackRect( const Track * target )
{
auto rect = FindTrackRect(target);
if (rect != wxRect{}) {
// Enlarge horizontally.
// PRL: perhaps it's one pixel too much each side, including some gray
// beyond the yellow?
rect.x = 0;
GetClientSize(&rect.width, nullptr);
// Enlarge vertically, enough to enclose the yellow focus border pixels
// The the outermost ring of gray pixels is included on three sides
// but not the top (should that be fixed?)
// (Note that TrackPanel paints its focus over the "top margin" of the
// rectangle allotted to the track, according to TrackView::GetY() and
// TrackView::GetHeight(), but also over the margin of the next track.)
rect.height += kBottomMargin;
int dy = kTopMargin - 1;
rect.Inflate( 0, dy );
// Note that this rectangle does not coincide with any one of
// the nodes in the subdivision.
}
return rect;
}
std::vector<wxRect> TrackPanel::FindRulerRects( const Track *target )
{
std::vector<wxRect> results;
if (target)
VisitCells( [&]( const wxRect &rect, TrackPanelCell &visited ) {
if (auto pRuler = dynamic_cast<const TrackVRulerControls*>(&visited);
pRuler && pRuler->FindTrack().get() == target)
results.push_back(rect);
} );
return results;
}
TrackPanelCell *TrackPanel::GetFocusedCell()
{
auto pTrack = TrackFocus::Get( *GetProject() ).Get();
@ -1575,27 +1678,3 @@ void TrackPanel::OnTrackFocusChange( wxCommandEvent &event )
Refresh( false );
}
}
IsVisibleTrack::IsVisibleTrack(AudacityProject *project)
: mPanelRect {
wxPoint{ 0, ViewInfo::Get( *project ).vpos },
wxSize{
ViewInfo::Get( *project ).GetTracksUsableWidth(),
ViewInfo::Get( *project ).GetHeight()
}
}
{}
bool IsVisibleTrack::operator () (const Track *pTrack) const
{
// Need to return true if this track or a later channel intersects
// the view
return
TrackList::Channels(pTrack).StartingWith(pTrack).any_of(
[this]( const Track *pT ) {
auto &view = TrackView::Get( *pT );
wxRect r(0, view.GetY(), 1, view.GetHeight());
return r.Intersects(mPanelRect);
}
);
}

30
src/TrackPanel.h

@ -31,6 +31,9 @@
class wxRect;
// All cells of the TrackPanel are subclasses of this
class CommonTrackPanelCell;
class SpectrumAnalyst;
class Track;
class TrackList;
@ -135,10 +138,27 @@ protected:
public:
void MakeParentRedrawScrollbars();
// Rectangle includes track control panel, and the vertical ruler, and
// the proper track area of all channels, and the separators between them.
/*!
@return includes track control panel, and the vertical ruler, and
the proper track area of all channels, and the separators between them.
If target is nullptr, returns empty rectangle.
*/
wxRect FindTrackRect( const Track * target );
/*!
@return includes what's in `FindTrackRect(target)` and the focus ring
area around it.
If target is nullptr, returns empty rectangle.
*/
wxRect FindFocusedTrackRect( const Track * target );
/*!
@return extents of the vertical rulers of one channel, top to bottom.
(There may be multiple sub-views, each with a ruler.)
If target is nullptr, returns an empty vector.
*/
std::vector<wxRect> FindRulerRects( const Track * target );
protected:
// Get the root object defining a recursive subdivision of the panel's
// area into cells
@ -160,8 +180,8 @@ public:
// Set the object that performs catch-all event handling when the pointer
// is not in any track or ruler or control panel.
void SetBackgroundCell
(const std::shared_ptr< TrackPanelCell > &pCell);
std::shared_ptr< TrackPanelCell > GetBackgroundCell();
(const std::shared_ptr< CommonTrackPanelCell > &pCell);
std::shared_ptr< CommonTrackPanelCell > GetBackgroundCell();
public:
@ -201,7 +221,7 @@ protected:
protected:
std::shared_ptr<TrackPanelCell> mpBackground;
std::shared_ptr<CommonTrackPanelCell> mpBackground;
DECLARE_EVENT_TABLE()

27
src/TrackPanelConstants.h

@ -0,0 +1,27 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
TrackPanelConstants.h
Paul Licameli split from ViewInfo.h
**********************************************************************/
#ifndef __AUDACITY_TRACK_PANEL_CONSTANTS__
#define __AUDACITY_TRACK_PANEL_CONSTANTS__
#include "ZoomInfo.h"
// See big pictorial comment in TrackPanel.cpp for explanation of these numbers
//! constants related to y coordinates in the track panel
enum : int {
kAffordancesAreaHeight = 18,
kTopInset = 4,
kTopMargin = kTopInset + kBorderThickness,
kBottomMargin = kShadowThickness + kBorderThickness,
kTrackSeparatorThickness = kBottomMargin + kTopMargin,
kChannelSeparatorThickness = 1,
};
#endif

28
src/TrackPanelResizeHandle.cpp

@ -69,7 +69,7 @@ UIHandle::Result TrackPanelResizeHandle::Click(
int coord = 0;
for ( const auto channel : range ) {
int newCoord = ((double)ii++ /size) * height;
TrackView::Get(*channel).SetHeight( newCoord - coord );
TrackView::Get(*channel).SetExpandedHeight( newCoord - coord );
coord = newCoord;
}
ProjectHistory::Get( *pProject ).ModifyState(false);
@ -92,7 +92,7 @@ TrackPanelResizeHandle::TrackPanelResizeHandle
auto last = *channels.rbegin();
auto &lastView = TrackView::Get( *last );
mInitialTrackHeight = lastView.GetHeight();
mInitialActualHeight = lastView.GetActualHeight();
mInitialExpandedHeight = lastView.GetExpandedHeight();
mInitialMinimized = lastView.GetMinimized();
if (channels.size() > 1) {
@ -100,7 +100,7 @@ TrackPanelResizeHandle::TrackPanelResizeHandle
auto &firstView = TrackView::Get( *first );
mInitialUpperTrackHeight = firstView.GetHeight();
mInitialUpperActualHeight = firstView.GetActualHeight();
mInitialUpperExpandedHeight = firstView.GetExpandedHeight();
if (track.get() == *channels.rbegin())
// capturedTrack is the lowest track
@ -137,7 +137,7 @@ UIHandle::Result TrackPanelResizeHandle::Drag
auto channels = TrackList::Channels( pTrack.get() );
for (auto channel : channels) {
auto &channelView = TrackView::Get( *channel );
channelView.SetHeight(channelView.GetHeight());
channelView.SetExpandedHeight(channelView.GetHeight());
channelView.SetMinimized( false );
}
@ -171,8 +171,8 @@ UIHandle::Result TrackPanelResizeHandle::Drag
if (newUpperTrackHeight < prevView.GetMinimizedHeight())
newUpperTrackHeight = prevView.GetMinimizedHeight();
view.SetHeight(newTrackHeight);
prevView.SetHeight(newUpperTrackHeight);
view.SetExpandedHeight(newTrackHeight);
prevView.SetExpandedHeight(newUpperTrackHeight);
};
auto doResizeBetween = [&] (Track *next, bool WXUNUSED(vStereo)) {
@ -194,15 +194,15 @@ UIHandle::Result TrackPanelResizeHandle::Drag
mInitialUpperTrackHeight + mInitialTrackHeight - view.GetMinimizedHeight();
}
view.SetHeight(newUpperTrackHeight);
nextView.SetHeight(newTrackHeight);
view.SetExpandedHeight(newUpperTrackHeight);
nextView.SetExpandedHeight(newTrackHeight);
};
auto doResize = [&] {
int newTrackHeight = mInitialTrackHeight + delta;
if (newTrackHeight < view.GetMinimizedHeight())
newTrackHeight = view.GetMinimizedHeight();
view.SetHeight(newTrackHeight);
view.SetExpandedHeight(newTrackHeight);
};
//STM: We may be dragging one or two (stereo) tracks.
@ -268,7 +268,7 @@ UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject)
case IsResizing:
{
auto &view = TrackView::Get( *pTrack );
view.SetHeight(mInitialActualHeight);
view.SetExpandedHeight(mInitialExpandedHeight);
view.SetMinimized( mInitialMinimized );
}
break;
@ -277,9 +277,9 @@ UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject)
Track *const next = * ++ tracks.Find(pTrack.get());
auto
&view = TrackView::Get( *pTrack ), &nextView = TrackView::Get( *next );
view.SetHeight(mInitialUpperActualHeight);
view.SetExpandedHeight(mInitialUpperExpandedHeight);
view.SetMinimized( mInitialMinimized );
nextView.SetHeight(mInitialActualHeight);
nextView.SetExpandedHeight(mInitialExpandedHeight);
nextView.SetMinimized( mInitialMinimized );
}
break;
@ -288,9 +288,9 @@ UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject)
Track *const prev = * -- tracks.Find(pTrack.get());
auto
&view = TrackView::Get( *pTrack ), &prevView = TrackView::Get( *prev );
view.SetHeight(mInitialActualHeight);
view.SetExpandedHeight(mInitialExpandedHeight);
view.SetMinimized( mInitialMinimized );
prevView.SetHeight(mInitialUpperActualHeight);
prevView.SetExpandedHeight(mInitialUpperExpandedHeight);
prevView.SetMinimized(mInitialMinimized);
}
break;

4
src/TrackPanelResizeHandle.h

@ -58,9 +58,9 @@ private:
bool mInitialMinimized{};
int mInitialTrackHeight{};
int mInitialActualHeight{};
int mInitialExpandedHeight{};
int mInitialUpperTrackHeight{};
int mInitialUpperActualHeight{};
int mInitialUpperExpandedHeight{};
int mMouseClickY{};
};

2
src/TrackPanelResizerCell.cpp

@ -74,7 +74,7 @@ void TrackPanelResizerCell::Draw(
// Paint the left part of the background
const auto artist = TrackArtist::Get( context );
auto labelw = artist->pZoomInfo->GetLabelWidth();
auto labelw = artist->pZoomInfo->GetLeftOffset() - 1;
AColor::MediumTrackInfo( dc, pTrack->GetSelected() );
dc->DrawRectangle(
rect.GetX(), rect.GetY(), labelw, rect.GetHeight() );

8
src/ViewInfo.h

@ -98,14 +98,8 @@ private:
SelectedRegion mRegion;
};
// See big pictorial comment in TrackPanel.cpp for explanation of these numbers
enum : int {
// constants related to y coordinates in the track panel
kAffordancesAreaHeight = 20,
kTopInset = 4,
kTopMargin = kTopInset + kBorderThickness,
kBottomMargin = kShadowThickness + kBorderThickness,
kSeparatorThickness = kBottomMargin + kTopMargin,
kVerticalPadding = 6, /*!< Width of padding below each channel group */
};
enum : int {

66
src/WaveTrack.cpp

@ -35,6 +35,7 @@ from the project that will own the track.
#include <wx/defs.h>
#include <wx/intl.h>
#include <wx/debug.h>
#include <wx/log.h>
#include <float.h>
#include <math.h>
@ -64,6 +65,33 @@ from the project that will own the track.
using std::max;
namespace {
bool AreAligned(const WaveClipPointers& a, const WaveClipPointers& b)
{
if (a.size() != b.size())
return false;
const auto compare = [](const WaveClip* a, const WaveClip* b) {
return a->GetStartTime() == b->GetStartTime() &&
a->GetNumSamples() == b->GetNumSamples();
};
return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
}
//Handles possible future file values
Track::LinkType ToLinkType(int value)
{
if (value < 0)
return Track::LinkType::None;
else if (value > 3)
return Track::LinkType::Group;
return static_cast<Track::LinkType>(value);
}
}
static ProjectFileIORegistry::Entry registerFactory{
wxT( "wavetrack" ),
[]( AudacityProject &project ){
@ -240,7 +268,39 @@ void WaveTrack::SetPanFromChannelType()
SetPan( -1.0f );
else if( mChannel == Track::RightChannel )
SetPan( 1.0f );
};
}
bool WaveTrack::LinkConsistencyCheck()
{
auto err = PlayableTrack::LinkConsistencyCheck();
auto linkType = GetLinkType();
if (static_cast<int>(linkType) == 1 || //Comes from old audacity version
linkType == LinkType::Aligned)
{
auto next = dynamic_cast<WaveTrack*>(*std::next(GetOwner()->Find(this)));
if (next == nullptr)
{
//next track is not a wave track, fix and report error
wxLogWarning(
wxT("Right track %s is expected to be a WaveTrack.\n Removing link from left wave track %s."),
next->GetName(), GetName());
SetLinkType(LinkType::None);
SetChannel(MonoChannel);
err = true;
}
else
{
auto newLinkType = AreAligned(SortedClipArray(), next->SortedClipArray())
? LinkType::Aligned : LinkType::Group;
//not an error
if (newLinkType != linkType)
SetLinkType(newLinkType);
}
}
return !err;
}
void WaveTrack::SetLastScaleType() const
{
@ -1667,7 +1727,7 @@ bool WaveTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
}
else if (!wxStrcmp(attr, wxT("linked")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
SetLinked(nValue != 0);
SetLinkType(ToLinkType(nValue));
else if (!wxStrcmp(attr, wxT("colorindex")) &&
XMLValueChecker::IsGoodString(strValue) &&
strValue.ToLong(&nValue))
@ -1734,7 +1794,7 @@ void WaveTrack::WriteXML(XMLWriter &xmlFile) const
xmlFile.StartTag(wxT("wavetrack"));
this->Track::WriteCommonXMLAttributes( xmlFile );
xmlFile.WriteAttr(wxT("channel"), mChannel);
xmlFile.WriteAttr(wxT("linked"), mLinked);
xmlFile.WriteAttr(wxT("linked"), static_cast<int>(GetLinkType()));
this->PlayableTrack::WriteXMLAttributes(xmlFile);
xmlFile.WriteAttr(wxT("rate"), mRate);
xmlFile.WriteAttr(wxT("gain"), (double)mGain);

2
src/WaveTrack.h

@ -100,6 +100,8 @@ private:
ChannelType GetChannel() const override;
virtual void SetPanFromChannelType() override;
bool LinkConsistencyCheck() override;
/** @brief Get the time at which the first clip in the track starts
*
* @return time in seconds, or zero if there are no clips in the track

6
src/ZoomInfo.h

@ -98,9 +98,11 @@ public:
int GetVRulerWidth() const { return mVRulerWidth; }
void SetVRulerWidth( int width ) { mVRulerWidth = width; }
int GetVRulerOffset() const { return kTrackInfoWidth + kLeftMargin; }
int GetLabelWidth() const { return GetVRulerOffset() + GetVRulerWidth(); }
int GetLeftOffset() const { return GetLabelWidth() + 1;}
// The x-coordinate of the start of the displayed track data
int GetLeftOffset() const
{ return GetVRulerOffset() + GetVRulerWidth() + 1; }
// The number of pixel columns for display of track data
int GetTracksUsableWidth