M src/CMakeLists.txt => src/CMakeLists.txt +1 -0
@@ 298,6 298,7 @@ list( APPEND SOURCES PRIVATE
TrackPanelAx.h
TrackPanelCell.cpp
TrackPanelCell.h
+ TrackPanelConstants.h
TrackPanelDrawable.cpp
TrackPanelDrawable.h
TrackPanelDrawingContext.h
M src/LabelDialog.cpp => src/LabelDialog.cpp +1 -1
@@ 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;
M src/LyricsWindow.cpp => src/LyricsWindow.cpp +20 -16
@@ 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());
M src/LyricsWindow.h => src/LyricsWindow.h +2 -1
@@ 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:
M src/Printing.cpp => src/Printing.cpp +3 -0
@@ 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;
M src/ProjectAudioManager.cpp => src/ProjectAudioManager.cpp +1 -1
@@ 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())
M src/ProjectFileManager.cpp => src/ProjectFileManager.cpp +1 -1
@@ 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();
M src/Screenshot.cpp => src/Screenshot.cpp +2 -2
@@ 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();
M src/Track.cpp => src/Track.cpp +105 -78
@@ 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;
+}
M src/Track.h => src/Track.h +38 -22
@@ 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
M src/TrackInfo.cpp => src/TrackInfo.cpp +2 -2
@@ 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 );
M src/TrackPanel.cpp => src/TrackPanel.cpp +129 -50
@@ 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);
- }
- );
-}
M src/TrackPanel.h => src/TrackPanel.h +25 -5
@@ 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()
A src/TrackPanelConstants.h => src/TrackPanelConstants.h +27 -0
@@ 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
M src/TrackPanelResizeHandle.cpp => src/TrackPanelResizeHandle.cpp +14 -14
@@ 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;
M src/TrackPanelResizeHandle.h => src/TrackPanelResizeHandle.h +2 -2
@@ 58,9 58,9 @@ private:
bool mInitialMinimized{};
int mInitialTrackHeight{};
- int mInitialActualHeight{};
+ int mInitialExpandedHeight{};
int mInitialUpperTrackHeight{};
- int mInitialUpperActualHeight{};
+ int mInitialUpperExpandedHeight{};
int mMouseClickY{};
};
M src/TrackPanelResizerCell.cpp => src/TrackPanelResizerCell.cpp +1 -1
@@ 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() );
M src/ViewInfo.h => src/ViewInfo.h +1 -7
@@ 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 {
M src/WaveTrack.cpp => src/WaveTrack.cpp +63 -3
@@ 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);
M src/WaveTrack.h => src/WaveTrack.h +2 -0
@@ 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
M src/ZoomInfo.h => src/ZoomInfo.h +4 -2
@@ 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() const
{
return
M src/commands/GetInfoCommand.cpp => src/commands/GetInfoCommand.cpp +9 -69
@@ 694,80 694,20 @@ void GetInfoCommand::ExploreAdornments( const CommandContext &context,
}
void GetInfoCommand::ExploreTrackPanel( const CommandContext &context,
- wxPoint P, wxWindow * pWin, int WXUNUSED(Id), int depth )
+ wxPoint P, int depth )
{
AudacityProject * pProj = &context.project;
auto &tp = TrackPanel::Get( *pProj );
- auto &viewInfo = ViewInfo::Get( *pProj );
-
- wxRect trackRect = pWin->GetRect();
-
- for ( auto t : TrackList::Get( *pProj ).Any() + IsVisibleTrack{ pProj } ) {
- auto &view = TrackView::Get( *t );
- trackRect.y = view.GetY() - viewInfo.vpos;
- trackRect.height = view.GetHeight();
-
-#if 0
- // Work in progress on getting the TCP button positions and sizes.
- wxRect rect = trackRect;
- Track *l = t->GetLink();
-
- if (t->GetLinked()) {
- rect.height += l->GetHeight();
- }
-
- switch (t->GetKind()) {
- case Track::Wave:
- {
- break;
- }
-#ifdef USE_MIDI
- case Track::Note:
- {
- break;
- }
-#endif // USE_MIDI
- case Track::Label:
- break;
- case Track::Time:
- break;
- }
- {
- // Start with whole track rect
- wxRect R = trackRect;
-
- // Now exclude left, right, and top insets
- R.x += kLeftInset;
- R.y += kTopInset;
- R.width -= kLeftInset * 2;
- R.height -= kTopInset;
-
- int labelw = viewInfo.GetLabelWidth();
- //int vrul = viewInfo.GetVRulerOffset();
- bool bIsWave = true;
- //mTrackInfo.DrawBackground(dc, R, t->GetSelected(), bIsWave, labelw, vrul);
-
-
- for (Overlay * pOverlay : pTP->mOverlays) {
- auto R2(pOverlay->GetRectangle(trackRect.GetSize()).first);
- context.Status( wxString::Format(" [ %2i, %3i, %3i, %3i, %3i, \"%s\" ],",
- depth, R2.GetLeft(), R2.GetTop(), R2.GetRight(), R2.GetBottom(), "Otherthing" ));
- }
- }
-#endif
-
- // The VRuler.
- {
- wxRect R = trackRect;
- R.x += viewInfo.GetVRulerOffset();
- R.y += kTopMargin;
- R.width = viewInfo.GetVRulerWidth();
- R.height -= (kTopMargin + kBottomMargin);
+ wxRect panelRect{ {}, tp.GetSize() };
+ for ( auto t : TrackList::Get( *pProj ).Any() ) {
+ auto rulers = tp.FindRulerRects(t);
+ for (auto &R : rulers) {
+ if (!R.Intersects(panelRect))
+ continue;
R.SetPosition( R.GetPosition() + P );
-
context.StartStruct();
context.AddItem( depth, "depth" );
- context.AddItem( "VRuler", "label" );
+ context.AddItem( "VRuler", "label" );
context.StartField("box");
context.StartArray();
context.AddItem( R.GetLeft() );
@@ 790,7 730,7 @@ void GetInfoCommand::ExploreWindows( const CommandContext &context,
if( pWin->GetName() == "Track Panel" )
{
wxRect R = pWin->GetScreenRect();
- ExploreTrackPanel( context, R.GetPosition()-P, pWin, Id, depth );
+ ExploreTrackPanel( context, R.GetPosition()-P, depth );
return;
}
wxWindowList list = pWin->GetChildren();
M src/commands/GetInfoCommand.h => src/commands/GetInfoCommand.h +1 -1
@@ 58,7 58,7 @@ private:
void ExploreMenu( const CommandContext &context, wxMenu * pMenu, int Id, int depth );
void ExploreTrackPanel( const CommandContext & context,
- wxPoint P, wxWindow * pWin, int Id, int depth );
+ wxPoint P, int depth );
void ExploreAdornments( const CommandContext & context,
wxPoint P, wxWindow * pWin, int Id, int depth );
void ExploreWindows( const CommandContext & context,
M src/commands/ScreenshotCommand.cpp => src/commands/ScreenshotCommand.cpp +1 -21
@@ 708,27 708,7 @@ wxRect ScreenshotCommand::GetTracksRect(TrackPanel * panel){
wxRect ScreenshotCommand::GetTrackRect( AudacityProject * pProj, TrackPanel * panel, int n){
auto FindRectangle = []( TrackPanel &panel, Track &t )
{
- // This rectangle omits the focus ring about the track, and
- // also within that, a narrow black border with a "shadow" below and
- // to the right
- wxRect rect = panel.FindTrackRect( &t );
-
- // Enlarge horizontally.
- // PRL: perhaps it's one pixel too much each side, including some gray
- // beyond the yellow?
- rect.x = 0;
- panel.GetClientSize(&rect.width, nullptr);
-
- // Enlarge vertically, enough to enclose the yellow focus border pixels
- // Omit the outermost ring of gray pixels
-
- // (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 );
+ wxRect rect = panel.FindFocusedTrackRect( &t );
// Reposition it relative to parent of panel
rect.SetPosition(
M src/commands/SetLabelCommand.cpp => src/commands/SetLabelCommand.cpp +3 -3
@@ 114,13 114,13 @@ bool SetLabelCommand::Apply(const CommandContext & context)
auto &view = LabelTrackView::Get( *labelTrack );
if( mbSelected )
{
- view.SetSelectedIndex( ii );
+ view.SetNavigationIndex( ii );
double t0 = pLabel->selectedRegion.t0();
double t1 = pLabel->selectedRegion.t1();
selectedRegion.setTimes( t0, t1);
}
- else if( view.GetSelectedIndex( context.project ) == ii )
- view.SetSelectedIndex( -1 );
+ else if( view.GetNavigationIndex( context.project ) == ii )
+ view.SetNavigationIndex( -1 );
}
labelTrack->SortLabels();
M src/commands/SetTrackInfoCommand.cpp => src/commands/SetTrackInfoCommand.cpp +1 -1
@@ 383,7 383,7 @@ bool SetTrackVisualsCommand::ApplyInner(const CommandContext & context, Track *
wt->SetWaveColorIndex( mColour );
if( t && bHasHeight )
- TrackView::Get( *t ).SetHeight( mHeight );
+ TrackView::Get( *t ).SetExpandedHeight( mHeight );
if( wt && bHasDisplayType ) {
auto &view = WaveTrackView::Get( *wt );
M src/effects/Effect.cpp => src/effects/Effect.cpp +1 -1
@@ 2395,8 2395,8 @@ void Effect::Preview(bool dryOnly)
mixRight->Offset(-mixRight->GetStartTime());
mixRight->SetSelected(true);
pRight = mTracks->Add( mixRight );
+ mTracks->MakeMultiChannelTrack(*pLeft, 2, true);
}
- mTracks->GroupChannels(*pLeft, pRight ? 2 : 1);
}
else {
for (auto src : saveTracks->Any< const WaveTrack >()) {
M src/effects/StereoToMono.cpp => src/effects/StereoToMono.cpp +1 -1
@@ 213,7 213,7 @@ bool EffectStereoToMono::ProcessOne(sampleCount & curTime, sampleCount totalTime
double minStart = wxMin(left->GetStartTime(), right->GetStartTime());
left->Clear(left->GetStartTime(), left->GetEndTime());
left->Paste(minStart, outTrack.get());
- mOutputTracks->GroupChannels(*left, 1);
+ mOutputTracks->UnlinkChannels(*left);
mOutputTracks->Remove(right);
return bResult;
M => +2 -2
@@ 643,7 643,7 @@ double DoClipMove( AudacityProject &project, Track *track,
// Find the first channel that has a clip at time t0
auto hitTestResult = TrackShifter::HitTestResult::Track;
for (auto channel : TrackList::Channels(track) ) {
uShifter = MakeTrackShifter::Call( *track, project );
uShifter = MakeTrackShifter::Call( *channel, project );
if ( (hitTestResult = uShifter->HitTest( t0, viewInfo )) ==
TrackShifter::HitTestResult::Miss )
uShifter.reset();
@@ 657,7 657,7 @@ double DoClipMove( AudacityProject &project, Track *track,
auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) );
auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 );
state.Init( project, *track, hitTestResult, std::move( uShifter ),
state.Init( project, pShifter->GetTrack(), hitTestResult, std::move( uShifter ),
t0, viewInfo, trackList, syncLocked );
auto hSlideAmount = state.DoSlideHorizontal( desiredSlideAmount );
M => +2 -1
@@ 46,7 46,7 @@ bool DoPasteText(AudacityProject &project)
for (auto pLabelTrack : tracks.Any<LabelTrack>())
{
// Does this track have an active label?
if (LabelTrackView::Get( *pLabelTrack ).HasSelection( project )) {
if (LabelTrackView::Get( *pLabelTrack ).GetTextEditIndex(project) != -1) {
// Yes, so try pasting into it
auto &view = LabelTrackView::Get( *pLabelTrack );
@@ 607,6 607,7 @@ void OnPaste(const CommandContext &context)
if (ff) {
TrackFocus::Get(project).Set(ff);
ff->EnsureVisible();
ff->LinkConsistencyCheck();
}
}
}
M => +1 -1
@@ 346,7 346,7 @@ void OnPasteNewLabel(const CommandContext &context)
// Unselect the last label, so we'll have just one active label when
// we're done
if (plt)
LabelTrackView::Get( *plt ).SetSelectedIndex( -1 );
LabelTrackView::Get( *plt ).ResetTextSelection();
// Add a NEW label, paste into it
// Paul L: copy whatever defines the selected region, not just times
M => +5 -5
@@ 87,10 87,10 @@ void DoMixAndRender
auto pNewLeft = tracks.Add( uNewLeft );
decltype(pNewLeft) pNewRight{};
if (uNewRight)
pNewRight = tracks.Add( uNewRight );
// Do this only after adding tracks to the list
tracks.GroupChannels(*pNewLeft, pNewRight ? 2 : 1);
{
pNewRight = tracks.Add(uNewRight);
tracks.MakeMultiChannelTrack(*pNewLeft, 2, true);
}
// If we're just rendering (not mixing), keep the track name the same
if (selectedCount==1) {
@@ 644,7 644,7 @@ void OnNewStereoTrack(const CommandContext &context)
auto right = tracks.Add( trackFactory.NewWaveTrack( defaultFormat, rate ) );
right->SetSelected(true);
tracks.GroupChannels(*left, 2);
tracks.MakeMultiChannelTrack(*left, 2, true);
ProjectHistory::Get( project )
.PushState(XO("Created new stereo audio track"), XO("New Track"));
M => +1 -1
@@ 154,7 154,7 @@ void DoZoomFitV(AudacityProject &project)
height = std::max( (int)TrackInfo::MinimumTrackHeight(), height );
for (auto t : range)
TrackView::Get( *t ).SetHeight(height);
TrackView::Get( *t ).SetExpandedHeight(height);
}
}
M src/tracks/labeltrack/ui/LabelDefaultClickHandle.cpp => src/tracks/labeltrack/ui/LabelDefaultClickHandle.cpp +0 -1
@@ 72,7 72,6 @@ UIHandle::Result LabelDefaultClickHandle::Click
if (pLT != &TrackView::Get( *lt )) {
auto &view = LabelTrackView::Get( *lt );
view.ResetFlags();
- view.SetSelectedIndex( -1 );
}
}
}
M src/tracks/labeltrack/ui/LabelGlyphHandle.cpp => src/tracks/labeltrack/ui/LabelGlyphHandle.cpp +85 -34
@@ 19,6 19,9 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../TrackPanelMouseEvent.h"
#include "../../../UndoManager.h"
#include "../../../ViewInfo.h"
+#include "../../../SelectionState.h"
+#include "../../../ProjectAudioIO.h"
+#include "../../../tracks/ui/TimeShiftHandle.h"
#include <wx/cursor.h>
#include <wx/translation.h>
@@ 57,6 60,7 @@ void LabelTrackHit::OnLabelPermuted( LabelTrackEvent &e )
update( mMouseOverLabelLeft );
update( mMouseOverLabelRight );
+ update( mMouseOverLabel );
}
LabelGlyphHandle::LabelGlyphHandle
@@ 124,26 128,24 @@ LabelGlyphHandle::~LabelGlyphHandle()
void LabelGlyphHandle::HandleGlyphClick
(LabelTrackHit &hit, const wxMouseEvent & evt,
const wxRect & r, const ZoomInfo &zoomInfo,
- NotifyingSelectedRegion &WXUNUSED(newSel))
+ NotifyingSelectedRegion &newSel)
{
if (evt.ButtonDown())
{
//OverGlyph sets mMouseOverLabel to be the chosen label.
const auto pTrack = mpLT;
LabelTrackView::OverGlyph(*pTrack, hit, evt.m_x, evt.m_y);
+
hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
( hit.mEdge & 3 ) != 0;
if (hit.mIsAdjustingLabel)
{
- double t = 0.0;
- // We move if we hit the centre, we adjust one edge if we hit a chevron.
- // This is if we are moving just one edge.
- hit.mbIsMoving = (hit.mEdge & 4)!=0;
-
- // No to the above! We initially expect to be moving just one edge.
- hit.mbIsMoving = false;
+ auto& view = LabelTrackView::Get(*pTrack);
+ view.ResetTextSelection();
+ double t = 0.0;
+
// When we start dragging the label(s) we don't want them to jump.
// so we calculate the displacement of the mouse from the drag center
// and use that in subsequent dragging calculations. The mouse stays
@@ 170,11 172,14 @@ void LabelGlyphHandle::HandleGlyphClick
// If we're on a boundary between two different labels,
// then it's an adjust.
// In both cases the two points coalesce.
- hit.mbIsMoving = (hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight);
-
+ //
+ // NOTE: seems that it's not neccessary that hitting the both
+ // left and right handles mean that we're dealing with a point,
+ // but the range will be turned into a point on click
+ bool isPointLabel = hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight;
// Except! We don't coalesce if both ends are from the same label and
// we have deliberately chosen to preserve length, by holding shift down.
- if (!(hit.mbIsMoving && evt.ShiftDown()))
+ if (!(isPointLabel && evt.ShiftDown()))
{
MayAdjustLabel(hit, hit.mMouseOverLabelLeft, -1, false, t);
MayAdjustLabel(hit, hit.mMouseOverLabelRight, 1, false, t);
@@ 190,6 195,10 @@ void LabelGlyphHandle::HandleGlyphClick
{
t = mLabels[ hit.mMouseOverLabelLeft ].getT0();
}
+ else if (hit.mMouseOverLabel >= 0)
+ {
+ t = mLabels[hit.mMouseOverLabel].getT0();
+ }
mxMouseDisplacement = zoomInfo.TimeToPosition(t, r.x) - evt.m_x;
}
}
@@ 201,6 210,8 @@ UIHandle::Result LabelGlyphHandle::Click
auto result = LabelDefaultClickHandle::Click( evt, pProject );
const wxMouseEvent &event = evt.event;
+ auto& selectionState = SelectionState::Get(*pProject);
+ auto& tracks = TrackList::Get(*pProject);
auto &viewInfo = ViewInfo::Get( *pProject );
HandleGlyphClick(
@@ 216,13 227,6 @@ UIHandle::Result LabelGlyphHandle::Click
// redraw the track.
result |= RefreshCode::RefreshCell;
- // handle shift+ctrl down
- /*if (event.ShiftDown()) { // && event.ControlDown()) {
- lTrack->SetHighlightedByKey(true);
- Refresh(false);
- return;
- }*/
-
return result;
}
@@ 300,24 304,66 @@ bool LabelGlyphHandle::HandleGlyphDragRelease
const auto &mLabels = pTrack->GetLabels();
if(evt.LeftUp())
{
- bool lupd = false, rupd = false;
+ bool updated = false;
if( hit.mMouseOverLabelLeft >= 0 ) {
auto labelStruct = mLabels[ hit.mMouseOverLabelLeft ];
- lupd = labelStruct.updated;
+ updated |= labelStruct.updated;
labelStruct.updated = false;
pTrack->SetLabel( hit.mMouseOverLabelLeft, labelStruct );
}
if( hit.mMouseOverLabelRight >= 0 ) {
auto labelStruct = mLabels[ hit.mMouseOverLabelRight ];
- rupd = labelStruct.updated;
+ updated |= labelStruct.updated;
labelStruct.updated = false;
pTrack->SetLabel( hit.mMouseOverLabelRight, labelStruct );
}
+ if (hit.mMouseOverLabel >= 0)
+ {
+ auto labelStruct = mLabels[hit.mMouseOverLabel];
+ if (!labelStruct.updated)
+ {
+ //happens on click over bar between handles (without moving a cursor)
+ newSel = labelStruct.selectedRegion;
+
+ // IF the user clicked a label, THEN select all other tracks by Label
+ // do nothing if at least one other track is selected
+ auto& selectionState = SelectionState::Get(project);
+ auto& tracks = TrackList::Get(project);
+
+ bool done = tracks.Selected().any_of(
+ [&](const Track* track) { return track != static_cast<Track*>(pTrack.get()); }
+ );
+
+ if (!done) {
+ //otherwise, select all tracks
+ for (auto t : tracks.Any())
+ selectionState.SelectTrack(*t, true, true);
+ }
+
+ // Do this after, for its effect on TrackPanel's memory of last selected
+ // track (which affects shift-click actions)
+ selectionState.SelectTrack(*pTrack.get(), true, true);
+
+ // PRL: bug1659 -- make selection change undo correctly
+ updated = !ProjectAudioIO::Get(project).IsAudioActive();
+
+ auto& view = LabelTrackView::Get(*pTrack);
+ view.SetNavigationIndex(hit.mMouseOverLabel);
+ }
+ else
+ {
+ labelStruct.updated = false;
+ pTrack->SetLabel(hit.mMouseOverLabel, labelStruct);
+ updated = true;
+ }
+ }
+
hit.mIsAdjustingLabel = false;
hit.mMouseOverLabelLeft = -1;
hit.mMouseOverLabelRight = -1;
- return lupd || rupd;
+ hit.mMouseOverLabel = -1;
+ return updated;
}
if(evt.Dragging())
@@ 328,35 374,40 @@ bool LabelGlyphHandle::HandleGlyphDragRelease
// to allow scrolling while dragging labels
int x = Constrain( evt.m_x + mxMouseDisplacement - r.x, 0, r.width);
- // If exactly one edge is selected we allow swapping
- bool bAllowSwapping =
- ( hit.mMouseOverLabelLeft >=0 ) !=
- ( hit.mMouseOverLabelRight >= 0);
+ double fNewX = zoomInfo.PositionToTime(x, 0);
+ // Moving the whole ranged label
+ if (hit.mMouseOverLabel != -1)
+ {
+ MayMoveLabel(hit.mMouseOverLabel, -1, fNewX);
+ }
// If we're on the 'dot' and nowe're moving,
// Though shift-down inverts that.
// and if both edges the same, then we're always moving the label.
- bool bLabelMoving = hit.mbIsMoving;
- bLabelMoving ^= evt.ShiftDown();
- bLabelMoving |= ( hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight );
- double fNewX = zoomInfo.PositionToTime(x, 0);
- if( bLabelMoving )
+ else if((hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight) || evt.ShiftDown())
{
MayMoveLabel( hit.mMouseOverLabelLeft, -1, fNewX );
MayMoveLabel( hit.mMouseOverLabelRight, +1, fNewX );
}
else
{
+ // If exactly one edge is selected we allow swapping
+ bool bAllowSwapping =
+ (hit.mMouseOverLabelLeft >= 0) !=
+ (hit.mMouseOverLabelRight >= 0);
MayAdjustLabel( hit, hit.mMouseOverLabelLeft, -1, bAllowSwapping, fNewX );
MayAdjustLabel( hit, hit.mMouseOverLabelRight, +1, bAllowSwapping, fNewX );
}
const auto &view = LabelTrackView::Get( *pTrack );
- if( view.HasSelection( project ) )
+ auto navigationIndex = view.GetNavigationIndex(project);
+ if(navigationIndex != -1 &&
+ (navigationIndex == hit.mMouseOverLabel ||
+ navigationIndex == hit.mMouseOverLabelLeft ||
+ navigationIndex == hit.mMouseOverLabelRight))
{
- auto selIndex = view.GetSelectedIndex( project );
//Set the selection region to be equal to
//the NEW size of the label.
- newSel = mLabels[ selIndex ].selectedRegion;
+ newSel = mLabels[navigationIndex].selectedRegion;
}
pTrack->SortLabels();
}
M src/tracks/labeltrack/ui/LabelGlyphHandle.h => src/tracks/labeltrack/ui/LabelGlyphHandle.h +2 -1
@@ 35,9 35,10 @@ struct LabelTrackHit
~LabelTrackHit();
int mEdge{};
+ //This one is to distinguish ranged label from point label
+ int mMouseOverLabel{ -1 }; /// Keeps track of which (ranged) label the mouse is currently over.
int mMouseOverLabelLeft{ -1 }; /// Keeps track of which left label the mouse is currently over.
int mMouseOverLabelRight{ -1 }; /// Keeps track of which right label the mouse is currently over.
- bool mbIsMoving {};
bool mIsAdjustingLabel {};
std::shared_ptr<LabelTrack> mpLT {};
M src/tracks/labeltrack/ui/LabelTextHandle.cpp => src/tracks/labeltrack/ui/LabelTextHandle.cpp +24 -74
@@ 70,34 70,21 @@ LabelTextHandle::~LabelTextHandle()
{
}
-void LabelTextHandle::HandleTextClick(AudacityProject &
-#if defined(__WXGTK__) && (HAVE_GTK)
- project
-#endif
- ,
- const wxMouseEvent & evt,
- const wxRect & r, const ZoomInfo &zoomInfo,
- NotifyingSelectedRegion &newSel)
+void LabelTextHandle::HandleTextClick(AudacityProject &project, const wxMouseEvent & evt)
{
auto pTrack = mpLT.lock();
if (!pTrack)
return;
auto &view = LabelTrackView::Get( *pTrack );
- static_cast<void>(r);//compiler food.
- static_cast<void>(zoomInfo);//compiler food.
if (evt.ButtonDown())
{
const auto selIndex = LabelTrackView::OverATextBox( *pTrack, evt.m_x, evt.m_y );
- view.SetSelectedIndex( selIndex );
if ( selIndex != -1 ) {
- const auto &mLabels = pTrack->GetLabels();
- const auto &labelStruct = mLabels[ selIndex ];
- newSel = labelStruct.selectedRegion;
-
if (evt.LeftDown()) {
+ mRightDragging = false;
// Find the NEW drag end
- auto position = view.FindCursorPosition( evt.m_x );
+ auto position = view.FindCursorPosition(selIndex, evt.m_x );
// Anchor shift-drag at the farther end of the previous highlight
// that is farther from the click, on Mac, for consistency with
@@ 118,13 105,18 @@ void LabelTextHandle::HandleTextClick(AudacityProject &
else
initial = position;
- view.SetTextHighlight( initial, position );
- mRightDragging = false;
+ view.SetTextSelection(selIndex, initial, position );
}
else
+ {
+ if (!view.IsTextSelected(project))
+ {
+ auto position = view.FindCursorPosition(selIndex, evt.m_x);
+ view.SetTextSelection(selIndex, position, position);
+ }
// Actually this might be right or middle down
mRightDragging = true;
-
+ }
// Middle click on GTK: paste from primary selection
#if defined(__WXGTK__) && (HAVE_GTK)
if (evt.MiddleDown()) {
@@ 132,7 124,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject &
// case PasteSelectedText() will start a NEW label at the click
// location
if (!LabelTrackView::OverTextBox(&labelStruct, evt.m_x, evt.m_y))
- view.SetSelectedIndex( -1 );
+ view.ResetTextSelection();
double t = zoomInfo.PositionToTime(evt.m_x, r.x);
newSel = SelectedRegion(t, t);
}
@@ 158,41 150,10 @@ UIHandle::Result LabelTextHandle::Click
auto result = LabelDefaultClickHandle::Click( evt, pProject );
- auto &selectionState = SelectionState::Get( *pProject );
- auto &tracks = TrackList::Get( *pProject );
- mChanger =
- std::make_shared< SelectionStateChanger >( selectionState, tracks );
-
const wxMouseEvent &event = evt.event;
auto &viewInfo = ViewInfo::Get( *pProject );
- mSelectedRegion = viewInfo.selectedRegion;
- HandleTextClick( *pProject,
- event, evt.rect, viewInfo, viewInfo.selectedRegion );
-
- {
- // IF the user clicked a label, THEN select all other tracks by Label
-
- //do nothing if at least one other track is selected
- bool done = tracks.Selected().any_of(
- [&](const Track *pTrack){ return pTrack != pLT.get(); }
- );
-
- if (!done) {
- //otherwise, select all tracks
- for (auto t : tracks.Any())
- selectionState.SelectTrack( *t, true, true );
- }
-
- // Do this after, for its effect on TrackPanel's memory of last selected
- // track (which affects shift-click actions)
- selectionState.SelectTrack( *pLT, true, true );
- }
-
- // PRL: bug1659 -- make selection change undo correctly
- const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
- if (!unsafe)
- ProjectHistory::Get( *pProject ).ModifyState(false);
+ HandleTextClick(*pProject, event);
return result | RefreshCode::RefreshCell;
}
@@ 228,19 189,19 @@ void LabelTextHandle::HandleTextDragRelease(
if(evt.Dragging())
{
- if (!mRightDragging)
+ auto index = view.GetTextEditIndex(project);
+ if (!mRightDragging && index != -1)
// Update drag end
- view.SetCurrentCursorPosition(
- view.FindCursorPosition( evt.m_x ) );
-
+ view.SetCurrentCursorPosition(view.FindCursorPosition(index, evt.m_x ));
return;
}
- if (evt.RightUp()) {
- const auto selIndex = view.GetSelectedIndex( project );
- if ( selIndex != -1 &&
- LabelTrackView::OverTextBox(
- pTrack->GetLabel( selIndex ), evt.m_x, evt.m_y ) ) {
+ if (evt.RightUp())
+ {
+ auto index = view.GetTextEditIndex(project);
+ if(index != -1 &&
+ LabelTrackView::OverTextBox(pTrack->GetLabel(index), evt.m_x, evt.m_y))
+ {
// popup menu for editing
// TODO: handle context menus via CellularPanel?
view.ShowContextMenu( project );
@@ 269,10 230,9 @@ UIHandle::Result LabelTextHandle::Drag
mLabelTrackStartYPos = event.m_y;
auto pView = pLT ? &LabelTrackView::Get( *pLT ) : nullptr;
- if (pLT &&
- (pView->GetSelectedIndex( project ) != -1) &&
+ if (pLT && (pView->GetTextEditIndex( project ) != -1) &&
LabelTrackView::OverTextBox(
- pLT->GetLabel(pView->GetSelectedIndex( project )),
+ pLT->GetLabel(pView->GetTextEditIndex( project )),
mLabelTrackStartXPos,
mLabelTrackStartYPos))
mLabelTrackStartYPos = -1;
@@ 301,11 261,6 @@ UIHandle::Result LabelTextHandle::Release
// Only selected a part of a text string and changed track selectedness.
// No undoable effects.
- if (mChanger) {
- mChanger->Commit();
- mChanger.reset();
- }
-
const wxMouseEvent &event = evt.event;
auto pLT = TrackList::Get( *pProject ).Lock(mpLT);
if (pLT)
@@ 320,11 275,6 @@ UIHandle::Result LabelTextHandle::Release
UIHandle::Result LabelTextHandle::Cancel( AudacityProject *pProject )
{
- // Restore the selection states of tracks
- // Note that we are also relying on LabelDefaultClickHandle::Cancel
- // to restore the selection state of the labels in the tracks.
- auto &viewInfo = ViewInfo::Get( *pProject );
- viewInfo.selectedRegion = mSelectedRegion;
auto result = LabelDefaultClickHandle::Cancel( pProject );
return result | RefreshCode::RefreshAll;
}
M src/tracks/labeltrack/ui/LabelTextHandle.h => src/tracks/labeltrack/ui/LabelTextHandle.h +1 -5
@@ 58,9 58,7 @@ public:
private:
void HandleTextClick
- (AudacityProject &project,
- const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo,
- NotifyingSelectedRegion &newSel);
+ (AudacityProject &project, const wxMouseEvent & evt);
void HandleTextDragRelease(
AudacityProject &project, const wxMouseEvent & evt);
@@ 68,8 66,6 @@ private:
int mLabelNum{ -1 };
int mLabelTrackStartXPos { -1 };
int mLabelTrackStartYPos { -1 };
- SelectedRegion mSelectedRegion{};
- std::shared_ptr<SelectionStateChanger> mChanger;
/// flag to tell if it's a valid dragging
bool mRightDragging{ false };
M src/tracks/labeltrack/ui/LabelTrackShifter.cpp => src/tracks/labeltrack/ui/LabelTrackShifter.cpp +1 -1
@@ 60,7 60,7 @@ public:
iLabel =
LabelTrackView::OverATextBox(*mpTrack, pParams->xx, pParams->yy);
if (iLabel == -1)
- iLabel = LabelTrackView::Get(*mpTrack).GetSelectedIndex(mProject);
+ iLabel = LabelTrackView::Get(*mpTrack).GetNavigationIndex(mProject);
if (iLabel != -1) {
UnfixIntervals([&](const auto &myInterval){
return GetIndex( myInterval ) == iLabel;
M src/tracks/labeltrack/ui/LabelTrackView.cpp => src/tracks/labeltrack/ui/LabelTrackView.cpp +285 -244
@@ 147,10 147,10 @@ void LabelTrackView::CopyTo( Track &track ) const
auto &other = TrackView::Get( track );
if ( const auto pOther = dynamic_cast< const LabelTrackView* >( &other ) ) {
- pOther->mSelIndex = mSelIndex;
+ pOther->mNavigationIndex = mNavigationIndex;
pOther->mInitialCursorPos = mInitialCursorPos;
pOther->mCurrentCursorPos = mCurrentCursorPos;
- pOther->mDrawCursor = mDrawCursor;
+ pOther->mTextEditIndex = mTextEditIndex;
pOther->mUndoLabel = mUndoLabel;
}
}
@@ 218,15 218,24 @@ void LabelTrackView::ResetFlags()
{
mInitialCursorPos = 1;
mCurrentCursorPos = 1;
- mDrawCursor = false;
+ mTextEditIndex = -1;
+ mNavigationIndex = -1;
+}
+
+LabelTrackView::Flags LabelTrackView::SaveFlags() const
+{
+ return {
+ mInitialCursorPos, mCurrentCursorPos, mNavigationIndex,
+ mTextEditIndex, mUndoLabel
+ };
}
void LabelTrackView::RestoreFlags( const Flags& flags )
{
mInitialCursorPos = flags.mInitialCursorPos;
mCurrentCursorPos = flags.mCurrentCursorPos;
- mSelIndex = flags.mSelIndex;
- mDrawCursor = flags.mDrawCursor;
+ mNavigationIndex = flags.mNavigationIndex;
+ mTextEditIndex = flags.mTextEditIndex;
}
wxFont LabelTrackView::GetFont(const wxString &faceName, int size)
@@ 236,8 245,13 @@ wxFont LabelTrackView::GetFont(const wxString &faceName, int size)
encoding = wxFONTENCODING_DEFAULT;
else
encoding = wxFONTENCODING_SYSTEM;
- return wxFont(size, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
- wxFONTWEIGHT_NORMAL, false, faceName, encoding);
+
+ auto fontInfo = size == 0 ? wxFontInfo() : wxFontInfo(size);
+ fontInfo
+ .Encoding(encoding)
+ .FaceName(faceName);
+
+ return wxFont(fontInfo);
}
void LabelTrackView::ResetFont()
@@ 576,6 590,11 @@ void LabelTrackView::DrawGlyphs(
dc.DrawBitmap(GetGlyph(GlyphRight), x1-xHalfWidth,yStart, true);
}
+int LabelTrackView::GetTextFrameHeight()
+{
+ return mTextHeight + TextFramePadding * 2;
+}
+
/// Draw the text of the label and also draw
/// a long thin rectangle for its full extent
/// from x to x1 and a rectangular frame
@@ 584,6 603,7 @@ void LabelTrackView::DrawGlyphs(
/// @param r the LabelTrack rectangle.
void LabelTrackView::DrawText(wxDC & dc, const LabelStruct &ls, const wxRect & r)
{
+ const int yFrameHeight = mTextHeight + TextFramePadding * 2;
//If y is positive then it is the center line for the
//text we are about to draw.
//if it isn't, nothing to draw.
@@ 603,7 623,9 @@ void LabelTrackView::DrawText(wxDC & dc, const LabelStruct &ls, const wxRect & r
if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
{
// Now draw the text itself.
- dc.DrawText(ls.title, xText, y-mTextHeight/2);
+ auto pos = y - LabelBarHeight - yFrameHeight + TextFrameYOffset +
+ (yFrameHeight - mFontHeight) / 2 + dc.GetFontMetrics().ascent;
+ dc.DrawText(ls.title, xText, pos);
}
}
@@ 612,32 634,6 @@ void LabelTrackView::DrawText(wxDC & dc, const LabelStruct &ls, const wxRect & r
void LabelTrackView::DrawTextBox(
wxDC & dc, const LabelStruct &ls, const wxRect & r)
{
- //If y is positive then it is the center line for the
- //text we are about to draw.
- const int yBarHeight=3;
- const int yFrameHeight = mTextHeight+3;
- const int xBarShorten = mIconWidth+4;
- auto &y = ls.y;
- if( y == -1 )
- return;
-
- {
- auto &x = ls.x;
- auto &x1 = ls.x1;
- const int xStart=wxMax(r.x,x+xBarShorten/2);
- const int xEnd=wxMin(r.x+r.width,x1-xBarShorten/2);
- const int xWidth = xEnd-xStart;
-
- if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
- {
-
- wxRect bar( xStart,y-yBarHeight/2+yFrameHeight/2,
- xWidth,yBarHeight);
- if( x1 > x+xBarShorten )
- dc.DrawRectangle(bar);
- }
- }
-
// In drawing the bar and the frame, we compute the clipping
// to the viewport ourselves. Under Win98 the GDI does its
// calculations in 16 bit arithmetic, and so gets it completely
@@ 647,19 643,42 @@ void LabelTrackView::DrawTextBox(
// Draw bar for label extent...
// We don't quite draw from x to x1 because we allow
// half an icon width at each end.
- {
- auto &xText = ls.xText;
- const int xStart=wxMax(r.x,xText-mIconWidth/2);
- const int xEnd=wxMin(r.x+r.width,xText+ls.width+mIconWidth/2);
- const int xWidth = xEnd-xStart;
+ const auto textFrameHeight = GetTextFrameHeight();
+ auto& xText = ls.xText;
+ const int xStart = wxMax(r.x, xText - mIconWidth / 2);
+ const int xEnd = wxMin(r.x + r.width, xText + ls.width + mIconWidth / 2);
+ const int xWidth = xEnd - xStart;
- if( (xStart < (r.x+r.width)) && (xEnd > r.x) && (xWidth>0))
- {
- wxRect frame(
- xStart,y-yFrameHeight/2,
- xWidth,yFrameHeight );
- dc.DrawRectangle(frame);
- }
+ if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
+ {
+ wxRect frame(
+ xStart, ls.y - (textFrameHeight + LabelBarHeight) / 2 + TextFrameYOffset,
+ xWidth, textFrameHeight);
+ dc.DrawRectangle(frame);
+ }
+}
+
+void LabelTrackView::DrawBar(wxDC& dc, const LabelStruct& ls, const wxRect& r)
+{
+ //If y is positive then it is the center line for the
+ //text we are about to draw.
+ const int xBarShorten = mIconWidth + 4;
+ auto& y = ls.y;
+ if (y == -1)
+ return;
+
+ auto& x = ls.x;
+ auto& x1 = ls.x1;
+ const int xStart = wxMax(r.x, x + xBarShorten / 2);
+ const int xEnd = wxMin(r.x + r.width, x1 - xBarShorten / 2);
+ const int xWidth = xEnd - xStart;
+
+ if ((xStart < (r.x + r.width)) && (xEnd > r.x) && (xWidth > 0))
+ {
+ wxRect bar(xStart, y - (LabelBarHeight - GetTextFrameHeight()) / 2,
+ xWidth, LabelBarHeight);
+ if (x1 > x + xBarShorten)
+ dc.DrawRectangle(bar);
}
}
@@ 667,15 686,16 @@ void LabelTrackView::DrawTextBox(
void LabelTrackView::DrawHighlight( wxDC & dc, const LabelStruct &ls,
int xPos1, int xPos2, int charHeight)
{
- wxPen curPen = dc.GetPen();
- curPen.SetColour(wxString(wxT("BLUE")));
+ const int yFrameHeight = mTextHeight + TextFramePadding * 2;
+
+ dc.SetPen(*wxTRANSPARENT_PEN);
wxBrush curBrush = dc.GetBrush();
curBrush.SetColour(wxString(wxT("BLUE")));
- auto &y = ls.y;
+ auto top = ls.y + TextFrameYOffset - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - charHeight) / 2;
if (xPos1 < xPos2)
- dc.DrawRectangle(xPos1-1, y-charHeight/2, xPos2-xPos1+1, charHeight);
+ dc.DrawRectangle(xPos1-1, top, xPos2-xPos1+1, charHeight);
else
- dc.DrawRectangle(xPos2-1, y-charHeight/2, xPos1-xPos2+1, charHeight);
+ dc.DrawRectangle(xPos2-1, top, xPos1-xPos2+1, charHeight);
}
namespace {
@@ 694,7 714,7 @@ void getXPos( const LabelStruct &ls, wxDC & dc, int * xPos1, int cursorPos)
bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const
{
- if ( HasSelection( project ) ) {
+ if (IsValidIndex(mTextEditIndex, project)) {
wxMemoryDC dc;
if (msFont.Ok()) {
@@ 704,7 724,7 @@ bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- getXPos(mLabels[mSelIndex], dc, x, mCurrentCursorPos);
+ getXPos(mLabels[mTextEditIndex], dc, x, mCurrentCursorPos);
*x += mIconWidth / 2;
return true;
}
@@ 726,7 746,7 @@ void LabelTrackView::CalcHighlightXs(int *x1, int *x2) const
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- const auto &labelStruct = mLabels[mSelIndex];
+ const auto &labelStruct = mLabels[mTextEditIndex];
// find the left X pos of highlighted area
getXPos(labelStruct, dc, x1, pos1);
@@ 797,8 817,9 @@ void LabelTrackView::Draw
// guarding against the case where there are no
// labels or all are empty strings, which for example
// happens with a NEW label track.
- dc.GetTextExtent(wxT("Demo Text x^y"), &textWidth, &textHeight);
- mTextHeight = (int)textHeight;
+ mTextHeight = dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent;
+ const int yFrameHeight = mTextHeight + TextFramePadding * 2;
+
ComputeLayout( r, zoomInfo );
dc.SetTextForeground(theTheme.Colour( clrLabelTrackText));
dc.SetBackgroundMode(wxTRANSPARENT);
@@ 838,41 859,47 @@ void LabelTrackView::Draw
#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
highlight = highlightTrack && target->GetLabelNum() == i;
#endif
- bool selected = GetSelectedIndex( project ) == i;
+
+ dc.SetBrush(mNavigationIndex == i || (pHit && pHit->mMouseOverLabel == i)
+ ? AColor::labelTextEditBrush : AColor::labelTextNormalBrush);
+ DrawBar(dc, labelStruct, r);
- if( selected )
- dc.SetBrush( AColor::labelTextEditBrush );
- else if ( highlight )
- dc.SetBrush( AColor::uglyBrush );
- DrawTextBox( dc, labelStruct, r );
+ bool selected = mTextEditIndex == i;
- if (highlight || selected)
+ if (selected)
+ dc.SetBrush(AColor::labelTextEditBrush);
+ else if (highlight)
+ dc.SetBrush(AColor::uglyBrush);
+ else
dc.SetBrush(AColor::labelTextNormalBrush);
+ DrawTextBox(dc, labelStruct, r);
+
+ dc.SetBrush(AColor::labelTextNormalBrush);
}
}
// Draw highlights
- if ( (mInitialCursorPos != mCurrentCursorPos) && HasSelection( project ) )
+ if ( (mInitialCursorPos != mCurrentCursorPos) && IsValidIndex(mTextEditIndex, project))
{
int xpos1, xpos2;
CalcHighlightXs(&xpos1, &xpos2);
- DrawHighlight(dc, mLabels[mSelIndex],
- xpos1, xpos2, mFontHeight);
+ DrawHighlight(dc, mLabels[mTextEditIndex],
+ xpos1, xpos2, dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent);
}
// Draw the text and the label boxes.
{ int i = -1; for (const auto &labelStruct : mLabels) { ++i;
- if( GetSelectedIndex( project ) == i )
+ if(mTextEditIndex == i )
dc.SetBrush(AColor::labelTextEditBrush);
DrawText( dc, labelStruct, r );
- if( GetSelectedIndex( project ) == i )
+ if(mTextEditIndex == i )
dc.SetBrush(AColor::labelTextNormalBrush);
}}
// Draw the cursor, if there is one.
- if( mDrawCursor && HasSelection( project ) )
+ if(mInitialCursorPos == mCurrentCursorPos && IsValidIndex(mTextEditIndex, project))
{
- const auto &labelStruct = mLabels[mSelIndex];
+ const auto &labelStruct = mLabels[mTextEditIndex];
int xPos = labelStruct.xText;
if( mCurrentCursorPos > 0)
@@ 886,9 913,10 @@ void LabelTrackView::Draw
wxPen currentPen = dc.GetPen();
const int CursorWidth=2;
currentPen.SetWidth(CursorWidth);
+ const auto top = labelStruct.y - (LabelBarHeight + yFrameHeight) / 2 + (yFrameHeight - mFontHeight) / 2 + TextFrameYOffset;
AColor::Line(dc,
- xPos-1, labelStruct.y - mFontHeight/2 + 1,
- xPos-1, labelStruct.y + mFontHeight/2 - 1);
+ xPos-1, top,
+ xPos-1, top + mFontHeight);
currentPen.SetWidth(1);
}
}
@@ 902,17 930,9 @@ void LabelTrackView::Draw(
CommonTrackView::Draw( context, rect, iPass );
}
-void LabelTrackView::SetSelectedIndex( int index )
-{
- if ( index >= 0 && index < (int)FindLabelTrack()->GetLabels().size() )
- mSelIndex = index;
- else
- mSelIndex = -1;
-}
-
/// uses GetTextExtent to find the character position
/// corresponding to the x pixel position.
-int LabelTrackView::FindCursorPosition(wxCoord xPos)
+int LabelTrackView::FindCursorPosition(int labelIndex, wxCoord xPos)
{
int result = -1;
wxMemoryDC dc;
@@ 929,7 949,7 @@ int LabelTrackView::FindCursorPosition(wxCoord xPos)
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- const auto &labelStruct = mLabels[mSelIndex];
+ const auto &labelStruct = mLabels[labelIndex];
const auto &title = labelStruct.title;
const int length = title.length();
while (!finished && (charIndex < length + 1))
@@ 970,13 990,33 @@ void LabelTrackView::SetCurrentCursorPosition(int pos)
{
mCurrentCursorPos = pos;
}
-
-void LabelTrackView::SetTextHighlight(
- int initialPosition, int currentPosition )
+void LabelTrackView::SetTextSelection(int labelIndex, int start, int end)
+{
+ mTextEditIndex = labelIndex;
+ mInitialCursorPos = start;
+ mCurrentCursorPos = end;
+}
+int LabelTrackView::GetTextEditIndex(AudacityProject& project) const
+{
+ if (IsValidIndex(mTextEditIndex, project))
+ return mTextEditIndex;
+ return -1;
+}
+void LabelTrackView::ResetTextSelection()
+{
+ mTextEditIndex = -1;
+ mCurrentCursorPos = 1;
+ mInitialCursorPos = 1;
+}
+void LabelTrackView::SetNavigationIndex(int index)
{
- mInitialCursorPos = initialPosition;
- mCurrentCursorPos = currentPosition;
- mDrawCursor = true;
+ mNavigationIndex = index;
+}
+int LabelTrackView::GetNavigationIndex(AudacityProject& project) const
+{
+ if (IsValidIndex(mNavigationIndex, project))
+ return mNavigationIndex;
+ return -1;
}
void LabelTrackView::calculateFontHeight(wxDC & dc)
@@ 997,13 1037,20 @@ void LabelTrackView::calculateFontHeight(wxDC & dc)
mFontHeight += CursorExtraHeight - (charLeading+charDescent);
}
+bool LabelTrackView::IsValidIndex(const Index& index, AudacityProject& project) const
+{
+ if (index == -1)
+ return false;
+ // may make delayed update of mutable mSelIndex after track selection change
+ auto track = FindLabelTrack();
+ if (track->GetSelected() || (TrackFocus::Get(project).Get() == track.get()))
+ return index >= 0 && index < static_cast<int>(track->GetLabels().size());
+ return false;
+}
+
bool LabelTrackView::IsTextSelected( AudacityProject &project ) const
{
- if ( !HasSelection( project ) )
- return false;
- if (mCurrentCursorPos == mInitialCursorPos)
- return false;
- return true;
+ return mCurrentCursorPos != mInitialCursorPos && IsValidIndex(mTextEditIndex, project);
}
/// Cut the selected text in the text box
@@ 1017,10 1064,10 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
const auto &mLabels = pTrack->GetLabels();
wxString left, right;
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto &text = labelStruct.title;
- if (!mSelIndex.IsModified()) {
+ if (!mTextEditIndex.IsModified()) {
mUndoLabel = text;
}
@@ 1043,7 1090,7 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
// set title to the combination of the two remainders
text = left + right;
- pTrack->SetLabel( mSelIndex, labelStruct );
+ pTrack->SetLabel( mTextEditIndex, labelStruct );
// copy data onto clipboard
if (wxTheClipboard->Open()) {
@@ 1055,7 1102,7 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
// set cursor positions
mInitialCursorPos = mCurrentCursorPos = left.length();
- mSelIndex.SetModified(true);
+ mTextEditIndex.SetModified(true);
return true;
}
@@ 1063,13 1110,13 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
/// @return true if text is selected in text box, false otherwise
bool LabelTrackView::CopySelectedText( AudacityProject &project )
{
- if ( !HasSelection( project ) )
+ if (!IsTextSelected(project))
return false;
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- const auto &labelStruct = mLabels[mSelIndex];
+ const auto &labelStruct = mLabels[mTextEditIndex];
int init = mInitialCursorPos;
int cur = mCurrentCursorPos;
@@ 1100,8 1147,8 @@ bool LabelTrackView::PasteSelectedText(
{
const auto pTrack = FindLabelTrack();
- if ( !HasSelection( project ) )
- AddLabel(SelectedRegion(sel0, sel1));
+ if (!IsValidIndex(mTextEditIndex, project))
+ SetTextSelection(AddLabel(SelectedRegion(sel0, sel1)));
wxString text, left, right;
@@ 1114,7 1161,7 @@ bool LabelTrackView::PasteSelectedText(
text = data.GetText();
}
- if (!mSelIndex.IsModified()) {
+ if (!mTextEditIndex.IsModified()) {
mUndoLabel = text;
}
@@ 1127,7 1174,7 @@ bool LabelTrackView::PasteSelectedText(
}
const auto &mLabels = pTrack->GetLabels();
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title;
int cur = mCurrentCursorPos, init = mInitialCursorPos;
if (init > cur)
@@ 1138,11 1185,11 @@ bool LabelTrackView::PasteSelectedText(
title = left + text + right;
- pTrack->SetLabel( mSelIndex, labelStruct );
+ pTrack->SetLabel(mTextEditIndex, labelStruct );
mInitialCursorPos = mCurrentCursorPos = left.length() + text.length();
- mSelIndex.SetModified(true);
+ mTextEditIndex.SetModified(true);
return true;
}
@@ 1152,20 1199,6 @@ bool LabelTrackView::IsTextClipSupported()
return wxTheClipboard->IsSupported(wxDF_UNICODETEXT);
}
-
-int LabelTrackView::GetSelectedIndex( AudacityProject &project ) const
-{
- // may make delayed update of mutable mSelIndex after track selection change
- auto track = FindLabelTrack();
- if ( track->GetSelected() ||
- TrackFocus::Get( project ).Get() == track.get()
- )
- return mSelIndex = std::max( -1,
- std::min<int>( track->GetLabels().size() - 1, mSelIndex ) );
- else
- return mSelIndex = -1;
-}
-
/// TODO: Investigate what happens with large
/// numbers of labels, might need a binary search
/// rather than a linear one.
@@ 1180,11 1213,23 @@ void LabelTrackView::OverGlyph(
//If not over a label, reset it
hit.mMouseOverLabelLeft = -1;
hit.mMouseOverLabelRight = -1;
+ hit.mMouseOverLabel = -1;
hit.mEdge = 0;
const auto pTrack = &track;
const auto &mLabels = pTrack->GetLabels();
{ int i = -1; for (const auto &labelStruct : mLabels) { ++i;
+ // give text box better priority for selecting
+ // reset selection state
+ if (OverTextBox(&labelStruct, x, y))
+ {
+ result = 0;
+ hit.mMouseOverLabel = -1;
+ hit.mMouseOverLabelLeft = -1;
+ hit.mMouseOverLabelRight = -1;
+ break;
+ }
+
//over left or right selection bound
//Check right bound first, since it is drawn after left bound,
//so give it precedence for matching/highlighting.
@@ 1215,13 1260,12 @@ void LabelTrackView::OverGlyph(
result |= 4;
result |= 1;
}
-
- // give text box better priority for selecting
- if(OverTextBox(&labelStruct, x, y))
+ else if (x >= labelStruct.x && x <= labelStruct.x1 &&
+ abs(y - (labelStruct.y + mTextHeight / 2)) < d1)
{
- result = 0;
+ hit.mMouseOverLabel = i;
+ result = 3;
}
-
}}
hit.mEdge = result;
}
@@ 1304,7 1348,7 @@ bool LabelTrackView::DoCaptureKey(
!mLabels.empty())
return true;
- if ( HasSelection( project ) ) {
+ if (IsValidIndex(mTextEditIndex, project) || IsValidIndex(mNavigationIndex, project)) {
if (IsGoodLabelEditKey(event)) {
return true;
}
@@ 1363,10 1407,10 @@ unsigned LabelTrackView::KeyDown(
double bkpSel0 = viewInfo.selectedRegion.t0(),
bkpSel1 = viewInfo.selectedRegion.t1();
- if (!mSelIndex.IsModified() && HasSelection( *project )) {
+ if (IsValidIndex(mTextEditIndex, *project) && !mTextEditIndex.IsModified()) {
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title;
mUndoLabel = title;
}
@@ 1376,12 1420,12 @@ unsigned LabelTrackView::KeyDown(
if (DoKeyDown( *project, viewInfo.selectedRegion, event )) {
ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
XO("Label Edit"),
- mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
+ mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
- mSelIndex.SetModified(true);
+ mTextEditIndex.SetModified(true);
}
- if (!mSelIndex.IsModified()) {
+ if (!mTextEditIndex.IsModified()) {
mUndoLabel.clear();
}
@@ 1409,10 1453,10 @@ unsigned LabelTrackView::Char(
// Pass keystroke to labeltrack's handler and add to history if any
// updates were done
- if (!mSelIndex.IsModified() && HasSelection( *project )) {
+ if (IsValidIndex(mTextEditIndex, *project) && !mTextEditIndex.IsModified()) {
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title;
mUndoLabel = title;
}
@@ 1420,12 1464,12 @@ unsigned LabelTrackView::Char(
if (DoChar( *project, viewInfo.selectedRegion, event )) {
ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
XO("Label Edit"),
- mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
+ mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
- mSelIndex.SetModified(true);
+ mTextEditIndex.SetModified(true);
}
- if (!mSelIndex.IsModified()) {
+ if (!mTextEditIndex.IsModified()) {
mUndoLabel.clear();
}
@@ 1460,8 1504,8 @@ bool LabelTrackView::DoKeyDown(
// All editing keys are only active if we're currently editing a label
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- if ( HasSelection( project ) ) {
- auto labelStruct = mLabels[mSelIndex];
+ if (IsValidIndex(mTextEditIndex, project)) {
+ auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title;
wxUniChar wchar;
bool more=true;
@@ 1486,7 1530,7 @@ bool LabelTrackView::DoKeyDown(
title.erase(mCurrentCursorPos-1, 1);
mCurrentCursorPos--;
if( ((int)wchar > 0xDFFF) || ((int)wchar <0xDC00)){
- pTrack->SetLabel(mSelIndex, labelStruct);
+ pTrack->SetLabel(mTextEditIndex, labelStruct);
more = false;
}
}
@@ 1495,7 1539,8 @@ bool LabelTrackView::DoKeyDown(
else
{
// ELSE no text in text box, so DELETE whole label.
- pTrack->DeleteLabel( mSelIndex );
+ pTrack->DeleteLabel(mTextEditIndex);
+ ResetTextSelection();
}
mInitialCursorPos = mCurrentCursorPos;
updated = true;
@@ 1520,7 1565,7 @@ bool LabelTrackView::DoKeyDown(
wchar = title.at( mCurrentCursorPos );
title.erase(mCurrentCursorPos, 1);
if( ((int)wchar > 0xDBFF) || ((int)wchar <0xD800)){
- pTrack->SetLabel(mSelIndex, labelStruct);
+ pTrack->SetLabel(mTextEditIndex, labelStruct);
more = false;
}
}
@@ 1529,7 1574,8 @@ bool LabelTrackView::DoKeyDown(
else
{
// DELETE whole label if no text in text box
- pTrack->DeleteLabel( mSelIndex );
+ pTrack->DeleteLabel(mTextEditIndex);
+ ResetTextSelection();
}
mInitialCursorPos = mCurrentCursorPos;
updated = true;
@@ 1589,17 1635,18 @@ bool LabelTrackView::DoKeyDown(
break;
case WXK_ESCAPE:
- if (mSelIndex.IsModified()) {
+ if (mTextEditIndex.IsModified()) {
title = mUndoLabel;
- pTrack->SetLabel(mSelIndex, labelStruct);
+ pTrack->SetLabel(mTextEditIndex, labelStruct);
ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"),
- mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
+ mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
}
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
+ case WXK_TAB:
if (mRestoreFocus >= 0) {
auto track = *TrackList::Get( project ).Any()
.begin().advance(mRestoreFocus);
@@ 1607,27 1654,9 @@ bool LabelTrackView::DoKeyDown(
TrackFocus::Get( project ).Set(track);
mRestoreFocus = -2;
}
- mSelIndex = -1;
+ SetNavigationIndex(mTextEditIndex);
+ ResetTextSelection();
break;
-
- case WXK_TAB:
- case WXK_NUMPAD_TAB:
- if (event.ShiftDown()) {
- --mSelIndex;
- } else {
- ++mSelIndex;
- }
-
- mSelIndex = (mSelIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
- {
- const auto &newLabel = mLabels[mSelIndex];
- mCurrentCursorPos = newLabel.title.length();
- mInitialCursorPos = mCurrentCursorPos;
- //Set the selection region to be equal to the selection bounds of the tabbed-to label.
- newSel = newLabel.selectedRegion;
- }
- break;
-
case '\x10': // OSX
case WXK_MENU:
case WXK_WINDOWS_MENU:
@@ 1649,37 1678,61 @@ bool LabelTrackView::DoKeyDown(
case WXK_NUMPAD_TAB:
if (!mLabels.empty()) {
int len = (int) mLabels.size();
- if (event.ShiftDown()) {
- mSelIndex = len - 1;
- if (newSel.t0() > mLabels[0].getT0()) {
- while (mSelIndex >= 0 &&
- mLabels[mSelIndex].getT0() > newSel.t0()) {
- --mSelIndex;
- }
- }
- } else {
- mSelIndex = 0;
- if (newSel.t0() < mLabels[len - 1].getT0()) {
- while (mSelIndex < len &&
- mLabels[mSelIndex].getT0() < newSel.t0()) {
- ++mSelIndex;
- }
- }
+ if (IsValidIndex(mNavigationIndex, project))
+ {
+ if (event.ShiftDown()) {
+ --mNavigationIndex;
+ }
+ else {
+ ++mNavigationIndex;
+ }
+ mNavigationIndex = (mNavigationIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
+ }
+ else
+ {
+ // no valid navigation index, then
+ if (event.ShiftDown()) {
+ //search for the first label starting from the end (and before selection)
+ mNavigationIndex = len - 1;
+ if (newSel.t0() > mLabels[0].getT0()) {
+ while (mNavigationIndex >= 0 &&
+ mLabels[mNavigationIndex].getT0() > newSel.t0()) {
+ --mNavigationIndex;
+ }
+ }
+ }
+ else {
+ //search for the first label starting from the beginning (and after selection)
+ mNavigationIndex = 0;
+ if (newSel.t0() < mLabels[len - 1].getT0()) {
+ while (mNavigationIndex < len &&
+ mLabels[mNavigationIndex].getT0() < newSel.t0()) {
+ ++mNavigationIndex;
+ }
+ }
+ }
}
- if (mSelIndex >= 0 && mSelIndex < len) {
- const auto &labelStruct = mLabels[mSelIndex];
+ if (mNavigationIndex >= 0 && mNavigationIndex < len) {
+ const auto &labelStruct = mLabels[mNavigationIndex];
mCurrentCursorPos = labelStruct.title.length();
mInitialCursorPos = mCurrentCursorPos;
//Set the selection region to be equal to the selection bounds of the tabbed-to label.
newSel = labelStruct.selectedRegion;
}
else {
- mSelIndex = -1;
+ mNavigationIndex = -1;
}
}
break;
-
+ case WXK_RETURN:
+ case WXK_NUMPAD_ENTER:
+ //pressing Enter key activates editing of the label
+ //pointed to by mNavigationIndex (if valid)
+ if (IsValidIndex(mNavigationIndex, project)) {
+ SetTextSelection(mNavigationIndex);
+ }
+ break;
default:
if (!IsGoodLabelFirstKey(event)) {
event.Skip();
@@ 1688,9 1741,6 @@ bool LabelTrackView::DoKeyDown(
}
}
- // Make sure the caret is visible
- mDrawCursor = true;
-
return updated;
}
@@ 1711,7 1761,7 @@ bool LabelTrackView::DoChar(
}
// Only track true changes to the label
- bool updated = false;
+ //bool updated = false;
// Cache the character
wxChar charCode = event.GetUnicodeKey();
@@ 1724,7 1774,7 @@ bool LabelTrackView::DoChar(
// If we've reached this point and aren't currently editing, add NEW label
const auto pTrack = FindLabelTrack();
- if ( !HasSelection( project ) ) {
+ if (!IsValidIndex(mTextEditIndex, project)) {
// Don't create a NEW label for a space
if (wxIsspace(charCode)) {
event.Skip();
@@ 1754,6 1804,9 @@ bool LabelTrackView::DoChar(
}
}
+ if (!IsValidIndex(mTextEditIndex, project))
+ return false;
+
//
// Now we are definitely in a label; append the incoming character
//
@@ 1763,7 1816,7 @@ bool LabelTrackView::DoChar(
RemoveSelectedText();
const auto& mLabels = pTrack->GetLabels();
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto& title = labelStruct.title;
if (mCurrentCursorPos < (int)title.length()) {
@@ 1780,16 1833,12 @@ bool LabelTrackView::DoChar(
//append charCode
title += charCode;
- pTrack->SetLabel( mSelIndex, labelStruct );
+ pTrack->SetLabel(mTextEditIndex, labelStruct );
//moving cursor position forward
mInitialCursorPos = ++mCurrentCursorPos;
- updated = true;
-
- // Make sure the caret is visible
- mDrawCursor = true;
-
- return updated;
+
+ return true;
}
enum
@@ 1829,13 1878,12 @@ void LabelTrackView::ShowContextMenu( AudacityProject &project )
menu.Enable(OnDeleteSelectedLabelID, true);
menu.Enable(OnEditSelectedLabelID, true);
- if( !HasSelection( project ) ) {
- wxASSERT( false );
+ if(!IsValidIndex(mTextEditIndex, project)) {
return;
}
const auto pTrack = FindLabelTrack();
- const LabelStruct *ls = pTrack->GetLabel(mSelIndex);
+ const LabelStruct *ls = pTrack->GetLabel(mTextEditIndex);
wxClientDC dc(parent);
@@ 1875,7 1923,7 @@ void LabelTrackView::OnContextMenu(
{
ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"),
- mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
+ mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
}
break;
@@ 1891,17 1939,16 @@ void LabelTrackView::OnContextMenu(
{
ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"),
- mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
+ mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
}
break;
/// DELETE selected label
case OnDeleteSelectedLabelID: {
- int ndx = GetLabelIndex(selectedRegion.t0(), selectedRegion.t1());
- if (ndx != -1)
+ if (IsValidIndex(mTextEditIndex, project))
{
const auto pTrack = FindLabelTrack();
- pTrack->DeleteLabel(ndx);
+ pTrack->DeleteLabel(mTextEditIndex);
ProjectHistory::Get( project ).PushState(XO("Deleted Label"),
XO("Label Edit"),
UndoPush::CONSOLIDATE);
@@ 1911,7 1958,8 @@ void LabelTrackView::OnContextMenu(
case OnEditSelectedLabelID: {
// Bug #2571: See above
- mEditIndex = GetLabelIndex(selectedRegion.t0(), selectedRegion.t1());
+ if (IsValidIndex(mTextEditIndex, project))
+ mEditIndex = mTextEditIndex;
}
break;
}
@@ 1928,7 1976,7 @@ void LabelTrackView::RemoveSelectedText()
const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels();
- auto labelStruct = mLabels[mSelIndex];
+ auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title;
if (init > 0)
@@ 1938,16 1986,16 @@ void LabelTrackView::RemoveSelectedText()
right = title.Mid(cur);
title = left + right;
- pTrack->SetLabel( mSelIndex, labelStruct );
+ pTrack->SetLabel( mTextEditIndex, labelStruct );
mInitialCursorPos = mCurrentCursorPos = left.length();
}
-
-bool LabelTrackView::HasSelection( AudacityProject &project ) const
+/*
+bool LabelTrackView::HasSelectedLabel( AudacityProject &project ) const
{
- const auto selIndex = GetSelectedIndex( project );
+ const auto selIndex = GetSelectionIndex( project );
return (selIndex >= 0 &&
selIndex < (int)FindLabelTrack()->GetLabels().size());
-}
+}*/
int LabelTrackView::GetLabelIndex(double t, double t1)
{
@@ 1999,21 2047,10 @@ void LabelTrackView::OnLabelAdded( LabelTrackEvent &e )
// -1 means we don't need to restore it to anywhere.
// 0 or above is the track to restore to after editing the label is complete.
if( mRestoreFocus >= -1 )
- mSelIndex = pos;
+ mTextEditIndex = pos;
if( mRestoreFocus < 0 )
mRestoreFocus = -2;
-
- // Make sure the caret is visible
- //
- // LLL: The cursor will not be drawn when the first label
- // is added since mDrawCursor will be false. Presumably,
- // if the user adds a label, then a cursor should be drawn
- // to indicate that typing is expected.
- //
- // If the label is added during actions like import, then the
- // mDrawCursor flag will be reset once the action is complete.
- mDrawCursor = true;
}
void LabelTrackView::OnLabelDeleted( LabelTrackEvent &e )
@@ 2026,17 2063,13 @@ void LabelTrackView::OnLabelDeleted( LabelTrackEvent &e )
// IF we've deleted the selected label
// THEN set no label selected.
- if( mSelIndex== index )
- {
- mSelIndex = -1;
- mCurrentCursorPos = 1;
- }
+ if (mTextEditIndex == index)
+ ResetTextSelection();
+
// IF we removed a label before the selected label
// THEN the NEW selected label number is one less.
- else if( index < mSelIndex )
- {
- --mSelIndex;
- }
+ else if( index < mTextEditIndex)
+ --mTextEditIndex;//NB: Keep cursor selection region
}
void LabelTrackView::OnLabelPermuted( LabelTrackEvent &e )
@@ 2048,12 2081,16 @@ void LabelTrackView::OnLabelPermuted( LabelTrackEvent &e )
auto former = e.mFormerPosition;
auto present = e.mPresentPosition;
- if ( mSelIndex == former )
- mSelIndex = present;
- else if ( former < mSelIndex && mSelIndex <= present )
- -- mSelIndex;
- else if ( former > mSelIndex && mSelIndex >= present )
- ++ mSelIndex;
+ auto fix = [&](Index& index) {
+ if (index == former)
+ index = present;
+ else if (former < index && index <= present)
+ --index;
+ else if (former > index && index >= present)
+ ++index;
+ };
+ fix(mNavigationIndex);
+ fix(mTextEditIndex);
}
void LabelTrackView::OnSelectionChange( LabelTrackEvent &e )
@@ 2062,8 2099,11 @@ void LabelTrackView::OnSelectionChange( LabelTrackEvent &e )
if ( e.mpTrack.lock() != FindTrack() )
return;
- if ( !FindTrack()->GetSelected() )
- mSelIndex = -1;
+ if (!FindTrack()->GetSelected())
+ {
+ SetNavigationIndex(-1);
+ ResetTextSelection();
+ }
}
wxBitmap & LabelTrackView::GetGlyph( int i)
@@ 2213,9 2253,10 @@ int LabelTrackView::DialogForLabelName(
trackPanel.FindTrackRect( trackFocus.Get() ).GetBottomLeft();
// The start of the text in the text box will be roughly in line with the label's position
// if it's a point label, or the start of its region if it's a region label.
- position.x += viewInfo.GetLabelWidth()
- + std::max(0, static_cast<int>(viewInfo.TimeToPosition(region.t0())))
- -40;
+ position.x +=
+ + std::max(0, static_cast<int>(viewInfo.TimeToPosition(
+ viewInfo.GetLeftOffset(), region.t0())))
+ - 39;
position.y += 2; // just below the bottom of the track
position = trackPanel.ClientToScreen(position);
auto &window = GetProjectFrame( project );
M src/tracks/labeltrack/ui/LabelTrackView.h => src/tracks/labeltrack/ui/LabelTrackView.h +27 -21
@@ 44,8 44,11 @@ class TENACITY_DLL_API LabelTrackView final : public CommonTrackView
void Reparent( const std::shared_ptr<Track> &parent ) override;
public:
- enum : int { DefaultFontSize = 12 };
-
+ enum : int { DefaultFontSize = 0 }; //system preferred
+ static constexpr int TextFramePadding { 2 };
+ static constexpr int TextFrameYOffset { -1 };
+ static constexpr int LabelBarHeight { 6 };
+
explicit
LabelTrackView( const std::shared_ptr<Track> &pTrack );
~LabelTrackView() override;
@@ 109,9 112,6 @@ public:
void Draw( TrackPanelDrawingContext &context, const wxRect & r ) const;
- int GetSelectedIndex( AudacityProject &project ) const;
- void SetSelectedIndex( int index );
-
bool CutSelectedText( AudacityProject &project );
bool CopySelectedText( AudacityProject &project );
bool PasteSelectedText(
@@ 143,19 143,13 @@ private:
public:
struct Flags {
int mInitialCursorPos, mCurrentCursorPos;
- Index mSelIndex{-1};
- bool mDrawCursor;
+ Index mNavigationIndex;
+ Index mTextEditIndex;
wxString mUndoLabel;
};
void ResetFlags();
- Flags SaveFlags() const
- {
- return {
- mInitialCursorPos, mCurrentCursorPos, mSelIndex,
- mDrawCursor, mUndoLabel
- };
- }
+ Flags SaveFlags() const;
void RestoreFlags( const Flags& flags );
static int OverATextBox( const LabelTrack &track, int xx, int yy );
@@ 187,7 181,11 @@ public:
private:
void OnContextMenu( AudacityProject &project, wxCommandEvent & evt);
- mutable Index mSelIndex{-1}; /// Keeps track of the currently selected label
+ /// Keeps track of the currently selected label (not same as selection region)
+ /// used for navigation between labels
+ mutable Index mNavigationIndex{ -1 };
+ /// Index of the current label text beeing edited
+ mutable Index mTextEditIndex{ -1 };
mutable wxString mUndoLabel;
@@ 202,8 200,7 @@ private:
mutable int mCurrentCursorPos; /// current cursor position
mutable int mInitialCursorPos; /// initial cursor position
- mutable bool mDrawCursor; /// flag to tell if drawing the
- /// cursor or not
+
int mRestoreFocus{-2}; /// Restore focus to this track
/// when done editing
@@ 212,18 209,28 @@ private:
static void DrawLines( wxDC & dc, const LabelStruct &ls, const wxRect & r);
static void DrawGlyphs( wxDC & dc, const LabelStruct &ls, const wxRect & r,
int GlyphLeft, int GlyphRight);
+ static int GetTextFrameHeight();
static void DrawText( wxDC & dc, const LabelStruct &ls, const wxRect & r);
static void DrawTextBox( wxDC & dc, const LabelStruct &ls, const wxRect & r);
+ static void DrawBar(wxDC& dc, const LabelStruct& ls, const wxRect& r);
static void DrawHighlight(
wxDC & dc, const LabelStruct &ls, int xPos1, int xPos2, int charHeight);
public:
/// convert pixel coordinate to character position in text box
- int FindCursorPosition(wxCoord xPos);
+ int FindCursorPosition(int labelIndex, wxCoord xPos);
int GetCurrentCursorPosition() const { return mCurrentCursorPos; }
void SetCurrentCursorPosition(int pos);
int GetInitialCursorPosition() const { return mInitialCursorPos; }
- void SetTextHighlight( int initialPosition, int currentPosition );
+
+ /// Sets the label with specified index for editing,
+ /// optionaly selection may be specified with [start, end]
+ void SetTextSelection(int labelIndex, int start = 1, int end = 1);
+ int GetTextEditIndex(AudacityProject& project) const;
+ void ResetTextSelection();
+
+ void SetNavigationIndex(int index);
+ int GetNavigationIndex(AudacityProject& project) const;
private:
@@ 234,8 241,7 @@ private:
static void calculateFontHeight(wxDC & dc);
-public:
- bool HasSelection( AudacityProject &project ) const;
+ bool IsValidIndex(const Index& index, AudacityProject& project) const;
private:
void RemoveSelectedText();
M src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp => src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp +6 -2
@@ 76,9 76,13 @@ std::shared_ptr<TrackVRulerControls> NoteTrackView::DoGetVRulerControls()
#define TIME_TO_X(t) (zoomInfo.TimeToPosition((t), rect.x))
#define X_TO_TIME(xx) (zoomInfo.PositionToTime((xx), rect.x))
-std::shared_ptr<CommonTrackCell> NoteTrackView::DoGetAffordanceControls()
+std::shared_ptr<CommonTrackCell> NoteTrackView::GetAffordanceControls()
{
- return std::make_shared<NoteTrackAffordanceControls>(DoFindTrack());
+ if (mpAffordanceCellControl == nullptr)
+ {
+ mpAffordanceCellControl = std::make_shared<NoteTrackAffordanceControls>(DoFindTrack());
+ }
+ return mpAffordanceCellControl;
}
namespace {
M src/tracks/playabletrack/notetrack/ui/NoteTrackView.h => src/tracks/playabletrack/notetrack/ui/NoteTrackView.h +3 -1
@@ 25,7 25,7 @@ public:
private:
std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() override;
- std::shared_ptr<CommonTrackCell> DoGetAffordanceControls() override;
+ std::shared_ptr<CommonTrackCell> GetAffordanceControls() override;
std::vector<UIHandlePtr> DetailedHitTest
(const TrackPanelMouseState &state,
@@ 36,5 36,7 @@ private:
void Draw(
TrackPanelDrawingContext &context,
const wxRect &rect, unsigned iPass ) override;
+
+ std::shared_ptr<CommonTrackCell> mpAffordanceCellControl;
};
#endif
M src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp => src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp +21 -5
@@ 16,6 16,7 @@
#include "../../../../TrackPanelMouseEvent.h"
#include "../../../../TrackArtist.h"
#include "../../../../TrackPanelDrawingContext.h"
+#include "../../../../TrackPanelResizeHandle.h"
#include "../../../../ViewInfo.h"
#include "../../../../WaveTrack.h"
#include "../../../../WaveClip.h"
@@ 34,14 35,29 @@ std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMo
std::vector<UIHandlePtr> results;
- const auto track = FindTrack();
-
- const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
+ auto px = state.state.m_x;
+ auto py = state.state.m_y;
const auto rect = state.rect;
- auto px = state.state.m_x;
- auto py = state.state.m_y;
+ const auto track = FindTrack();
+
+ auto trackList = track->GetOwner();
+ if ((std::abs(rect.GetTop() - py) <= WaveTrackView::kChannelSeparatorThickness / 2)
+ && trackList
+ && !track->IsLeader())
+ {
+ //given that track is not a leader there always should be
+ //another track before this one
+ auto prev = --trackList->Find(track.get());
+ auto result = std::static_pointer_cast<UIHandle>(
+ std::make_shared<TrackPanelResizeHandle>((*prev)->shared_from_this(), py)
+ );
+ result = AssignUIHandlePtr(mResizeHandle, result);
+ results.push_back(result);
+ }
+
+ const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
auto& zoomInfo = ViewInfo::Get(*pProject);
for (const auto& clip : waveTrack->GetClips())
M src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h => src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h +1 -0
@@ 19,6 19,7 @@ class TENACITY_DLL_API WaveTrackAffordanceControls : public CommonTrackCell
{
std::weak_ptr<WaveClip> mFocusClip;
std::weak_ptr<AffordanceHandle> mAffordanceHandle;
+ std::weak_ptr<UIHandle> mResizeHandle;
public:
WaveTrackAffordanceControls(const std::shared_ptr<Track>& pTrack);
M src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp => src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp +9 -9
@@ 828,7 828,7 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
((TrackView::Get( *pTrack ).GetMinimized()) &&
(TrackView::Get( *partner ).GetMinimized()));
- tracks.GroupChannels( *pTrack, 2 );
+ tracks.MakeMultiChannelTrack( *pTrack, 2, false );
// Set partner's parameters to match target.
partner->Merge(*pTrack);
@@ 843,8 843,8 @@ void WaveTrackMenuTable::OnMergeStereo(wxCommandEvent &)
view.SetMinimized(false);
partnerView.SetMinimized(false);
int AverageHeight = (view.GetHeight() + partnerView.GetHeight()) / 2;
- view.SetHeight(AverageHeight);
- partnerView.SetHeight(AverageHeight);
+ view.SetExpandedHeight(AverageHeight);
+ partnerView.SetExpandedHeight(AverageHeight);
view.SetMinimized(bBothMinimizedp);
partnerView.SetMinimized(bBothMinimizedp);
@@ 879,17 879,17 @@ void WaveTrackMenuTable::SplitStereo(bool stereo)
//make sure no channel is smaller than its minimum height
if (view.GetHeight() < view.GetMinimizedHeight())
- view.SetHeight(view.GetMinimizedHeight());
+ view.SetExpandedHeight(view.GetMinimizedHeight());
totalHeight += view.GetHeight();
++nChannels;
}
- TrackList::Get( *project ).GroupChannels( *pTrack, 1 );
+ TrackList::Get( *project ).UnlinkChannels( *pTrack );
int averageHeight = totalHeight / nChannels;
for (auto channel : channels)
// Make tracks the same height
- TrackView::Get( *channel ).SetHeight( averageHeight );
+ TrackView::Get( *channel ).SetExpandedHeight( averageHeight );
}
/// Swap the left and right channels of a stero track...
@@ 898,6 898,7 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
AudacityProject *const project = &mpData->project;
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
+ const auto linkType = pTrack->GetLinkType();
auto channels = TrackList::Channels( pTrack );
if (channels.size() != 2)
return;
@@ 911,9 912,8 @@ void WaveTrackMenuTable::OnSwapChannels(wxCommandEvent &)
SplitStereo(false);
auto &tracks = TrackList::Get( *project );
- tracks.MoveUp( partner );
- tracks.GroupChannels( *partner, 2 );
-
+ tracks.MoveUp(partner);
+ tracks.MakeMultiChannelTrack(*partner, 2, linkType == Track::LinkType::Aligned);
if (hasFocus)
trackFocus.Set(partner);
M src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp => src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +97 -26
@@ 31,6 31,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../../TrackArtist.h"
#include "../../../../TrackPanelDrawingContext.h"
#include "../../../../TrackPanelMouseEvent.h"
+#include "../../../../TrackPanelResizeHandle.h"
#include "../../../../ViewInfo.h"
#include "../../../../prefs/TracksPrefs.h"
@@ 703,6 704,49 @@ std::pair<
mCloseHandle,
*pWaveTrackView, *this, state ) )
results.second.push_back( pHandle );
+
+ auto channels = TrackList::Channels(wt.get());
+ if(channels.size() > 1) {
+ // Only one cell is tested and we need to know
+ // which one and it's relative location to the border.
+ auto subviews = pWaveTrackView->GetSubViews();
+ auto currentSubview = std::find_if(subviews.begin(), subviews.end(),
+ [self = shared_from_this()](const auto& p){
+ return self == p.second;
+ });
+ if (currentSubview != subviews.end())
+ {
+ auto currentSubviewIndex = std::distance(subviews.begin(), currentSubview);
+
+ const auto py = state.state.GetY();
+ const auto topBorderHit = std::abs(py - state.rect.GetTop())
+ <= WaveTrackView::kChannelSeparatorThickness / 2;
+ const auto bottomBorderHit = std::abs(py - state.rect.GetBottom())
+ <= WaveTrackView::kChannelSeparatorThickness / 2;
+
+ auto currentChannel = channels.find(wt.get());
+ auto currentChannelIndex = std::distance(channels.begin(), currentChannel);
+
+ if (//for not-last-view check the bottom border hit
+ ((currentChannelIndex != channels.size() - 1)
+ && (currentSubviewIndex == static_cast<int>(subviews.size()) - 1)
+ && bottomBorderHit)
+ ||
+ //or for not-first-view check the top border hit
+ ((currentChannelIndex != 0) && currentSubviewIndex == 0 && topBorderHit))
+ {
+ //depending on which border hit test succeeded on we
+ //need to choose a proper target for resizing
+ auto it = bottomBorderHit ? currentChannel : currentChannel.advance(-1);
+ auto result = std::static_pointer_cast<UIHandle>(
+ std::make_shared<TrackPanelResizeHandle>((*it)->shared_from_this(), py)
+ );
+ result = AssignUIHandlePtr(mResizeHandle, result);
+ results.second.push_back(result);
+ }
+ }
+ }
+
if ( auto pHandle = SubViewAdjustHandle::HitTest(
mAdjustHandle,
*pWaveTrackView, *this, state ) )
@@ 965,6 1009,11 @@ void WaveTrackView::DoSetDisplay(Display display, bool exclusive)
auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
{
+ return GetSubViews(&rect);
+}
+
+auto WaveTrackView::GetSubViews(const wxRect* rect) -> Refinement
+{
BuildSubViews();
// Collect the visible views in the right sequence
@@ 974,41 1023,51 @@ auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
std::vector< Item > items;
size_t ii = 0;
float total = 0;
- WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
- auto &placement = mPlacements[ii];
+ WaveTrackSubViews::ForEach([&](WaveTrackSubView& subView) {
+ auto& placement = mPlacements[ii];
auto index = placement.index;
auto fraction = placement.fraction;
- if ( index >= 0 && fraction > 0.0 )
+ if (index >= 0 && fraction > 0.0)
total += fraction,
- items.push_back( { index, fraction, subView.shared_from_this() } );
+ items.push_back({ index, fraction, subView.shared_from_this() });
++ii;
- } );
- std::sort( items.begin(), items.end(), [](const Item &a, const Item &b){
+ });
+ std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
return a.index < b.index;
- } );
+ });
// Remove views we don't need
auto begin = items.begin(), end = items.end(),
- newEnd = std::remove_if( begin, end,
- []( const Item &item ){ return !item.pView; } );
- items.erase( newEnd, end );
+ newEnd = std::remove_if(begin, end,
+ [](const Item& item) { return !item.pView; });
+ items.erase(newEnd, end);
- // Assign coordinates, redenominating to the total height,
- // storing integer values
Refinement results;
- results.reserve( items.size() );
- const auto top = rect.GetTop();
- const auto height = rect.GetHeight();
- float partial = 0;
- wxCoord lastCoord = 0;
- for ( const auto &item : items ) {
- wxCoord newCoord = top + (partial / total) * height;
- results.emplace_back( newCoord, item.pView );
- partial += item.fraction;
- }
- // Cache for the use of sub-view dragging
- mLastHeight = height;
+ if (rect != nullptr)
+ {
+ // Assign coordinates, redenominating to the total height,
+ // storing integer values
+ results.reserve(items.size());
+ const auto top = rect->GetTop();
+ const auto height = rect->GetHeight();
+ float partial = 0;
+ wxCoord lastCoord = 0;
+ for (const auto& item : items) {
+ wxCoord newCoord = top + (partial / total) * height;
+ results.emplace_back(newCoord, item.pView);
+ partial += item.fraction;
+ }
+
+ // Cache for the use of sub-view dragging
+ mLastHeight = height;
+ }
+ else
+ {
+ std::transform(items.begin(), items.end(), std::back_inserter(results), [](const auto& item) {
+ return std::make_pair(0, item.pView);
+ });
+ }
return results;
}
@@ 1026,9 1085,14 @@ WaveTrackView::GetAllSubViews()
return results;
}
-std::shared_ptr<CommonTrackCell> WaveTrackView::DoGetAffordanceControls()
+std::shared_ptr<CommonTrackCell> WaveTrackView::GetAffordanceControls()
{
- return std::make_shared<WaveTrackAffordanceControls>(FindTrack());
+ auto track = FindTrack();
+ if (!track->IsAlignedWithLeader())
+ {
+ return DoGetAffordance(track);
+ }
+ return {};
}
void WaveTrackView::DoSetMinimized( bool minimized )
@@ 1042,6 1106,13 @@ void WaveTrackView::DoSetMinimized( bool minimized )
} );
}
+std::shared_ptr<CommonTrackCell> WaveTrackView::DoGetAffordance(const std::shared_ptr<Track>& track)
+{
+ if (mpAffordanceCellControl == nullptr)
+ mpAffordanceCellControl = std::make_shared<WaveTrackAffordanceControls>(track);
+ return mpAffordanceCellControl;
+}
+
using DoGetWaveTrackView = DoGetView::Override< WaveTrack >;
template<> template<> auto DoGetWaveTrackView::Implementation() -> Function {
return [](WaveTrack &track) {
M src/tracks/playabletrack/wavetrack/ui/WaveTrackView.h => src/tracks/playabletrack/wavetrack/ui/WaveTrackView.h +19 -3
@@ 52,6 52,7 @@ protected:
private:
std::weak_ptr<UIHandle> mCloseHandle;
+ std::weak_ptr<UIHandle> mResizeHandle;
std::weak_ptr<UIHandle> mAdjustHandle;
std::weak_ptr<UIHandle> mRearrangeHandle;
std::weak_ptr<CutlineHandle> mCutlineHandle;
@@ 77,6 78,8 @@ class TENACITY_DLL_API WaveTrackView final
WaveTrackView &operator=( const WaveTrackView& ) = delete;
public:
+ static constexpr int kChannelSeparatorThickness{ 8 };
+
using Display = WaveTrackViewConstants::Display;
static WaveTrackView &Get( WaveTrack &track );
@@ 128,6 131,15 @@ public:
std::weak_ptr<WaveClip> GetSelectedClip();
+ // Returns a visible subset of subviews, sorted in the same
+ // order as they are supposed to be displayed
+
+
+ // Get the visible sub-views,
+ // if rect is provided then result will contain
+ // y coordinate for each subview within this rect
+ Refinement GetSubViews(const wxRect* rect = nullptr);
+
private:
void BuildSubViews() const;
void DoSetDisplay(Display display, bool exclusive = true);
@@ 143,11 155,10 @@ private:
override;
// TrackView implementation
- // Get the visible sub-views with top y coordinates
- Refinement GetSubViews( const wxRect &rect ) override;
+ Refinement GetSubViews(const wxRect& rect) override;
protected:
- std::shared_ptr<CommonTrackCell> DoGetAffordanceControls() override;
+ std::shared_ptr<CommonTrackCell> GetAffordanceControls() override;
void DoSetMinimized( bool minimized ) override;
@@ 158,6 169,11 @@ protected:
mutable wxCoord mLastHeight{};
bool mMultiView{ false };
+
+private:
+ std::shared_ptr<CommonTrackCell> DoGetAffordance(const std::shared_ptr<Track>& track);
+
+ std::shared_ptr<CommonTrackCell> mpAffordanceCellControl;
};
// Helper for drawing routines
M src/tracks/ui/BackgroundCell.cpp => src/tracks/ui/BackgroundCell.cpp +6 -6
@@ 19,6 19,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../Track.h"
#include "../../TrackArtist.h"
#include "../../TrackPanel.h"
+#include "../../TrackPanelConstants.h"
#include "../../TrackPanelDrawingContext.h"
#include "../../TrackPanelMouseEvent.h"
#include "../../UIHandle.h"
@@ 37,6 38,9 @@ class BackgroundHandle : public UIHandle
public:
BackgroundHandle() {}
+ BackgroundHandle(BackgroundHandle&&) = default;
+ BackgroundHandle& operator=(BackgroundHandle&&) = default;
+
static HitTestPreview HitPreview()
{
static wxCursor arrowCursor{ wxCURSOR_ARROW };
@@ 108,12 112,8 @@ std::vector<UIHandlePtr> BackgroundCell::HitTest
(const TrackPanelMouseState &,
const AudacityProject *)
{
- std::vector<UIHandlePtr> results;
- auto result = mHandle.lock();
- if (!result)
- result = std::make_shared<BackgroundHandle>();
- results.push_back(result);
- return results;
+ auto result = AssignUIHandlePtr(mHandle, std::make_shared<BackgroundHandle>());
+ return { result };
}
std::shared_ptr<Track> BackgroundCell::DoFindTrack()
M src/tracks/ui/EnvelopeHandle.cpp => src/tracks/ui/EnvelopeHandle.cpp +2 -2
@@ 43,9 43,9 @@ EnvelopeHandle::~EnvelopeHandle()
{}
UIHandlePtr EnvelopeHandle::HitAnywhere
-(std::weak_ptr<EnvelopeHandle> & WXUNUSED(holder), Envelope *envelope, bool timeTrack)
+(std::weak_ptr<EnvelopeHandle> &holder, Envelope *envelope, bool timeTrack)
{
- auto result = std::make_shared<EnvelopeHandle>( envelope );
+ auto result = AssignUIHandlePtr(holder, std::make_shared<EnvelopeHandle>(envelope));
result->mTimeTrack = timeTrack;
return result;
}
M src/tracks/ui/EnvelopeHandle.h => src/tracks/ui/EnvelopeHandle.h +3 -0
@@ 38,6 38,9 @@ class TENACITY_DLL_API EnvelopeHandle final : public UIHandle
public:
explicit EnvelopeHandle( Envelope *pEnvelope );
+
+ EnvelopeHandle(EnvelopeHandle&&) = default;
+ EnvelopeHandle& operator=(EnvelopeHandle&&) = default;
virtual ~EnvelopeHandle();
M src/tracks/ui/TimeShiftHandle.cpp => src/tracks/ui/TimeShiftHandle.cpp +2 -0
@@ 961,6 961,8 @@ UIHandle::Result TimeShiftHandle::Release
if (mDidSlideVertically) {
msg = XO("Moved clips to another track");
consolidate = false;
+ for (auto& pair : mClipMoveState.shifters)
+ pair.first->LinkConsistencyCheck();
}
else {
msg = ( mClipMoveState.hSlideAmount > 0
M src/tracks/ui/TrackView.cpp => src/tracks/ui/TrackView.cpp +10 -15
@@ 41,7 41,7 @@ int TrackView::GetCumulativeHeight( const Track *pTrack )
if ( !pTrack )
return 0;
auto &view = Get( *pTrack );
- return view.GetY() + view.GetHeight();
+ return view.GetCumulativeHeightBefore() + view.GetHeight();
}
int TrackView::GetTotalHeight( const TrackList &list )
@@ 87,7 87,7 @@ void TrackView::SetMinimized(bool isMinimized)
void TrackView::WriteXMLAttributes( XMLWriter &xmlFile ) const
{
- xmlFile.WriteAttr(wxT("height"), GetActualHeight());
+ xmlFile.WriteAttr(wxT("height"), GetExpandedHeight());
xmlFile.WriteAttr(wxT("minimized"), GetMinimized());
}
@@ 101,7 101,7 @@ bool TrackView::HandleXMLAttribute( const wxChar *attr, const wxChar *value )
// will stall Audacity as it tries to create an enormous vertical ruler.
// So clamp to reasonable values.
nValue = std::max( 40l, std::min( nValue, 1000l ));
- SetHeight(nValue);
+ SetExpandedHeight(nValue);
return true;
}
else if (!wxStrcmp(attr, wxT("minimized")) &&
@@ 141,13 141,6 @@ std::shared_ptr<const TrackVRulerControls> TrackView::GetVRulerControls() const
return const_cast< TrackView* >( this )->GetVRulerControls();
}
-std::shared_ptr<CommonTrackCell> TrackView::GetAffordanceControls()
-{
- if (!mpAffordanceCellControl)
- mpAffordanceCellControl = DoGetAffordanceControls();
- return mpAffordanceCellControl;
-}
-
void TrackView::DoSetY(int y)
{
mY = y;
@@ 161,7 154,7 @@ int TrackView::GetHeight() const
return mHeight;
}
-void TrackView::SetHeight(int h)
+void TrackView::SetExpandedHeight(int h)
{
DoSetHeight(h);
FindTrack()->AdjustPositions();
@@ 172,15 165,17 @@ void TrackView::DoSetHeight(int h)
mHeight = h;
}
-std::shared_ptr<CommonTrackCell> TrackView::DoGetAffordanceControls()
+std::shared_ptr<CommonTrackCell> TrackView::GetAffordanceControls()
{
return {};
}
namespace {
-// Attach an object to each project. It receives track list events and updates
-// track Y coordinates
+/*!
+ Attached to each project, it receives track list events and maintains the
+ cache of cumulative track view heights for use by TrackPanel.
+ */
struct TrackPositioner final : ClientData::Base, wxEvtHandler
{
AudacityProject &mProject;
@@ 214,7 209,7 @@ struct TrackPositioner final : ClientData::Base, wxEvtHandler
while( auto pTrack = *iter ) {
auto &view = TrackView::Get( *pTrack );
- view.SetY( yy );
+ view.SetCumulativeHeightBefore( yy );
yy += view.GetHeight();
++iter;
}
M src/tracks/ui/TrackView.h => src/tracks/ui/TrackView.h +33 -11
@@ 49,22 49,45 @@ public:
bool GetMinimized() const { return mMinimized; }
void SetMinimized( bool minimized );
- int GetY() const { return mY; }
- int GetActualHeight() const { return mHeight; }
+ //! @return cached sum of `GetHeight()` of all preceding tracks
+ int GetCumulativeHeightBefore() const { return mY; }
+
+ //! @return height of the track when expanded
+ /*! See other comments for GetHeight */
+ int GetExpandedHeight() const { return mHeight; }
+
+ //! @return height of the track when collapsed
+ /*! See other comments for GetHeight */
virtual int GetMinimizedHeight() const = 0;
+
+ //! @return height of the track as it now appears, expanded or collapsed
+ /*!
+ Total "height" of channels of a track includes padding areas above and
+ below it, and is pixel-accurate for the channel group.
+ The "heights" of channels within a group determine the proportions of
+ heights of the track data shown -- but the actual total pixel heights
+ may differ when other fixed-height adornments and paddings are added,
+ according to other rules for allocation of height.
+ */
int GetHeight() const;
- void SetY(int y) { DoSetY( y ); }
- void SetHeight(int height);
+ //! Set cached value dependent on position within the track list
+ void SetCumulativeHeightBefore(int y) { DoSetY( y ); }
+
+ /*! Sets height for expanded state.
+ Does not expand a track if it is now collapsed.
+ See other comments for GetHeight
+ */
+ void SetExpandedHeight(int height);
// Return another, associated TrackPanelCell object that implements the
// mouse actions for the vertical ruler
std::shared_ptr<TrackVRulerControls> GetVRulerControls();
std::shared_ptr<const TrackVRulerControls> GetVRulerControls() const;
- // by default returns nullptr, meaning that track has no drag controls area
- std::shared_ptr<CommonTrackCell> GetAffordanceControls();
-
+ // Returns cell that would be used at affordance area, by default returns nullptr,
+ // meaning that track has no such area.
+ virtual std::shared_ptr<CommonTrackCell> GetAffordanceControls();
void WriteXMLAttributes( XMLWriter & ) const override;
bool HandleXMLAttribute( const wxChar *attr, const wxChar *value ) override;
@@ 81,21 104,20 @@ public:
virtual void DoSetMinimized( bool isMinimized );
-protected:
+private:
// No need yet to make this virtual
void DoSetY(int y);
void DoSetHeight(int h);
+protected:
+
// Private factory to make appropriate object; class TrackView handles
// memory management thereafter
virtual std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() = 0;
- // May return nullptr (which is default) if track does not need affordance area
- virtual std::shared_ptr<CommonTrackCell> DoGetAffordanceControls();
std::shared_ptr<TrackVRulerControls> mpVRulerControls;
- std::shared_ptr<CommonTrackCell> mpAffordanceCellControl;
private:
bool mMinimized{ false };