mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-24 08:10:05 +02:00
Split wave and spectrogram views...
To get a split view: move the mouse near the top or bottom of a wave track view until the cursor changes to "north-south", then drag and release. When a separation of views exists, the separation line can be dragged again. You can drag until one of the sub-views shrinks to nothing. There is a certain snapping (now 5 pixels) to make this easier and prevent very small views shorter than that. Notes: - ESC key before mouse release cancels the drag, as expected. - Either wave or spectrum view can be on top, depending on how you drag. - Please note, and test, that the vertical ruler also divides in two portions, and that all mouse drag actions (including spectral selection) in the track view or the ruler are available, and only those proper for the sub-view. - If you have a stereo track, then the wave and spectrum views keep the same height proportions in the two channels. - If you want to drag the channel separator instead, unfortunately there is no difference yet between the preview cursors. North-south seems the most sensible cursor for both of these drags, among pre-defined cursors. - The drop-down menu of the track control panel may now have two checkmarks, for spectrum and for either Waveform or Waveform dB. Selecting any of those items causes that view type to become the sole view. - In my opinion, the behavior above is not satisfactory. I think the more consistent user interface would have only Waveform and Spectrum items as a non-exclusive radio button choice in the menu, while the choice between linear and dB waveform view should be part of the right-mouse popup menu in the vertical ruler, analogous to the choice among scales for spectrum view. - The 'VIEW property of *TRACK* in Nyquist is now a list rather than an atom when there is a split view. None of the existing .ny plug-ins that ship with Audacity need to be changed, because they only ever test whether the property is nil, indicating that an effect preview is being computed.
This commit is contained in:
commit
92bdec7a46
@ -344,6 +344,40 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// \brief Invoke predicate on the ClientData objects that have been created in
|
||||
// this, but do not cause the creation of any. Stop at the first for which
|
||||
// the predicate returns true, and return a pointer to the corresponding
|
||||
// object, or return nullptr if no return values were true.
|
||||
// Beware that the sequence of visitation is not specified.
|
||||
template< typename Function >
|
||||
ClientData *FindIf( const Function &function )
|
||||
{
|
||||
auto data = GetData();
|
||||
for( auto &pObject : data.mObject ) {
|
||||
const auto &ptr = Dereferenceable(pObject);
|
||||
if ( ptr && function ( *ptr ) )
|
||||
return &*ptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// const counterpart of previous, only compiles with a function that takes
|
||||
// a value or const reference argument
|
||||
template< typename Function >
|
||||
const ClientData *FindIf( const Function &function ) const
|
||||
{
|
||||
auto data = GetData();
|
||||
for( auto &pObject : data.mObject ) {
|
||||
const auto &ptr = Dereferenceable(pObject);
|
||||
if ( ptr ) {
|
||||
const auto &c_ref = *ptr;
|
||||
if ( function( c_ref ) );
|
||||
return &*c_ref;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// \brief For each registered factory, if the corresponding object in this
|
||||
// is absent, then invoke the factory and store the result.
|
||||
void BuildAll()
|
||||
|
@ -923,6 +923,7 @@ void TrackPanel::UpdateTrackVRuler(Track *t)
|
||||
for (auto channel : TrackList::Channels(t)) {
|
||||
auto &view = TrackView::Get( *channel );
|
||||
const auto height = view.GetHeight() - (kTopMargin + kBottomMargin);
|
||||
rect.SetHeight( height );
|
||||
const auto subViews = view.GetSubViews( rect );
|
||||
if (subViews.empty())
|
||||
continue;
|
||||
@ -1142,7 +1143,7 @@ struct ChannelGroup final : TrackPanelGroup {
|
||||
auto &view = TrackView::Get( *channel );
|
||||
auto height = view.GetHeight();
|
||||
rect.SetTop( yy );
|
||||
rect.SetHeight( height );
|
||||
rect.SetHeight( height - kSeparatorThickness );
|
||||
const auto subViews = TrackView::Get( *channel ).GetSubViews( rect );
|
||||
auto y1 = yy;
|
||||
for ( const auto &subView : subViews ) {
|
||||
|
@ -1073,19 +1073,26 @@ bool NyquistEffect::ProcessOne()
|
||||
type = wxT("wave");
|
||||
spectralEditp = mCurTrack[0]->GetSpectrogramSettings().SpectralSelectionEnabled()? wxT("T") : wxT("NIL");
|
||||
// To do: accommodate split views
|
||||
auto viewType = WaveTrackViewConstants::NoDisplay;
|
||||
auto displays = WaveTrackView::Get( *wt ).GetDisplays();
|
||||
if (!displays.empty())
|
||||
viewType = displays[0];
|
||||
switch ( viewType )
|
||||
auto format = [&]( decltype(displays[0]) display ){
|
||||
switch ( display )
|
||||
{
|
||||
case Waveform:
|
||||
view = (mCurTrack[0]->GetWaveformSettings().scaleType == 0) ? wxT("\"Waveform\"") : wxT("\"Waveform (dB)\"");
|
||||
break;
|
||||
return (mCurTrack[0]->GetWaveformSettings().scaleType == 0) ? wxT("\"Waveform\"") : wxT("\"Waveform (dB)\"");
|
||||
case Spectrum:
|
||||
view = wxT("\"Spectrogram\"");
|
||||
break;
|
||||
default: view = wxT("NIL"); break;
|
||||
return wxT("\"Spectrogram\"");
|
||||
default: return wxT("NIL");
|
||||
}
|
||||
};
|
||||
if (displays.empty())
|
||||
view = wxT("NIL");
|
||||
else if (displays.size() == 1)
|
||||
view = format( displays[0] );
|
||||
else {
|
||||
view = wxT("(list");
|
||||
for ( auto display : displays )
|
||||
view += wxString(wxT(" ")) + format( display );
|
||||
view += wxT(")");
|
||||
}
|
||||
},
|
||||
#if defined(USE_MIDI)
|
||||
|
@ -649,6 +649,6 @@ void SpectrumView::Draw(
|
||||
|
||||
static const WaveTrackSubViews::RegisteredFactory key{
|
||||
[]( WaveTrackView &view ){
|
||||
return std::make_shared< SpectrumView >( view.FindTrack() );
|
||||
return std::make_shared< SpectrumView >( view );
|
||||
}
|
||||
};
|
||||
|
@ -690,10 +690,13 @@ void WaveTrackMenuTable::InitMenu(Menu *pMenu, void *pUserData)
|
||||
BEGIN_POPUP_MENU(WaveTrackMenuTable)
|
||||
POPUP_MENU_SEPARATOR()
|
||||
|
||||
// These radio items may become non-exclusive check items
|
||||
POPUP_MENU_RADIO_ITEM(OnWaveformID, _("Wa&veform"), OnSetDisplay)
|
||||
POPUP_MENU_RADIO_ITEM(OnWaveformDBID, _("&Waveform (dB)"), OnSetDisplay)
|
||||
POPUP_MENU_RADIO_ITEM(OnSpectrumID, _("&Spectrogram"), OnSetDisplay)
|
||||
// View types are now a non-exclusive choice. The first two are mutually
|
||||
// exclusive, but the view may be in a state with either of those, and also
|
||||
// spectrogram, after a mouse drag. Clicking any of these three makes that
|
||||
// view take up all the height.
|
||||
POPUP_MENU_CHECK_ITEM(OnWaveformID, _("Wa&veform"), OnSetDisplay)
|
||||
POPUP_MENU_CHECK_ITEM(OnWaveformDBID, _("&Waveform (dB)"), OnSetDisplay)
|
||||
POPUP_MENU_CHECK_ITEM(OnSpectrumID, _("&Spectrogram"), OnSetDisplay)
|
||||
|
||||
POPUP_MENU_ITEM(OnSpectrogramSettingsID, _("S&pectrogram Settings..."), OnSpectrogramSettings)
|
||||
POPUP_MENU_SEPARATOR()
|
||||
|
@ -12,11 +12,15 @@ Paul Licameli split from TrackPanel.cpp
|
||||
|
||||
#include "../../../../Experimental.h"
|
||||
|
||||
#include <numeric>
|
||||
#include <wx/graphics.h>
|
||||
|
||||
#include "../../../../WaveClip.h"
|
||||
#include "../../../../WaveTrack.h"
|
||||
|
||||
#include "../../../../HitTestResult.h"
|
||||
#include "../../../../ProjectHistory.h"
|
||||
#include "../../../../RefreshCode.h"
|
||||
#include "../../../../TrackArtist.h"
|
||||
#include "../../../../TrackPanelDrawingContext.h"
|
||||
#include "../../../../TrackPanelMouseEvent.h"
|
||||
@ -27,6 +31,335 @@ Paul Licameli split from TrackPanel.cpp
|
||||
|
||||
#include "../../../ui/TimeShiftHandle.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using WaveTrackSubViewPtrs = std::vector< std::shared_ptr< WaveTrackSubView > >;
|
||||
|
||||
// Structure that collects and modifies information on sub-view positions
|
||||
// Written with great generality, allowing any number of sub-views
|
||||
struct SubViewAdjuster
|
||||
{
|
||||
enum { HotZoneSize = 5 }; // so many pixels at top and bottom of each subview
|
||||
|
||||
SubViewAdjuster( WaveTrackView &view )
|
||||
: mwView{
|
||||
std::static_pointer_cast<WaveTrackView>( view.shared_from_this() ) }
|
||||
{
|
||||
mSubViews = view.GetAllSubViews();
|
||||
mOrigPlacements = mNewPlacements = view.SavePlacements();
|
||||
FindPermutation();
|
||||
}
|
||||
|
||||
void FindPermutation()
|
||||
{
|
||||
// Find a certain sort of the sub-views
|
||||
auto size = mOrigPlacements.size();
|
||||
wxASSERT( mSubViews.size() == size );
|
||||
mPermutation.resize( size );
|
||||
const auto begin = mPermutation.begin(), end = mPermutation.end();
|
||||
std::iota( begin, end, 0 );
|
||||
static auto invisible = []( const WaveTrackSubViewPlacement &placement ){
|
||||
return placement.index < 0 || placement.fraction <= 0;
|
||||
};
|
||||
const auto comp = [this]( size_t ii, size_t jj ){
|
||||
auto &pi = mOrigPlacements[ii];
|
||||
bool iInvisible = invisible( pi );
|
||||
|
||||
auto &pj = mOrigPlacements[jj];
|
||||
bool jInvisible = invisible( pj );
|
||||
|
||||
// Sort the invisibles to the front, rest by index
|
||||
if ( iInvisible != jInvisible )
|
||||
return iInvisible;
|
||||
else if ( !iInvisible )
|
||||
return pi.index < pj.index;
|
||||
else
|
||||
// Minor sort among the invisible views by their type
|
||||
return mSubViews[ii]->SubViewType() < mSubViews[jj]->SubViewType();
|
||||
};
|
||||
std::sort( begin, end, comp );
|
||||
// Find the start of visible sub-views
|
||||
auto first = std::find_if( begin, end, [this](size_t ii){
|
||||
return !invisible( mOrigPlacements[ii] );
|
||||
} );
|
||||
mFirstSubView = first - begin;
|
||||
}
|
||||
|
||||
bool ModifyPermutation( bool top )
|
||||
{
|
||||
bool rotated = false;
|
||||
const auto pBegin = mPermutation.begin(), pEnd = mPermutation.end();
|
||||
auto pFirst = pBegin + mFirstSubView;
|
||||
if ( mFirstSubView > 0 ) {
|
||||
// In case of dragging the top edge of the topmost view, or the
|
||||
// bottom edge of the bottommost, decide which of the invisible
|
||||
// views can become visible, and reassign the sequence.
|
||||
// For definiteness, that choice depends on the subview type numbers;
|
||||
// see the sorting criteria above.
|
||||
--mFirstSubView;
|
||||
--pFirst;
|
||||
if ( top ) {
|
||||
// If you drag down the top, the greatest-numbered invisible
|
||||
// subview type will appear there.
|
||||
mNewPlacements[ *pFirst ].fraction = 0;
|
||||
}
|
||||
else {
|
||||
// If you drag up the bottom, let the least-numbered invisible
|
||||
// subview type appear there.
|
||||
mNewPlacements[ *pBegin ].fraction = 0;
|
||||
std::rotate( pBegin, pBegin + 1, pEnd );
|
||||
rotated = true;
|
||||
}
|
||||
}
|
||||
// Reassign index numbers to all sub-views and 0 fraction to invisibles
|
||||
for ( auto pIter = pBegin; pIter != pFirst; ++pIter ) {
|
||||
auto &placement = mNewPlacements[ *pIter ];
|
||||
placement.index = -1;
|
||||
placement.fraction = 0;
|
||||
}
|
||||
size_t index = 0;
|
||||
for ( auto pIter = pFirst; pIter != pEnd; ++pIter )
|
||||
mNewPlacements[ *pIter ].index = index++;
|
||||
return rotated;
|
||||
}
|
||||
|
||||
std::pair< size_t, bool >
|
||||
HitTest( WaveTrackSubView &subView,
|
||||
wxCoord yy, wxCoord top, wxCoord height )
|
||||
{
|
||||
const auto begin = mPermutation.begin(), end = mPermutation.end();
|
||||
auto iter = std::find_if( begin, end, [&](size_t ii){
|
||||
return mSubViews[ ii ].get() == &subView;
|
||||
} );
|
||||
auto index = iter - begin;
|
||||
auto size = mPermutation.size();
|
||||
if ( index < size ) {
|
||||
yy -= top;
|
||||
if ( yy >= 0 && yy < HotZoneSize && index > 0 )
|
||||
return { index, true }; // top hit
|
||||
if ( yy < height && yy >= height - HotZoneSize &&
|
||||
// Have not yet called ModifyPermutation; dragging bottom of
|
||||
// bottommost view allowed only if at least one view is invisible
|
||||
( index < size - 1 || mFirstSubView > 0 ) )
|
||||
return { index, false }; // bottom hit
|
||||
}
|
||||
return { size, false }; // not hit
|
||||
}
|
||||
|
||||
void UpdateViews( bool rollback )
|
||||
{
|
||||
auto pView = mwView.lock();
|
||||
if ( pView ) {
|
||||
auto pTrack = static_cast< WaveTrack* >( pView->FindTrack().get() );
|
||||
for ( auto pChannel : TrackList::Channels<WaveTrack>( pTrack ) )
|
||||
WaveTrackView::Get( *pChannel ).RestorePlacements(
|
||||
rollback ? mOrigPlacements : mNewPlacements );
|
||||
}
|
||||
}
|
||||
|
||||
std::weak_ptr< WaveTrackView > mwView;
|
||||
WaveTrackSubViewPtrs mSubViews;
|
||||
WaveTrackSubViewPlacements mOrigPlacements, mNewPlacements;
|
||||
// Array mapping ordinal into the placement and subview arrays
|
||||
std::vector< size_t > mPermutation;
|
||||
// index into mPermutation
|
||||
size_t mFirstSubView{};
|
||||
};
|
||||
|
||||
class SubViewAdjustHandle : public UIHandle
|
||||
{
|
||||
public:
|
||||
enum { MinHeight = SubViewAdjuster::HotZoneSize };
|
||||
|
||||
static UIHandlePtr HitTest(
|
||||
WaveTrackView &view, WaveTrackSubView &subView,
|
||||
const TrackPanelMouseState &state )
|
||||
{
|
||||
SubViewAdjuster adjuster{ view };
|
||||
auto hit = adjuster.HitTest( subView,
|
||||
state.state.GetY(), state.rect.GetTop(), state.rect.GetHeight() );
|
||||
auto index = hit.first;
|
||||
|
||||
if ( index < adjuster.mPermutation.size() )
|
||||
return std::make_shared< SubViewAdjustHandle >(
|
||||
std::move( adjuster ), index, hit.second
|
||||
);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
SubViewAdjustHandle(
|
||||
SubViewAdjuster &&adjuster, size_t subViewIndex, bool top )
|
||||
: mAdjuster{ std::move( adjuster ) }
|
||||
, mMySubView{ subViewIndex }
|
||||
, mTop{ top }
|
||||
{
|
||||
if ( mAdjuster.ModifyPermutation( top ) )
|
||||
--mMySubView;
|
||||
}
|
||||
|
||||
Result Click(
|
||||
const TrackPanelMouseEvent &event, AudacityProject * ) override
|
||||
{
|
||||
using namespace RefreshCode;
|
||||
const auto &permutation = mAdjuster.mPermutation;
|
||||
const auto size = permutation.size();
|
||||
if ( mMySubView >= size )
|
||||
return Cancelled;
|
||||
|
||||
const auto &rect = event.rect;
|
||||
const auto height = rect.GetHeight();
|
||||
mOrigHeight = height;
|
||||
|
||||
wxASSERT( height ==
|
||||
mAdjuster.mOrigPlacements[ mAdjuster.mPermutation[ mMySubView ] ]
|
||||
.fraction
|
||||
);
|
||||
|
||||
// Find the total height of the sub-views that may resize
|
||||
// Note that this depends on the redenomination of fractions that
|
||||
// happened in the last call to GetSubViews
|
||||
mTotalHeight = 0;
|
||||
const auto begin = permutation.begin();
|
||||
auto iter = begin + ( mTop ? mAdjuster.mFirstSubView : mMySubView );
|
||||
const auto end = ( mTop ? begin + mMySubView + 1 : permutation.end() );
|
||||
for (; iter != end; ++iter) {
|
||||
const auto &placement = mAdjuster.mOrigPlacements[ *iter ];
|
||||
mTotalHeight += placement.fraction;
|
||||
}
|
||||
|
||||
// Compute the maximum and minimum Y coordinates for drag effect
|
||||
if ( mTop ) {
|
||||
mOrigY = rect.GetTop();
|
||||
mYMax = rect.GetBottom();
|
||||
mYMin = mYMax - mTotalHeight + 1;
|
||||
}
|
||||
else {
|
||||
mOrigY = rect.GetBottom();
|
||||
mYMin = rect.GetTop();
|
||||
mYMax = mYMin + mTotalHeight - 1;
|
||||
}
|
||||
|
||||
return RefreshNone;
|
||||
}
|
||||
|
||||
Result Drag( const TrackPanelMouseEvent &event, AudacityProject * ) override
|
||||
{
|
||||
using namespace RefreshCode;
|
||||
auto pView = mAdjuster.mwView.lock();
|
||||
if ( !pView )
|
||||
return Cancelled;
|
||||
|
||||
// Find new height for the dragged sub-view
|
||||
auto newY = std::max( mYMin, std::min( mYMax, event.event.GetY() ) );
|
||||
const auto delta = newY - mOrigY;
|
||||
wxCoord newHeight = mTop
|
||||
? mOrigHeight - delta
|
||||
: mOrigHeight + delta
|
||||
;
|
||||
wxASSERT( newHeight >= 0 && newHeight <= mTotalHeight );
|
||||
if ( newHeight < MinHeight )
|
||||
// Snap the dragged sub-view to nothing
|
||||
newHeight = 0;
|
||||
|
||||
// Reassign height for the dragged sub-view
|
||||
auto &myPlacement =
|
||||
mAdjuster.mNewPlacements[ mAdjuster.mPermutation[ mMySubView ] ];
|
||||
myPlacement.fraction = newHeight;
|
||||
|
||||
// Grow or shrink other sub-views
|
||||
auto excess = newHeight - mOrigHeight; // maybe negative
|
||||
const auto adjustHeight = [&](size_t ii) {
|
||||
if (excess == 0)
|
||||
return true;
|
||||
|
||||
auto index = mAdjuster.mPermutation[ ii ];
|
||||
|
||||
const auto &origPlacement = mAdjuster.mOrigPlacements[ index ];
|
||||
const auto oldFraction = origPlacement.fraction;
|
||||
|
||||
auto &placement = mAdjuster.mNewPlacements[ index ];
|
||||
auto &fraction = placement.fraction;
|
||||
|
||||
if (excess > oldFraction) {
|
||||
excess -= oldFraction, fraction = 0;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
auto newFraction = oldFraction - excess;
|
||||
if ( newFraction < MinHeight ) {
|
||||
// This snaps very short sub-views to nothing
|
||||
myPlacement.fraction += newFraction;
|
||||
fraction = 0;
|
||||
}
|
||||
else
|
||||
fraction = newFraction;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if ( mTop ) {
|
||||
for ( size_t ii = mMySubView; ii > 0; ) {
|
||||
--ii;
|
||||
if ( adjustHeight( ii ) )
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ( size_t ii = mMySubView + 1, size = mAdjuster.mPermutation.size();
|
||||
ii < size; ++ii
|
||||
) {
|
||||
if ( adjustHeight( ii ) )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Save adjustment to the track and request a redraw
|
||||
mAdjuster.UpdateViews( false );
|
||||
return RefreshAll;
|
||||
}
|
||||
|
||||
HitTestPreview Preview(
|
||||
const TrackPanelMouseState &state, const AudacityProject * ) override
|
||||
{
|
||||
static wxCursor resizeCursor{ wxCURSOR_SIZENS };
|
||||
return {
|
||||
_("Click and drag to adjust sizes of sub-views."),
|
||||
&resizeCursor
|
||||
};
|
||||
}
|
||||
|
||||
Result Release(
|
||||
const TrackPanelMouseEvent &event, AudacityProject *pProject,
|
||||
wxWindow *pParent) override
|
||||
{
|
||||
ProjectHistory::Get( *pProject ).ModifyState( false );
|
||||
return RefreshCode::RefreshNone;
|
||||
}
|
||||
|
||||
Result Cancel( AudacityProject * ) override
|
||||
{
|
||||
mAdjuster.UpdateViews( true );
|
||||
return RefreshCode::RefreshAll;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
SubViewAdjuster mAdjuster;
|
||||
|
||||
// An index into mAdjuster.mPermutation
|
||||
size_t mMySubView{};
|
||||
|
||||
wxCoord mYMin{}, mYMax{};
|
||||
wxCoord mTotalHeight{};
|
||||
wxCoord mOrigHeight{};
|
||||
wxCoord mOrigY{};
|
||||
|
||||
// Whether we drag the top or the bottom of the sub-view
|
||||
bool mTop{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::pair<
|
||||
bool, // if true, hit-testing is finished
|
||||
std::vector<UIHandlePtr>
|
||||
@ -36,8 +369,20 @@ std::pair<
|
||||
const std::shared_ptr<WaveTrack> &wt,
|
||||
CommonTrackView &view)
|
||||
{
|
||||
return WaveTrackView::DoDetailedHitTest(
|
||||
auto results = WaveTrackView::DoDetailedHitTest(
|
||||
state, pProject, currentTool, bMultiTool, wt, view );
|
||||
if ( results.first )
|
||||
return results;
|
||||
|
||||
auto pWaveTrackView = mwWaveTrackView.lock();
|
||||
if ( pWaveTrackView && !state.state.HasModifiers() ) {
|
||||
auto pHandle = SubViewAdjustHandle::HitTest(
|
||||
*pWaveTrackView, *this, state );
|
||||
if (pHandle)
|
||||
results.second.push_back( pHandle );
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
WaveTrackView &WaveTrackView::Get( WaveTrack &track )
|
||||
@ -53,22 +398,13 @@ const WaveTrackView &WaveTrackView::Get( const WaveTrack &track )
|
||||
WaveTrackView::WaveTrackView( const std::shared_ptr<Track> &pTrack )
|
||||
: CommonTrackView{ pTrack }
|
||||
{
|
||||
WaveTrackSubViews::BuildAll();
|
||||
|
||||
auto display = TracksPrefs::ViewModeChoice();
|
||||
|
||||
// Force creation always:
|
||||
WaveformSettings &settings = static_cast< WaveTrack* >( pTrack.get() )
|
||||
->GetIndependentWaveformSettings();
|
||||
|
||||
if (display == WaveTrackViewConstants::obsoleteWaveformDBDisplay) {
|
||||
display = WaveTrackViewConstants::Waveform;
|
||||
settings.scaleType = WaveformSettings::stLogarithmic;
|
||||
}
|
||||
|
||||
mPlacements.resize( WaveTrackSubViews::size() );
|
||||
|
||||
SetDisplay( display );
|
||||
WaveTrackSubView::WaveTrackSubView( WaveTrackView &waveTrackView )
|
||||
: CommonTrackView( waveTrackView.FindTrack() )
|
||||
{
|
||||
mwWaveTrackView = std::static_pointer_cast<WaveTrackView>(
|
||||
waveTrackView.shared_from_this() );
|
||||
}
|
||||
|
||||
WaveTrackView::~WaveTrackView()
|
||||
@ -128,6 +464,8 @@ WaveTrackView::DoDetailedHitTest
|
||||
|
||||
auto WaveTrackView::GetDisplays() const -> std::vector<WaveTrackDisplay>
|
||||
{
|
||||
BuildSubViews();
|
||||
|
||||
// Collect the display types of visible views and sort them by position
|
||||
using Pair = std::pair< int, WaveTrackDisplay >;
|
||||
std::vector< Pair > pairs;
|
||||
@ -146,6 +484,12 @@ auto WaveTrackView::GetDisplays() const -> std::vector<WaveTrackDisplay>
|
||||
}
|
||||
|
||||
void WaveTrackView::SetDisplay(WaveTrackDisplay display)
|
||||
{
|
||||
BuildSubViews();
|
||||
DoSetDisplay( display );
|
||||
}
|
||||
|
||||
void WaveTrackView::DoSetDisplay(WaveTrackDisplay display)
|
||||
{
|
||||
size_t ii = 0;
|
||||
WaveTrackSubViews::ForEach( [&,display]( WaveTrackSubView &subView ){
|
||||
@ -159,18 +503,22 @@ void WaveTrackView::SetDisplay(WaveTrackDisplay display)
|
||||
|
||||
auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
|
||||
{
|
||||
// Collect the visible views
|
||||
using Pair = std::pair< float, std::shared_ptr< TrackView > >;
|
||||
BuildSubViews();
|
||||
|
||||
Refinement results;
|
||||
|
||||
// Collect the visible views in the right sequence
|
||||
using Pair = std::pair< float*, std::shared_ptr< TrackView > >;
|
||||
std::vector< Pair > pairs( mPlacements.size() );
|
||||
size_t ii = 0;
|
||||
float total = 0;
|
||||
WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
|
||||
const auto &placement = mPlacements[ii];
|
||||
auto &placement = mPlacements[ii];
|
||||
auto index = placement.index;
|
||||
auto fraction = placement.fraction;
|
||||
auto &fraction = placement.fraction;
|
||||
if ( index >= 0 && fraction > 0.0 )
|
||||
total += fraction,
|
||||
pairs[ index ] = { fraction, subView.shared_from_this() };
|
||||
pairs[ index ] = { &fraction, subView.shared_from_this() };
|
||||
++ii;
|
||||
} );
|
||||
|
||||
@ -179,17 +527,27 @@ auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
|
||||
newEnd = std::remove_if( begin, end,
|
||||
[]( const Pair &item ){ return !item.second; } );
|
||||
pairs.erase( newEnd, end );
|
||||
results.reserve( pairs.size() );
|
||||
|
||||
// Assign coordinates
|
||||
Refinement results;
|
||||
results.reserve( pairs.size() );
|
||||
float partial = 0;
|
||||
// Also update the stored placements, redenominating to the total height,
|
||||
// storing integer values
|
||||
const auto top = rect.GetTop();
|
||||
const auto height = rect.GetHeight();
|
||||
float partial = 0;
|
||||
wxCoord lastCoord = 0;
|
||||
float *lastFraction = nullptr;
|
||||
for ( const auto &pair : pairs ) {
|
||||
results.emplace_back( top + (partial / total) * height, pair.second );
|
||||
partial += pair.first;
|
||||
wxCoord newCoord = top + (partial / total) * height;
|
||||
results.emplace_back( newCoord, pair.second );
|
||||
partial += *pair.first;
|
||||
if (lastFraction)
|
||||
*lastFraction = newCoord - lastCoord;
|
||||
lastFraction = pair.first;
|
||||
lastCoord = newCoord;
|
||||
}
|
||||
if ( lastFraction )
|
||||
*lastFraction = top + height - lastCoord;
|
||||
|
||||
return results;
|
||||
}
|
||||
@ -197,6 +555,8 @@ auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
|
||||
std::vector< std::shared_ptr< WaveTrackSubView > >
|
||||
WaveTrackView::GetAllSubViews()
|
||||
{
|
||||
BuildSubViews();
|
||||
|
||||
std::vector< std::shared_ptr< WaveTrackSubView > > results;
|
||||
WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
|
||||
results.push_back( std::static_pointer_cast<WaveTrackSubView>(
|
||||
@ -207,6 +567,8 @@ WaveTrackView::GetAllSubViews()
|
||||
|
||||
void WaveTrackView::DoSetMinimized( bool minimized )
|
||||
{
|
||||
BuildSubViews();
|
||||
|
||||
// May come here. Invoke also on sub-views.
|
||||
TrackView::DoSetMinimized( minimized );
|
||||
WaveTrackSubViews::ForEach( [minimized](WaveTrackSubView &subView){
|
||||
@ -416,12 +778,41 @@ ClipParameters::ClipParameters
|
||||
|
||||
void WaveTrackView::Reparent( const std::shared_ptr<Track> &parent )
|
||||
{
|
||||
// BuildSubViews(); // not really needed
|
||||
CommonTrackView::Reparent( parent );
|
||||
WaveTrackSubViews::ForEach( [&parent](WaveTrackSubView &subView){
|
||||
subView.Reparent( parent );
|
||||
} );
|
||||
}
|
||||
|
||||
void WaveTrackView::BuildSubViews() const
|
||||
{
|
||||
if ( WaveTrackSubViews::size() == 0) {
|
||||
// On-demand steps that can't happen in the constructor
|
||||
auto pThis = const_cast<WaveTrackView*>( this );
|
||||
pThis->BuildAll();
|
||||
pThis->mPlacements.resize( WaveTrackSubViews::size() );
|
||||
bool minimized = GetMinimized();
|
||||
pThis->WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
|
||||
subView.DoSetMinimized( minimized );
|
||||
} );
|
||||
|
||||
auto pTrack = pThis->FindTrack();
|
||||
auto display = TracksPrefs::ViewModeChoice();
|
||||
|
||||
// Force creation always:
|
||||
WaveformSettings &settings = static_cast< WaveTrack* >( pTrack.get() )
|
||||
->GetIndependentWaveformSettings();
|
||||
|
||||
if (display == WaveTrackViewConstants::obsoleteWaveformDBDisplay) {
|
||||
display = WaveTrackViewConstants::Waveform;
|
||||
settings.scaleType = WaveformSettings::stLogarithmic;
|
||||
}
|
||||
|
||||
pThis->DoSetDisplay( display );
|
||||
}
|
||||
}
|
||||
|
||||
void WaveTrackView::Draw(
|
||||
TrackPanelDrawingContext &context,
|
||||
const wxRect &rect, unsigned iPass )
|
||||
|
@ -16,10 +16,13 @@ Paul Licameli split from class WaveTrack
|
||||
namespace WaveTrackViewConstants{ enum Display : int; }
|
||||
|
||||
class WaveTrack;
|
||||
class WaveTrackView;
|
||||
|
||||
class WaveTrackSubView : public CommonTrackView
|
||||
{
|
||||
public:
|
||||
using CommonTrackView::CommonTrackView;
|
||||
explicit
|
||||
WaveTrackSubView( WaveTrackView &waveTrackView );
|
||||
|
||||
virtual WaveTrackViewConstants::Display SubViewType() const = 0;
|
||||
|
||||
@ -31,6 +34,8 @@ public:
|
||||
const AudacityProject *pProject, int currentTool, bool bMultiTool,
|
||||
const std::shared_ptr<WaveTrack> &wt,
|
||||
CommonTrackView &view);
|
||||
private:
|
||||
std::weak_ptr<WaveTrackView> mwWaveTrackView;
|
||||
};
|
||||
|
||||
struct WaveTrackSubViewPlacement {
|
||||
@ -91,6 +96,9 @@ public:
|
||||
std::vector< std::shared_ptr< WaveTrackSubView > > GetAllSubViews();
|
||||
|
||||
private:
|
||||
void BuildSubViews() const;
|
||||
void DoSetDisplay(WaveTrackDisplay display);
|
||||
|
||||
// TrackPanelDrawable implementation
|
||||
void Draw(
|
||||
TrackPanelDrawingContext &context,
|
||||
|
@ -1084,6 +1084,6 @@ void WaveformView::Draw(
|
||||
|
||||
static const WaveTrackSubViews::RegisteredFactory key{
|
||||
[]( WaveTrackView &view ){
|
||||
return std::make_shared< WaveformView >( view.FindTrack() );
|
||||
return std::make_shared< WaveformView >( view );
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user