1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-02 08:39:46 +02:00

TrackPanel, AdornedRulerPanel each define subdivision in one place...

... excepting the duplication still remaining in TrackPanel::FindTrackRect.

Subdivision of the panel's area is defined recusively using helper classes
that each report a refinement of a sub-rectangle on one of the two axes until
what remains is a rectangle occupied by one cell only.

TrackPanelCellIterator, which iterated over TrackPanel's subdivision by other
means, is replaced by this.

CellularPanel thus requires only one virtual function definition for
subdivision, not two as before, and from that figures out in a generalized way
how to find a cell given a point, or a rectangle given a cell, or an iteration
over all cells within visible bounds.

In future, a rewrite of drawing may invoke virtual functions not only on the
cell objects, but also on the groupings of them.  For instance to draw the
focus rectangle of a stereo track, which surrounds several of the cells -- the
control panel, the channels, and the resizer between.  So it's good to reify
such groupings of cells as objects.
This commit is contained in:
Paul Licameli 2018-11-01 18:26:11 -04:00
commit b7e1cc09a5
18 changed files with 471 additions and 356 deletions

View File

@ -205,7 +205,6 @@ src/TrackPanel.h
src/TrackPanelAx.cpp
src/TrackPanelAx.h
src/TrackPanelCell.h
src/TrackPanelCellIterator.h
src/TrackPanelDrawingContext.h
src/TrackPanelListener.h
src/TrackPanelMouseEvent.h

View File

@ -3167,7 +3167,6 @@
5E73966F1DAFDB9D00BA0A4D /* SelectHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SelectHandle.cpp; sourceTree = "<group>"; };
5E7396701DAFDB9D00BA0A4D /* SelectHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectHandle.h; sourceTree = "<group>"; };
5E74D2D91CC4427B00D88B0B /* TrackPanelCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCell.h; sourceTree = "<group>"; };
5E74D2DA1CC4427B00D88B0B /* TrackPanelCellIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCellIterator.h; sourceTree = "<group>"; };
5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditCursorOverlay.cpp; sourceTree = "<group>"; };
5E74D2DE1CC4429700D88B0B /* EditCursorOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditCursorOverlay.h; sourceTree = "<group>"; };
5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlayIndicatorOverlay.cpp; sourceTree = "<group>"; };
@ -4273,7 +4272,6 @@
1790B0ED09883BFD008A330A /* TrackPanel.h */,
1790B0EF09883BFD008A330A /* TrackPanelAx.h */,
5E74D2D91CC4427B00D88B0B /* TrackPanelCell.h */,
5E74D2DA1CC4427B00D88B0B /* TrackPanelCellIterator.h */,
5E52335F1EFDD57D001E4BB8 /* TrackPanelDrawingContext.h */,
2803C8B619F35AA000278526 /* TrackPanelListener.h */,
5E15123A1DB000C000702E29 /* TrackPanelMouseEvent.h */,

View File

