diff --git a/src/Track.cpp b/src/Track.cpp index c519b8283..e353980bb 100644 --- a/src/Track.cpp +++ b/src/Track.cpp @@ -600,7 +600,11 @@ VisibleTrackIterator::VisibleTrackIterator(AudacityProject *project) bool VisibleTrackIterator::Condition(Track *t) { wxRect r(0, t->GetY(), 1, t->GetHeight()); - return r.Intersects(mPanelRect); + if( r.Intersects(mPanelRect) ) + return true; + auto partner = t->GetLink(); + if ( partner && t->GetLinked() ) + return Condition( partner ); } // SyncLockedTracksIterator diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 4e9fa3490..fd96c97b7 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -825,7 +825,9 @@ void TrackPanel::HandlePageDownKey() void TrackPanel::HandleCursorForLastMouseEvent() { - // Come here on modifier key transitions and change the cursor appropriately. + // Come here on modifier key transitions, + // or on starting or stopping of play or record, + // and change the cursor appropriately. HandleCursor( &mLastMouseEvent ); } @@ -2227,6 +2229,11 @@ void TrackPanel::SetBackgroundCell mpBackground = pCell; } +std::shared_ptr< TrackPanelCell > TrackPanel::GetBackgroundCell() +{ + return mpBackground; +} + /// Draw a three-level highlight gradient around the focused track. void TrackPanel::HighlightFocusedTrack(wxDC * dc, const wxRect & rect) { @@ -2488,137 +2495,28 @@ void TrackPanel::DrawShadow(Track * /* t */ , wxDC * dc, const wxRect & rect) /// @param mouseY - mouse Y position. TrackPanel::FoundCell TrackPanel::FindCell(int mouseX, int mouseY) { - enum class CellType { Label, Track, VRuler, Background }; - auto size = GetSize(); - size.x -= kRightMargin; - wxRect rect { 0, 0, 0, 0 }; - bool betweenTracks = false; - - // The type of cell that may be found is determined by the x coordinate. - CellType type = CellType::Track; - if (mouseX < kLeftMargin) - ; - else if (mouseX < GetVRulerOffset()) - type = CellType::Label, - rect.x = kLeftMargin, - rect.width = GetVRulerOffset() - kLeftMargin; - else if (mouseX < GetLeftOffset()) - type = CellType::VRuler, - rect.x = GetVRulerOffset(), - rect.width = GetLeftOffset() - GetVRulerOffset(); - else if (mouseX < size.x) - type = CellType::Track, - rect.x = GetLeftOffset(), - rect.width = size.x - GetLeftOffset(); - - auto output = [&](Track *pTrack) -> FoundCell { - TrackPanelCell *pCell {}; - - // Did we actually hit in the resizer region, which encompasses the - // bottom margin proper to "this" track, plus the top margin of the - // "next" track (or, an equally wide zone below, in case there is no - // next track)? - const auto margin = kBottomMargin + kTopMargin; - if ( rect.y + rect.height - mouseY <= margin ) { - auto instance = &TrackPanelResizerCell::Instance(); - instance->mpTrack = pTrack; - instance->mBetweenTracks = betweenTracks; - pCell = instance; - rect.y = rect.y + rect.height - kTopMargin; - rect.height = margin; - return { pTrack, pCell, rect }; - } - - // Undo the bias mentioned below. - rect.y -= kTopMargin; - if (pTrack) switch (type) { - case CellType::Label: - pCell = pTrack->GetTrackControl(); break; - case CellType::VRuler: - pCell = pTrack->GetVRulerControl(); break; - default: - pCell = pTrack; break; - } - if (pTrack) - return { pTrack, pCell, rect }; - else - return { nullptr, nullptr, {} }; + auto range = Cells(); + auto &iter = range.first, &end = range.second; + auto prev = iter; + while + ( iter != end && + !(*iter).second.Contains( mouseX, mouseY ) ) + prev = iter++; + if ( iter == end ) + // Default to the background cell, which is always last in the sequence, + // even if it does not contain the point + iter = prev; + auto found = *iter; + return { + static_cast( found.first )->FindTrack(), + found.first, + found.second }; - - VisibleTrackIterator iter(GetProject()); - for (Track * t = iter.First(); t; t = iter.Next()) { - // The zone to hit the track is biased to exclude the margin above - // but include the top margin of the track below. That makes the change - // to the track resizing cursor work right. - rect.y = t->GetY() - mViewInfo->vpos + kTopMargin; - rect.height = t->GetHeight(); - - if (type == CellType::Label) { - if (t->GetLink()) { - Track *l = t->GetLink(); - int h = l->GetHeight(); - if (!t->GetLinked()) - rect.y = l->GetY() - mViewInfo->vpos + kTopMargin; - else - t = l; - rect.height += h; - } -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - else if( MONO_WAVE_PAN(t) ) - rect.height += t->GetHeight(true); -#endif - } - else { -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - if( MONO_WAVE_PAN(t) ) - betweenTracks = true; - else -#endif - betweenTracks = t->GetLinked(); - } - - //Determine whether the mouse is inside - //the current rectangle. If so, return. - if (rect.Contains(mouseX, mouseY)) { -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - // PRL: Is it good to have a side effect in a hit-testing routine? - t->SetVirtualStereo(false); -#endif - return output(t); - } -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - if(type != CellType::Label && MONO_WAVE_PAN(t)){ - betweenTracks = false; - rect.y = t->GetY(true) - mViewInfo->vpos + kTopMargin; - rect.height = t->GetHeight(true); - if (rect.Contains(mouseX, mouseY)) { - // PRL: Is it good to have a side effect in a hit-testing routine? - t->SetVirtualStereo(true); - return output(t); - } - } -#endif // EXPERIMENTAL_OUTPUT_DISPLAY - } - - if (mpBackground) { - // In default of hits on any other cells - // Find a disjoint, maybe empty, rectangle - // for the empty space appearing at bottom - rect.x = kLeftMargin; - rect.width = size.x - rect.x; - rect.y = - std::min( size.y, - std::max( 0, - rect.y + rect.height ) ); - rect.height = size.y - rect.y; - return { nullptr, mpBackground.get(), rect }; - } - else - return { nullptr, nullptr, {} }; } -/// This finds the rectangle of a given track, either the -/// of the label 'adornment' or the track itself +// This finds the rectangle of a given track (including all channels), +// either that of the label 'adornment' or the track itself +// The given track is assumed to be the first channel wxRect TrackPanel::FindTrackRect( const Track * target, bool label ) { if (!target) { @@ -3163,17 +3061,68 @@ void TrackInfo::UpdatePrefs() } while (textWidth >= allowableWidth); } -TrackPanelCellIterator::TrackPanelCellIterator(TrackPanel *trackPanel, bool begin) - : mPanel(trackPanel) - , mIter(trackPanel->GetProject()) - , mpCell(begin ? mIter.First() : NULL) +IteratorRange< TrackPanelCellIterator > TrackPanel::Cells() { + return { + TrackPanelCellIterator( this, true ), + TrackPanelCellIterator( this, false ) + }; +} + +TrackPanelCellIterator::TrackPanelCellIterator(TrackPanel *trackPanel, bool begin) + : mPanel{ trackPanel } + , mIter{ trackPanel->GetProject() } + , mpTrack{ begin ? mIter.First() : nullptr } + , mpCell{ begin + ? ( mpTrack ? mpTrack : trackPanel->GetBackgroundCell().get() ) + : nullptr + } +{ + const auto size = mPanel->GetSize(); + mRect = { 0, 0, size.x, size.y }; + UpdateRect(); } TrackPanelCellIterator &TrackPanelCellIterator::operator++ () { - // To do, iterate over the other cells that are not tracks - mpCell = mIter.Next(); + if ( mpTrack ) { + if ( ++ mType == CellType::Background ) + mType = CellType::Track, mpTrack = mIter.Next(); + } + if ( mpTrack ) { + if ( mType == CellType::Label && + mpTrack->GetLink() && !mpTrack->GetLinked() ) + // Visit label of stereo track only once + ++mType; + switch ( mType ) { + case CellType::Track: + mpCell = mpTrack; + break; + case CellType::Label: + mpCell = mpTrack->GetTrackControl(); + break; + case CellType::VRuler: + mpCell = mpTrack->GetVRulerControl(); + break; + case CellType::Resizer: { + auto instance = &TrackPanelResizerCell::Instance(); + instance->mpTrack = mpTrack; + mpCell = instance; + break; + } + default: + // should not happen + mpCell = nullptr; + break; + } + } + else if ( !mDidBackground ) + mpCell = mPanel->GetBackgroundCell().get(), mDidBackground = true; + else + mpCell = nullptr; + + UpdateRect(); + return *this; } @@ -3186,24 +3135,79 @@ TrackPanelCellIterator TrackPanelCellIterator::operator++ (int) auto TrackPanelCellIterator::operator* () const -> value_type { - Track *const pTrack = dynamic_cast(mpCell); - if (!pTrack) - // to do: handle cells that are not tracks - return std::make_pair((Track*)nullptr, wxRect()); + return { mpCell, mRect }; +} - // Convert virtual coordinate to physical - int width; - mPanel->GetTracksUsableArea(&width, NULL); - int y = pTrack->GetY() - mPanel->GetViewInfo()->vpos; - return std::make_pair( - mpCell, - wxRect( - mPanel->GetLeftOffset(), - y + kTopMargin, - width, - pTrack->GetHeight() - (kTopMargin + kBottomMargin) - ) - ); +void TrackPanelCellIterator::UpdateRect() +{ + // TODO: cooperate with EXPERIMENTAL_OUTPUT_DISPLAY + const auto size = mPanel->GetSize(); + if ( mpTrack ) { + mRect = { + 0, + mpTrack->GetY() - mPanel->GetViewInfo()->vpos, + size.x, + mpTrack->GetHeight() + }; + switch ( mType ) { + case CellType::Track: + mRect.x = mPanel->GetLeftOffset(); + mRect.width -= (mRect.x + kRightMargin); + mRect.y += kTopMargin; + mRect.height -= (kBottomMargin + kTopMargin); + break; + case CellType::Label: { + mRect.x = kLeftMargin; + mRect.width = kTrackInfoWidth - mRect.x; + mRect.y += kTopMargin; + mRect.height -= (kBottomMargin + kTopMargin); + auto partner = mpTrack->GetLink(); + if ( partner && mpTrack->GetLinked() ) + mRect.height += partner->GetHeight(); + break; + } + case CellType::VRuler: + mRect.x = kTrackInfoWidth; + mRect.width = mPanel->GetLeftOffset() - mRect.x; + mRect.y += kTopMargin; + mRect.height -= (kBottomMargin + kTopMargin); + break; + case CellType::Resizer: { + // The resizer region encompasses the bottom margin proper to this + // track, plus the top margin of the next track (or, an equally + // tall zone below, in case there is no next track) + auto partner = mpTrack->GetLink(); + if ( partner && mpTrack->GetLinked() ) + mRect.x = mPanel->GetLeftOffset(); + else + mRect.x = kLeftMargin; + mRect.width -= (mRect.x + kRightMargin); + mRect.y += (mRect.height - kBottomMargin); + mRect.height = (kBottomMargin + kTopMargin); + break; + } + default: + // should not happen + break; + } + } + else if ( mpCell ) { + // Find a disjoint, maybe empty, rectangle + // for the empty space appearing at bottom + + mRect.x = kLeftMargin; + mRect.width = size.x - (mRect.x + kRightMargin); + + // Use previous value of the bottom, either the whole area if + // there were no tracks, or else the resizer of the last track + mRect.y = + std::min( size.y, + std::max( 0, + mRect.y + mRect.height ) ); + mRect.height = size.y - mRect.y; + } + else + mRect = {}; } static TrackPanel * TrackPanelFactory(wxWindow * parent, diff --git a/src/TrackPanel.h b/src/TrackPanel.h index b454c79a3..9dd93c36d 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -44,6 +44,7 @@ class MixerBoard; class AudacityProject; class TrackPanelAx; +class TrackPanelCellIterator; struct TrackPanelMouseEvent; class ViewInfo; @@ -259,6 +260,8 @@ class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { virtual ~ TrackPanel(); + IteratorRange< TrackPanelCellIterator > Cells(); + void UpdatePrefs(); void ApplyUpdatedTheme(); @@ -418,6 +421,7 @@ public: // is not in any track or ruler or control panel. void SetBackgroundCell (const std::shared_ptr< TrackPanelCell > &pCell); + std::shared_ptr< TrackPanelCell > GetBackgroundCell(); #ifdef EXPERIMENTAL_OUTPUT_DISPLAY void UpdateVirtualStereoOrder(); diff --git a/src/TrackPanelCellIterator.h b/src/TrackPanelCellIterator.h index ca69faa29..97adad3b0 100644 --- a/src/TrackPanelCellIterator.h +++ b/src/TrackPanelCellIterator.h @@ -12,15 +12,26 @@ Paul Licameli #define __AUDACITY_TRACK_PANEL_CELL_ITERATOR__ #include "Track.h" +#include +#include +class Track; class TrackPanelCell; class TrackPanel; // A class that allows iteration over the rectangles of visible cells. class TrackPanelCellIterator + : public std::iterator< + std::forward_iterator_tag, + const std::pair + > { public: + enum class CellType { + Track, Label, VRuler, Resizer, Background + }; + TrackPanelCellIterator(TrackPanel *trackPanel, bool begin); // implement the STL iterator idiom @@ -38,11 +49,24 @@ public: value_type operator * () const; private: + void UpdateRect(); + TrackPanel *mPanel; VisibleTrackIterator mIter; + Track *mpTrack; TrackPanelCell *mpCell; + CellType mType{ CellType::Track }; + bool mDidBackground{ false }; + wxRect mRect; }; +inline TrackPanelCellIterator::CellType &operator++ +( TrackPanelCellIterator::CellType &type ) +{ + type = TrackPanelCellIterator::CellType( 1 + int( type ) ); + return type; +} + inline bool operator != (const TrackPanelCellIterator &lhs, const TrackPanelCellIterator &rhs) { diff --git a/src/TrackPanelResizeHandle.h b/src/TrackPanelResizeHandle.h index 6f4f82895..498a3a0ce 100644 --- a/src/TrackPanelResizeHandle.h +++ b/src/TrackPanelResizeHandle.h @@ -16,6 +16,7 @@ Paul Licameli split from TrackPanel.cpp struct HitTestResult; class Track; +class TrackPanelCellIterator; class TrackPanelResizeHandle final : public UIHandle { @@ -80,7 +81,7 @@ public: Track *FindTrack() override { return mpTrack; }; private: - friend class TrackPanel; + friend class TrackPanelCellIterator; Track *mpTrack {}; bool mBetweenTracks {}; }; diff --git a/src/tracks/ui/EditCursorOverlay.cpp b/src/tracks/ui/EditCursorOverlay.cpp index 5e23c4c15..4f6207384 100644 --- a/src/tracks/ui/EditCursorOverlay.cpp +++ b/src/tracks/ui/EditCursorOverlay.cpp @@ -102,13 +102,10 @@ void EditCursorOverlay::Draw(OverlayPanel &panel, wxDC &dc) if (auto tp = dynamic_cast(&panel)) { wxASSERT(mIsMaster); AColor::CursorColor(&dc); - TrackPanelCellIterator begin(tp, true); - TrackPanelCellIterator end(tp, false); // Draw cursor in all selected tracks - for (; begin != end; ++begin) + for ( const auto &data : tp->Cells() ) { - TrackPanelCellIterator::value_type data(*begin); Track *const pTrack = dynamic_cast(data.first); if (!pTrack) continue; diff --git a/src/tracks/ui/PlayIndicatorOverlay.cpp b/src/tracks/ui/PlayIndicatorOverlay.cpp index 2a5e35d06..91b8977cb 100644 --- a/src/tracks/ui/PlayIndicatorOverlay.cpp +++ b/src/tracks/ui/PlayIndicatorOverlay.cpp @@ -69,13 +69,9 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc) if(auto tp = dynamic_cast(&panel)) { wxASSERT(mIsMaster); - TrackPanelCellIterator begin(tp, true); - TrackPanelCellIterator end(tp, false); - // Draw indicator in all visible tracks - for (; begin != end; ++begin) + for ( const auto &data : tp->Cells() ) { - TrackPanelCellIterator::value_type data(*begin); Track *const pTrack = dynamic_cast(data.first); if (!pTrack) continue; diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index b86fefcd3..a40a86beb 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -17,7 +17,6 @@ Paul Licameli split from TrackPanel.cpp #include "../../Project.h" #include "../../TrackPanel.h" #include "../../TrackPanelCell.h" -#include "../../TrackPanelCellIterator.h" #include "../../commands/CommandFunctors.h" #include "../../prefs/TracksPrefs.h" #include "../../toolbars/ControlToolBar.h" diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 6ebdf4db9..a9ba2fc36 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -1826,10 +1826,6 @@ std::pair QuickPlayIndicatorOverlay::DoGetRectangle(wxSize size) void QuickPlayIndicatorOverlay::Draw(OverlayPanel &panel, wxDC &dc) { - TrackPanel &tp = static_cast(panel); - TrackPanelCellIterator begin(&tp, true); - TrackPanelCellIterator end(&tp, false); - mOldQPIndicatorPos = mNewQPIndicatorPos; mOldQPIndicatorSnapped = mNewQPIndicatorSnapped; mOldPreviewingScrub = mNewPreviewingScrub; @@ -1843,9 +1839,8 @@ void QuickPlayIndicatorOverlay::Draw(OverlayPanel &panel, wxDC &dc) ; // Draw indicator in all visible tracks - for (; begin != end; ++begin) + for ( const auto &data : static_cast(panel).Cells() ) { - TrackPanelCellIterator::value_type data(*begin); Track *const pTrack = dynamic_cast(data.first); if (!pTrack) continue;