@ -33,7 +33,6 @@
#include "RefreshCode.h"
#include "Snap.h"
#include "TrackPanel.h"
#include "TrackPanelCellIterator.h"
#include "TrackPanelMouseEvent.h"
#include "UIHandle.h"
#include "prefs/TracksBehaviorsPrefs.h"
@ -328,20 +327,19 @@ void AdornedRulerPanel::QuickPlayIndicatorOverlay::Draw(
;
// Draw indicator in all visible tracks
for ( const auto &data : static_cast<TrackPanel&>(panel).Cells() )
{
Track *const pTrack = dynamic_cast<Track*>(data.first.get());
if (!pTrack)
continue;
const wxRect &rect = data.second;
static_cast<TrackPanel&>(panel)
.VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
const auto pTrack = dynamic_cast<Track*>(&cell);
if (!pTrack)
return;
// Draw the NEW indicator in its NEW location
AColor::Line(dc,
mOldQPIndicatorPos,
rect.GetTop(),
mOldQPIndicatorPos,
rect.GetBottom());
}
// Draw the NEW indicator in its NEW location
AColor::Line(dc,
mOldQPIndicatorPos,
rect.GetTop(),
mOldQPIndicatorPos,
rect.GetBottom());
} );
}
}
@ -2101,31 +2099,46 @@ void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
mRuler.GetMaxSize(width, height);
}
// Second-level subdivision includes quick-play region and maybe the scrub bar
// and also shaves little margins above and below
struct AdornedRulerPanel::Subgroup final : TrackPanelGroup {
explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
Subdivision Children( const wxRect & ) override
{
return { Axis::Y, ( mRuler.mShowScrubbing )
? Refinement{
{ mRuler.mInner.GetTop(), mRuler.mQPCell },
{ mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
{ mRuler.mScrubZone.GetBottom() + 1, nullptr }
}
: Refinement{
{ mRuler.mInner.GetTop(), mRuler.mQPCell },
{ mRuler.mInner.GetBottom() + 1, nullptr }
}
};
}
const AdornedRulerPanel &mRuler;
};
// Top-level subdivision shaves little margins off left and right
struct AdornedRulerPanel::MainGroup final : TrackPanelGroup {
explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
Subdivision Children( const wxRect & ) override
{ return { Axis::X, Refinement{
// Subgroup is a throwaway object
{ mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
{ mRuler.mInner.GetRight() + 1, nullptr }
} }; }
const AdornedRulerPanel &mRuler;
};
// CellularPanel implementation
auto AdornedRulerPanel::FindCell(int mouseX, int mouseY) -> FoundCell
std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
{
bool mayScrub = mProject->GetScrubber().CanScrub() &&
mShowScrubbing;
if (mayScrub && mScrubZone.Contains(mouseX, mouseY))
return { mScrubbingCell, mScrubZone };
if (mInner.Contains(mouseX, mouseY))
return { mQPCell, mInner };
return {};
// Root is a throwaway object
return std::make_shared< MainGroup >( *this );
}
wxRect AdornedRulerPanel::FindRect(const TrackPanelCell &cell)
{
if (&cell == mScrubbingCell.get())
return mScrubZone;
if (&cell == mQPCell.get())
return mInner;
return {};
}
AudacityProject * AdornedRulerPanel::GetProject() const
{
return mProject;

View File

@ -172,8 +172,10 @@ private:
//
// CellularPanel implementation
//
FoundCell FindCell(int mouseX, int mouseY) override;
wxRect FindRect(const TrackPanelCell &cell) override;
// Get the root object defining a recursive subdivision of the panel's
// area into cells
std::shared_ptr<TrackPanelNode> Root() override;
public:
AudacityProject * GetProject() const override;
private:
@ -207,6 +209,10 @@ private:
class ScrubbingCell;
std::shared_ptr<ScrubbingCell> mScrubbingCell;
// classes implementing subdivision for CellularPanel
struct Subgroup;
struct MainGroup;
};
#endif //define __AUDACITY_ADORNED_RULER_PANEL__

View File

@ -860,6 +860,174 @@ void CellularPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event))
Refresh( false);
}
// Empty out-of-line default functions to fill Visitor's vtable
CellularPanel::Visitor::~Visitor() {}
void CellularPanel::Visitor::VisitCell(
const wxRect &rect, TrackPanelCell &cell ) {}
void CellularPanel::Visitor::BeginGroup(
const wxRect &rect, TrackPanelGroup &group ) {}
void CellularPanel::Visitor::EndGroup(
const wxRect &rect, TrackPanelGroup &group ) {}
// Public, top-level entry for generalized Visit
void CellularPanel::Visit( Visitor &visitor )
{
Visit( GetClientRect(), Root(), visitor );
}
// Common utility class for the functions that follow
namespace {
struct Adaptor : CellularPanel::Visitor {
using SimpleCellVisitor = CellularPanel::SimpleCellVisitor;
using SimpleNodeVisitor = CellularPanel::SimpleNodeVisitor;
// Visit cells only
Adaptor( const SimpleCellVisitor& function_ )
: function{ [&](const wxRect &rect, TrackPanelNode &cell) {
return function_( rect, static_cast<TrackPanelCell&>(cell) );
} }
{}
// Visit cells and groups, each once only, choosing pre- or post- ordering
// for the groups
Adaptor( const SimpleNodeVisitor &function_, bool pre_ )
: function{ function_ }, pre{ pre_ }, post{ ! pre_ } {}
void VisitCell( const wxRect &rect, TrackPanelCell &cell ) override
{ return function( rect, cell ); }
void BeginGroup( const wxRect &rect, TrackPanelGroup &group ) override
{ if (pre) return function( rect, group ); }
void EndGroup( const wxRect &rect, TrackPanelGroup &group ) override
{ if (post) return function( rect, group ); }
SimpleNodeVisitor function;
const bool pre{ false }, post{ false };
};
}
// Simplified entry points for visits that don't need all the generality of
// CellularPanel::Visitor
void CellularPanel::VisitCells( const SimpleCellVisitor &visitor )
{
Adaptor adaptor{ visitor };
Visit( adaptor );
}
void CellularPanel::VisitPreorder( const SimpleNodeVisitor &visitor )
{
Adaptor adaptor{ visitor, true };
Visit( adaptor );
}
void CellularPanel::VisitPostorder( const SimpleNodeVisitor &visitor )
{
Adaptor adaptor{ visitor, false };
Visit( adaptor );
}
namespace {
wxRect Subdivide(
const wxRect &rect, bool divideX,
const TrackPanelGroup::Refinement &children,
const TrackPanelGroup::Refinement::const_iterator iter)
{
const auto lowerBound = (divideX ? rect.GetLeft() : rect.GetTop());
const auto upperBound = (divideX ? rect.GetRight() : rect.GetBottom());
const auto next = iter + 1;
const auto end = children.end();
const auto nextCoord = ((next == end) ? upperBound : next->first - 1);
auto lesser = std::max(lowerBound, std::min(upperBound, iter->first));
auto greater = std::max(lesser, std::min(upperBound, nextCoord));
auto result = rect;
if (divideX)
result.SetLeft(lesser), result.SetRight(greater);
else
result.SetTop(lesser), result.SetBottom(greater);
return result;
};
}
// Private, recursive implementation function of Visit
void CellularPanel::Visit(
const wxRect &rect, const std::shared_ptr<TrackPanelNode> &node,
Visitor &visitor )
{
if (auto pCell = dynamic_cast<TrackPanelCell*>(node.get()))
visitor.VisitCell( rect, *pCell );
else if (auto pGroup = dynamic_cast<TrackPanelGroup*>(node.get())) {
visitor.BeginGroup( rect, *pGroup );
// Recur on children
const auto results = pGroup->Children( rect );
const bool divideX = results.first == TrackPanelGroup::Axis::X;
const auto &children = results.second;
const auto begin = children.begin(), end = children.end();
for (auto iter = begin; iter != end; ++iter)
Visit(
Subdivide(rect, divideX, children, iter), iter->second, visitor );
visitor.EndGroup( rect, *pGroup );
}
else
return;
}
auto CellularPanel::FindCell(int mouseX, int mouseY) -> FoundCell
{
auto rect = this->GetClientRect();
auto node = Root();
while (node) {
if ( auto pCell = std::dynamic_pointer_cast< TrackPanelCell >( node ) )
// Found the bottom of the hierarchy
return { pCell, rect };
else if ( auto pGroup = dynamic_cast< TrackPanelGroup* >( node.get() ) ) {
// Ask node for its subdivision
const auto results = pGroup->Children( rect );
const bool divideX = results.first == TrackPanelGroup::Axis::X;
const auto &children = results.second;
// Find the correct child
const auto begin = children.begin(), end = children.end();
auto iter = std::upper_bound( begin, end,
(divideX ? mouseX : mouseY),
[&]( wxCoord coord, const TrackPanelGroup::Child &child ) {
return coord < child.first;
}
);
if (iter == begin)
break;
--iter;
// Descend the hierarchy of nodes
rect = Subdivide(rect, divideX, children, iter);
node = iter->second;
}
else
// Nulls in the array of children are allowed, to define a void with
// no cell
break;
}
return { {}, {} };
}
wxRect CellularPanel::FindRect( const TrackPanelCell &cell )
{
wxRect result;
struct Stop{};
try { VisitCells( [&]( const wxRect &rect, TrackPanelCell &visited ) {
if ( &visited == &cell )
result = rect, throw Stop{};
} ); }
catch ( const Stop& ) {}
return result;
}
UIHandlePtr CellularPanel::Target()
{
auto &state = *mState;

View File

@ -18,6 +18,8 @@ class ViewInfo;
class AudacityProject;
class TrackPanelCell;
class TrackPanelGroup;
class TrackPanelNode;
struct TrackPanelMouseEvent;
struct TrackPanelMouseState;
@ -42,13 +44,32 @@ public:
virtual AudacityProject *GetProject() const = 0;
// Find track info by coordinate
struct FoundCell {
std::shared_ptr<TrackPanelCell> pCell;
wxRect rect;
// Get the root object defining a recursive subdivision of the panel's
// area into cells
virtual std::shared_ptr<TrackPanelNode> Root() = 0;
// Structure and functions for generalized visitation of the subdivision
struct Visitor {
virtual ~Visitor();
virtual void VisitCell( const wxRect &rect, TrackPanelCell &cell );
virtual void BeginGroup( const wxRect &rect, TrackPanelGroup &group );
virtual void EndGroup( const wxRect &rect, TrackPanelGroup &group );
};
virtual FoundCell FindCell(int mouseX, int mouseY) = 0;
virtual wxRect FindRect(const TrackPanelCell &cell) = 0;
// Most general visit
void Visit( Visitor &visitor );
// Easier visit when you care only about cells
using SimpleCellVisitor =
std::function< void( const wxRect &rect, TrackPanelCell &cell ) >;
void VisitCells( const SimpleCellVisitor &visitor );
// Easier visits when you want to visit each node once only
using SimpleNodeVisitor =
std::function< void( const wxRect &rect, TrackPanelNode &node ) >;
void VisitPreorder( const SimpleNodeVisitor &visitor );
void VisitPostorder( const SimpleNodeVisitor &visitor );
virtual TrackPanelCell *GetFocusedCell() = 0;
virtual void SetFocusedCell() = 0;
@ -63,6 +84,19 @@ public:
virtual bool TakesFocus() const = 0;
public:
// Find cell by coordinate
struct FoundCell {
std::shared_ptr< TrackPanelCell > pCell;
wxRect rect;
};
FoundCell FindCell(int mouseX, int mouseY);
// Search the tree of subdivisions of the panel area for the given cell.
// If more than one sub-area is associated with the same cell object, it
// is not specified which rectangle is returned.
wxRect FindRect(const TrackPanelCell &cell);
UIHandlePtr Target();
std::shared_ptr<TrackPanelCell> LastCell() const;
@ -80,6 +114,10 @@ protected:
void ClearTargets();
private:
void Visit(
const wxRect &rect, const std::shared_ptr<TrackPanelNode> &node,
Visitor &visitor );
bool HasRotation();
bool ChangeTarget(bool forward, bool cycle);

View File

@ -251,7 +251,6 @@ audacity_SOURCES = \
TrackPanelAx.cpp \
TrackPanelAx.h \
TrackPanelCell.h \
TrackPanelCellIterator.h \
TrackPanelDrawingContext.h \
TrackPanelListener.h \
TrackPanelMouseEvent.h \

View File

@ -330,12 +330,12 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \
TimerRecordDialog.cpp TimerRecordDialog.h TimeTrack.cpp \
TimeTrack.h Track.cpp Track.h TrackArtist.cpp TrackArtist.h \
TrackPanel.cpp TrackPanel.h TrackPanelAx.cpp TrackPanelAx.h \
TrackPanelCell.h TrackPanelCellIterator.h \
TrackPanelDrawingContext.h TrackPanelListener.h \
TrackPanelMouseEvent.h TrackPanelResizeHandle.cpp \
TrackPanelResizeHandle.h TrackPanelResizerCell.cpp \
TrackPanelResizerCell.h TranslatableStringArray.h UIHandle.h \
UIHandle.cpp UndoManager.cpp UndoManager.h UserException.cpp \
TrackPanelCell.h TrackPanelDrawingContext.h \
TrackPanelListener.h TrackPanelMouseEvent.h \
TrackPanelResizeHandle.cpp TrackPanelResizeHandle.h \
TrackPanelResizerCell.cpp TrackPanelResizerCell.h \
TranslatableStringArray.h UIHandle.h UIHandle.cpp \
UndoManager.cpp UndoManager.h UserException.cpp \
UserException.h ViewInfo.cpp ViewInfo.h VoiceKey.cpp \
VoiceKey.h WaveClip.cpp WaveClip.h WaveTrack.cpp WaveTrack.h \
WaveTrackLocation.h WrappedType.cpp WrappedType.h \
@ -1378,12 +1378,12 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \
TimerRecordDialog.cpp TimerRecordDialog.h TimeTrack.cpp \
TimeTrack.h Track.cpp Track.h TrackArtist.cpp TrackArtist.h \
TrackPanel.cpp TrackPanel.h TrackPanelAx.cpp TrackPanelAx.h \
TrackPanelCell.h TrackPanelCellIterator.h \
TrackPanelDrawingContext.h TrackPanelListener.h \
TrackPanelMouseEvent.h TrackPanelResizeHandle.cpp \
TrackPanelResizeHandle.h TrackPanelResizerCell.cpp \
TrackPanelResizerCell.h TranslatableStringArray.h UIHandle.h \
UIHandle.cpp UndoManager.cpp UndoManager.h UserException.cpp \
TrackPanelCell.h TrackPanelDrawingContext.h \
TrackPanelListener.h TrackPanelMouseEvent.h \
TrackPanelResizeHandle.cpp TrackPanelResizeHandle.h \
TrackPanelResizerCell.cpp TrackPanelResizerCell.h \
TranslatableStringArray.h UIHandle.h UIHandle.cpp \
UndoManager.cpp UndoManager.h UserException.cpp \
UserException.h ViewInfo.cpp ViewInfo.h VoiceKey.cpp \
VoiceKey.h WaveClip.cpp WaveClip.h WaveTrack.cpp WaveTrack.h \
WaveTrackLocation.h WrappedType.cpp WrappedType.h \

View File

@ -71,7 +71,6 @@ is time to refresh some aspect of the screen.
#include "Experimental.h"
#include "TrackPanel.h"
#include "Project.h"
#include "TrackPanelCellIterator.h"
#include "TrackPanelMouseEvent.h"
#include "TrackPanelResizeHandle.h"
//#define DEBUG_DRAW_TIMING 1
@ -126,13 +125,13 @@ 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.
FindCell() for label returns a rectangle that OMITS left, top, and bottom
Cell for label has a rectangle that OMITS left, top, and bottom
margins
FindCell() for vruler returns a rectangle right of the label,
Cell for vruler has a rectangle right of the label,
up to and including the One Pixel column, and OMITS top and bottom margins
FindCell() for track returns a rectangle with x == GetLeftOffset(), and OMITS
Cell() for track returns a rectangle with x == GetLeftOffset(), and OMITS
right, top, and bottom margins
+--------------- ... ------ ... --------------------- ... ... -------------+
@ -1956,39 +1955,136 @@ void TrackPanel::DrawShadow(const Track * /* t */ , wxDC * dc, const wxRect & re
AColor::Line(*dc, right, rect.y, right, rect.y + 1);
}
/// Determines which cell is under the mouse
/// @param mouseX - mouse X position.
/// @param mouseY - mouse Y position.
auto TrackPanel::FindCell(int mouseX, int mouseY) -> FoundCell
{
auto range = Cells();
auto &iter = range.first, &end = range.second;
while
( iter != end &&
!(*iter).second.Contains( mouseX, mouseY ) )
++iter;
if (iter == end)
return {};
namespace {
// Helper classes to implement the subdivision of TrackPanel area for
// CellularPanel
struct EmptyCell final : CommonTrackPanelCell {
std::vector< UIHandlePtr > HitTest(
const TrackPanelMouseState &, const AudacityProject *) override
{ return {}; }
virtual std::shared_ptr< Track > FindTrack() override { return {}; }
static std::shared_ptr<EmptyCell> Instance()
{
static auto instance = std::make_shared< EmptyCell >();
return instance;
}
};
// n channels alternating with n - 1 resizers
struct ChannelGroup final : TrackPanelGroup {
ChannelGroup( const std::shared_ptr< Track > &pTrack )
: mpTrack{ pTrack } {}
Subdivision Children( const wxRect &rect ) override
{
Refinement refinement;
const auto channels = TrackList::Channels( mpTrack.get() );
const auto pLast = *channels.rbegin();
wxCoord yy = rect.GetTop();
for ( auto channel : channels ) {
refinement.emplace_back( yy, Track::Pointer( channel ) );
if ( channel != pLast ) {
const auto substitute =
Track::Pointer( channel )->SubstitutePendingChangedTrack();
yy += substitute->GetHeight();
refinement.emplace_back(
yy - kSeparatorThickness, channel->GetResizer() );
}
}
return { Axis::Y, std::move( refinement ) };
}
std::shared_ptr< Track > mpTrack;
};
// A track control panel left of n channels alternating with n - 1 resizers
struct LabeledChannelGroup final : TrackPanelGroup {
LabeledChannelGroup(
const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
: mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
Subdivision Children( const wxRect &rect ) override
{ return { Axis::X, Refinement{
{ rect.GetLeft(), mpTrack->GetTrackControl() },
{ mLeftOffset, std::make_shared< ChannelGroup >( mpTrack ) }
} }; }
std::shared_ptr< Track > mpTrack;
wxCoord mLeftOffset;
};
// Stacks a label and a single or multi-channel track on a resizer below,
// which is associated with the last channel
struct ResizingChannelGroup final : TrackPanelGroup {
ResizingChannelGroup(
const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
: mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
Subdivision Children( const wxRect &rect ) override
{ return { Axis::Y, Refinement{
{ rect.GetTop(),
std::make_shared< LabeledChannelGroup >( mpTrack, mLeftOffset ) },
{ rect.GetTop() + rect.GetHeight() - kSeparatorThickness,
( *TrackList::Channels( mpTrack.get() ).rbegin() )->GetResizer() }
} }; }
std::shared_ptr< Track > mpTrack;
wxCoord mLeftOffset;
};
// Stacks a dead area at top, the tracks, and the click-to-deselect area below
struct Subgroup final : TrackPanelGroup {
explicit Subgroup( TrackPanel &panel ) : mPanel{ panel } {}
Subdivision Children( const wxRect &rect ) override
{
wxCoord yy = -mPanel.GetViewInfo()->vpos;
Refinement refinement;
auto &tracks = *mPanel.GetTracks();
if ( tracks.Any() )
refinement.emplace_back( yy, EmptyCell::Instance() ),
yy += kTopMargin;
for ( const auto leader : tracks.Leaders() ) {
wxCoord height = 0;
for ( auto channel : TrackList::Channels( leader ) ) {
auto substitute =
Track::Pointer( channel )->SubstitutePendingChangedTrack();
height += substitute->GetHeight();
}
refinement.emplace_back( yy,
std::make_shared< ResizingChannelGroup >(
Track::Pointer( leader ), mPanel.GetLeftOffset() )
);
yy += height;
}
refinement.emplace_back( std::max( 0, yy ), mPanel.GetBackgroundCell() );
return { Axis::Y, std::move( refinement ) };
}
TrackPanel &mPanel;
};
// Main group shaves off the left and right margins
struct MainGroup final : TrackPanelGroup {
explicit MainGroup( TrackPanel &panel ) : mPanel{ panel } {}
Subdivision Children( const wxRect &rect ) override
{ return { Axis::X, Refinement{
{ 0, EmptyCell::Instance() },
{ kLeftMargin, std::make_shared< Subgroup >( mPanel ) },
{ rect.GetRight() + 1 - kRightMargin, EmptyCell::Instance() }
} }; }
TrackPanel &mPanel;
};
auto found = *iter;
return {
found.first,
found.second
};
}
wxRect TrackPanel::FindRect( const TrackPanelCell &cell )
std::shared_ptr<TrackPanelNode> TrackPanel::Root()
{
auto range = Cells();
auto end = range.second,
iter = std::find_if( range.first, end,
[&]( const decltype(*end) &pair )
{ return pair.first.get() == &cell; }
);
if (iter == end)
return {};
else
return (*iter).second;
// Root and other subgroup objects are throwaways.
// They might instead be cached to avoid repeated allocation.
// That cache would need invalidation when there is addition, deletion, or
// permutation of tracks, or change of width of the vertical rulers.
return std::make_shared< MainGroup >( *this );
}
// This finds the rectangle of a given track (including all channels),
@ -2537,164 +2633,6 @@ void TrackInfo::UpdatePrefs()
} while (textWidth >= allowableWidth);
}
IteratorRange< TrackPanelCellIterator > TrackPanel::Cells()
{
return {
TrackPanelCellIterator( this, true ),
TrackPanelCellIterator( this, false )
};
}
TrackPanelCellIterator::TrackPanelCellIterator(TrackPanel *trackPanel, bool begin)
: mPanel{ trackPanel }
, mIter{
trackPanel->GetTracks()->Any().begin()
.Filter( IsVisibleTrack( trackPanel->GetProject() ) )
}
{
if (begin) {
mpTrack = Track::Pointer( *mIter );
if (mpTrack)
mpCell = mpTrack;
else
mpCell = trackPanel->GetBackgroundCell();
}
else
mDidBackground = true;
const auto size = mPanel->GetSize();
mRect = { 0, 0, size.x, size.y };
UpdateRect();
}
TrackPanelCellIterator &TrackPanelCellIterator::operator++ ()
{
if ( mpTrack ) {
if ( ++ mType == CellType::Background )
mType = CellType::Track, mpTrack = Track::Pointer( * ++ mIter );
}
if ( mpTrack ) {
if ( mType == CellType::Label &&
!mpTrack->IsLeader() )
// 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: {
mpCell = mpTrack->GetResizer();
break;
}
default:
// should not happen
mpCell.reset();
break;
}
}
else if ( !mDidBackground )
mpCell = mPanel->GetBackgroundCell(), mDidBackground = true;
else
mpCell.reset();
UpdateRect();
return *this;
}
TrackPanelCellIterator TrackPanelCellIterator::operator++ (int)
{
TrackPanelCellIterator copy(*this);
++ *this;
return copy;
}
auto TrackPanelCellIterator::operator* () const -> value_type
{
return { mpCell, mRect };
}
void TrackPanelCellIterator::UpdateRect()
{
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 =
TrackList::Channels(mpTrack.get())
.sum( &Track::GetHeight );
mRect.height -= (kBottomMargin + kTopMargin);
break;
}
case CellType::VRuler:
{
mRect.x = kTrackInfoWidth;
// Right edge of the VRuler is inactive.
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)
if ( mpTrack.get() ==
*TrackList::Channels(mpTrack.get()).rbegin() )
// Last channel has a resizer extending farther leftward
mRect.x = kLeftMargin;
else
mRect.x = kTrackInfoWidth;
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,
wxWindowID id,
const wxPoint & pos,
@ -2729,6 +2667,22 @@ TrackPanel *(*TrackPanel::FactoryFunction)(
TrackPanelListener * listener,
AdornedRulerPanel * ruler) = TrackPanelFactory;
TrackPanelNode::TrackPanelNode()
{
}
TrackPanelNode::~TrackPanelNode()
{
}
TrackPanelGroup::TrackPanelGroup()
{
}
TrackPanelGroup::~TrackPanelGroup()
{
}
TrackPanelCell::~TrackPanelCell()
{
}

View File

@ -44,7 +44,6 @@ class ControlToolBar; //Needed because state of controls can affect what gets dr
class ToolsToolBar; //Needed because state of controls can affect what gets drawn.
class TrackPanelAx;
class TrackPanelCellIterator;
class NoteTrack;
class WaveTrack;
@ -248,8 +247,6 @@ class AUDACITY_DLL_API TrackPanel final : public CellularPanel {
virtual ~ TrackPanel();
IteratorRange< TrackPanelCellIterator > Cells();
void UpdatePrefs();
void ApplyUpdatedTheme();
@ -337,11 +334,9 @@ protected:
void MakeParentModifyState(bool bWantsAutoSave); // if true, writes auto-save file. Should set only if you really want the state change restored after
// a crash, as it can take many seconds for large (eg. 10 track-hours) projects
// Find track info by coordinate
FoundCell FindCell(int mouseX, int mouseY) override;
// Find rectangle of the given cell
wxRect FindRect(const TrackPanelCell &cell) override;
// Get the root object defining a recursive subdivision of the panel's
// area into cells
std::shared_ptr<TrackPanelNode> Root() override;
int GetVRulerWidth() const;
int GetVRulerOffset() const { return mTrackInfo.GetTrackInfoWidth(); }
@ -514,6 +509,7 @@ enum : int {
kBottomMargin = kShadowThickness + kBorderThickness,
kLeftMargin = kLeftInset + kBorderThickness,
kRightMargin = kRightInset + kShadowThickness + kBorderThickness,
kSeparatorThickness = kBottomMargin + kTopMargin,
};
enum : int {

View File

@ -28,9 +28,43 @@ using UIHandlePtr = std::shared_ptr<UIHandle>;
#include <vector>
// A subtree in the subdivision of the CellularPanel's area
class AUDACITY_DLL_API /* not final */ TrackPanelNode
{
public:
TrackPanelNode();
virtual ~TrackPanelNode() = 0;
};
// A non-leaf
class AUDACITY_DLL_API TrackPanelGroup /* not final */ : public TrackPanelNode
{
public:
TrackPanelGroup();
virtual ~TrackPanelGroup();
enum class Axis { X, Y };
// A refinement of a given rectangle partitions it along one of its axes
// and associates TrackPanelNodes with the partition.
// The sequence of coordinates should be increasing, giving left or top
// coordinates of sub-rectangles.
// Null pointers are permitted to define empty spaces with no cell object.
// If the first coordinate is right of or below the rectangle boundary,
// then that also defines an empty space at the edge.
// Sub-rectangles may be defined partly or wholly out of the bounds of the
// given rectangle. Such portions are ignored.
using Child = std::pair< wxCoord, std::shared_ptr<TrackPanelNode> >;
using Refinement = std::vector< Child >;
using Subdivision = std::pair< Axis, Refinement >;
// Report a subdivision of one of the axes of the given rectangle
virtual Subdivision Children( const wxRect &rect ) = 0;
};
// Abstract base class defining TrackPanel's access to specialist classes that
// implement drawing and user interactions
class AUDACITY_DLL_API TrackPanelCell /* not final */
class AUDACITY_DLL_API TrackPanelCell /* not final */ : public TrackPanelNode
{
public:
virtual ~TrackPanelCell () = 0;

View File

@ -1,76 +0,0 @@
/**********************************************************************
Audacity: A Digital Audio Editor
TrackPanelCellIterator.h
Paul Licameli
**********************************************************************/
#ifndef __AUDACITY_TRACK_PANEL_CELL_ITERATOR__
#define __AUDACITY_TRACK_PANEL_CELL_ITERATOR__
#include "Track.h"
#include <wx/gdicmn.h>
#include <iterator>
#include "MemoryX.h"
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< std::shared_ptr< TrackPanelCell >, wxRect>
>
{
public:
enum class CellType {
Track, Label, VRuler, Resizer, Background
};
TrackPanelCellIterator(TrackPanel *trackPanel, bool begin);
// implement the STL iterator idiom
TrackPanelCellIterator &operator++ ();
TrackPanelCellIterator operator++ (int);
friend inline bool operator==
(const TrackPanelCellIterator &lhs, const TrackPanelCellIterator &rhs)
{
return lhs.mpCell == rhs.mpCell &&
lhs.mDidBackground == rhs.mDidBackground;
}
value_type operator * () const;
private:
void UpdateRect();
TrackPanel *mPanel;
TrackIter<Track> mIter;
std::shared_ptr<Track> mpTrack;
std::shared_ptr<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)
{
return !(lhs == rhs);
}
#endif

View File

@ -15,7 +15,6 @@ Paul Licameli split from TrackPanel.cpp
#include "UIHandle.h"
class Track;
class TrackPanelCellIterator;
class TrackPanelResizeHandle final : public UIHandle
{

View File

@ -29,7 +29,6 @@ public:
std::shared_ptr<Track> FindTrack() override { return mpTrack.lock(); };
private:
friend class TrackPanelCellIterator;
std::weak_ptr<Track> mpTrack;
std::weak_ptr<TrackPanelResizeHandle> mResizeHandle;

View File

@ -15,8 +15,6 @@ Paul Licameli split from TrackPanel.cpp
#include "../../AColor.h"
#include "../../AdornedRulerPanel.h"
#include "../../Project.h"
#include "../../TrackPanelCell.h"
#include "../../TrackPanelCellIterator.h"
#include "../../TrackPanelAx.h"
#include "../../ViewInfo.h"
@ -100,21 +98,19 @@ void EditCursorOverlay::Draw(OverlayPanel &panel, wxDC &dc)
AColor::CursorColor(&dc);
// Draw cursor in all selected tracks
for ( const auto &data : tp->Cells() )
{
Track *const pTrack = dynamic_cast<Track*>(data.first.get());
tp->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
const auto pTrack = dynamic_cast<Track*>(&cell);
if (!pTrack)
continue;
return;
if (pTrack->GetSelected() ||
mProject->GetTrackPanel()->GetAx().IsFocused(pTrack))
{
const wxRect &rect = data.second;
// AColor::Line includes both endpoints so use GetBottom()
AColor::Line(dc, mLastCursorX, rect.GetTop(), mLastCursorX, rect.GetBottom());
// ^^^ The whole point of this routine.
}
}
} );
}
else if (auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) {
wxASSERT(!mIsMaster);

View File

@ -16,8 +16,6 @@ Paul Licameli split from TrackPanel.cpp
#include "../../AudioIO.h"
#include "../../Project.h"
#include "../../TrackPanel.h"
#include "../../TrackPanelCell.h"
#include "../../TrackPanelCellIterator.h"
#include "Scrubbing.h"
#include <wx/dc.h>
@ -82,9 +80,8 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
wxASSERT(mIsMaster);
// Draw indicator in all visible tracks
for ( const auto &data : tp->Cells() )
{
Track *const pTrack = dynamic_cast<Track*>(data.first.get());
tp->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
const auto pTrack = dynamic_cast<Track*>(&cell);
if (pTrack) pTrack->TypeSwitch(
[](LabelTrack *) {
// Don't draw the indicator in label tracks
@ -92,7 +89,6 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
[&](Track *) {
// Draw the NEW indicator in its NEW location
// AColor::Line includes both endpoints so use GetBottom()
const wxRect &rect = data.second;
AColor::Line(dc,
mLastIndicatorX,
rect.GetTop(),
@ -100,7 +96,7 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
rect.GetBottom());
}
);
}
} );
}
else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) {
wxASSERT(!mIsMaster);

View File

@ -545,7 +545,6 @@
<ClInclude Include="..\..\..\src\toolbars\SpectralSelectionBar.h" />
<ClInclude Include="..\..\..\src\toolbars\SpectralSelectionBarListener.h" />
<ClInclude Include="..\..\..\src\TrackPanelCell.h" />
<ClInclude Include="..\..\..\src\TrackPanelCellIterator.h" />
<ClInclude Include="..\..\..\src\TrackPanelDrawingContext.h" />
<ClInclude Include="..\..\..\src\TrackPanelListener.h" />
<ClInclude Include="..\..\..\src\TrackPanelMouseEvent.h" />

View File

@ -1978,9 +1978,6 @@
<ClInclude Include="..\..\..\src\tracks\ui\Scrubbing.h">
<Filter>src\tracks\ui</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\TrackPanelCellIterator.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\commands\CommandFunctors.h">
<Filter>src\commands</Filter>
</ClInclude>