/********************************************************************** Audacity: A Digital Audio Editor TrackPanelResizeHandle.cpp Paul Licameli split from TrackPanel.cpp **********************************************************************/ #include "TrackPanelResizeHandle.h" #include #include #include "HitTestResult.h" #include "ProjectHistory.h" #include "RefreshCode.h" #include "Track.h" #include "TrackPanelMouseEvent.h" #include "tracks/ui/TrackView.h" HitTestPreview TrackPanelResizeHandle::HitPreview(bool bLinked) { // TODO: more-than-two-channels-message static wxCursor resizeCursor{ wxCURSOR_SIZENS }; /// When in the resize area we can adjust size or relative size. // Check to see whether it is the first channel of a stereo track if (bLinked) { // If we are in the label we got here 'by mistake' and we're // not actually in the resize area at all. (The resize area // is shorter when it is between stereo tracks). return { XO( "Click and drag to adjust relative size of stereo tracks, double-click to make heights equal"), &resizeCursor }; } else { return { XO("Click and drag to resize the track."), &resizeCursor }; } } TrackPanelResizeHandle::~TrackPanelResizeHandle() { } UIHandle::Result TrackPanelResizeHandle::Click( const TrackPanelMouseEvent &evt, AudacityProject *pProject ) { using namespace RefreshCode; if ( evt.event.LeftDClick() && mMode == IsResizingBetweenLinkedTracks ) { auto &tracks = TrackList::Get( *pProject ); auto pTrack = tracks.Lock(mpTrack); if (pTrack && !TrackView::Get(*pTrack).GetMinimized()) { auto range = TrackList::Channels( pTrack.get() ); auto size = range.size(); auto height = range.sum( [](const Track *pTrack){ return TrackView::Get(*pTrack).GetHeight(); } ); int ii = 1; int coord = 0; for ( const auto channel : range ) { int newCoord = ((double)ii++ /size) * height; TrackView::Get(*channel).SetHeight( newCoord - coord ); coord = newCoord; } ProjectHistory::Get( *pProject ).ModifyState(false); // Do not start a drag return Cancelled | RefreshAll; } } return RefreshNone; } TrackPanelResizeHandle::TrackPanelResizeHandle ( const std::shared_ptr &track, int y ) : mpTrack{ track } , mMouseClickY( y ) { // TODO: more-than-two-channels //STM: Determine whether we should rescale one or two tracks auto channels = TrackList::Channels(track.get()); auto last = *channels.rbegin(); auto &lastView = TrackView::Get( *last ); mInitialTrackHeight = lastView.GetHeight(); mInitialActualHeight = lastView.GetActualHeight(); mInitialMinimized = lastView.GetMinimized(); if (channels.size() > 1) { auto first = *channels.begin(); auto &firstView = TrackView::Get( *first ); mInitialUpperTrackHeight = firstView.GetHeight(); mInitialUpperActualHeight = firstView.GetActualHeight(); if (track.get() == *channels.rbegin()) // capturedTrack is the lowest track mMode = IsResizingBelowLinkedTracks; else // capturedTrack is not the lowest track mMode = IsResizingBetweenLinkedTracks; } else mMode = IsResizing; } UIHandle::Result TrackPanelResizeHandle::Drag (const TrackPanelMouseEvent &evt, AudacityProject *pProject) { auto &tracks = TrackList::Get( *pProject ); auto pTrack = tracks.Lock(mpTrack); if ( !pTrack ) return RefreshCode::Cancelled; auto &view = TrackView::Get( *pTrack ); const wxMouseEvent &event = evt.event; int delta = (event.m_y - mMouseClickY); // On first drag, jump out of minimized mode. Initial height // will be height of minimized track. // // This used to be in HandleResizeClick(), but simply clicking // on a resize border would switch the minimized state. auto &data = TrackView::Get( *pTrack ); if (data.GetMinimized()) { auto channels = TrackList::Channels( pTrack.get() ); for (auto channel : channels) { auto &channelView = TrackView::Get( *channel ); channelView.SetHeight(channelView.GetHeight()); channelView.SetMinimized( false ); } if (channels.size() > 1) { // Initial values must be reset since they weren't based on the // minimized heights. auto &channelView = TrackView::Get( **channels.begin() ); mInitialUpperTrackHeight = channelView.GetHeight(); mInitialTrackHeight = channelView.GetHeight(); } } // Common pieces of code for MONO_WAVE_PAN and otherwise. auto doResizeBelow = [&] (Track *prev, bool WXUNUSED(vStereo)) { // TODO: more-than-two-channels auto &prevView = TrackView::Get( *prev ); double proportion = static_cast < double >(mInitialTrackHeight) / (mInitialTrackHeight + mInitialUpperTrackHeight); int newTrackHeight = static_cast < int > (mInitialTrackHeight + delta * proportion); int newUpperTrackHeight = static_cast < int > (mInitialUpperTrackHeight + delta * (1.0 - proportion)); //make sure neither track is smaller than its minimum height if (newTrackHeight < view.GetMinimizedHeight()) newTrackHeight = view.GetMinimizedHeight(); if (newUpperTrackHeight < prevView.GetMinimizedHeight()) newUpperTrackHeight = prevView.GetMinimizedHeight(); view.SetHeight(newTrackHeight); prevView.SetHeight(newUpperTrackHeight); }; auto doResizeBetween = [&] (Track *next, bool WXUNUSED(vStereo)) { // TODO: more-than-two-channels auto &nextView = TrackView::Get( *next ); int newUpperTrackHeight = mInitialUpperTrackHeight + delta; int newTrackHeight = mInitialTrackHeight - delta; // make sure neither track is smaller than its minimum height if (newTrackHeight < nextView.GetMinimizedHeight()) { newTrackHeight = nextView.GetMinimizedHeight(); newUpperTrackHeight = mInitialUpperTrackHeight + mInitialTrackHeight - nextView.GetMinimizedHeight(); } if (newUpperTrackHeight < view.GetMinimizedHeight()) { newUpperTrackHeight = view.GetMinimizedHeight(); newTrackHeight = mInitialUpperTrackHeight + mInitialTrackHeight - view.GetMinimizedHeight(); } view.SetHeight(newUpperTrackHeight); nextView.SetHeight(newTrackHeight); }; auto doResize = [&] { int newTrackHeight = mInitialTrackHeight + delta; if (newTrackHeight < view.GetMinimizedHeight()) newTrackHeight = view.GetMinimizedHeight(); view.SetHeight(newTrackHeight); }; //STM: We may be dragging one or two (stereo) tracks. // If two, resize proportionally if we are dragging the lower track, and // adjust compensatively if we are dragging the upper track. switch( mMode ) { case IsResizingBelowLinkedTracks: { auto prev = * -- tracks.Find(pTrack.get()); doResizeBelow(prev, false); break; } case IsResizingBetweenLinkedTracks: { auto next = * ++ tracks.Find(pTrack.get()); doResizeBetween(next, false); break; } case IsResizing: { doResize(); break; } default: // don't refresh in this case. return RefreshCode::RefreshNone; } return RefreshCode::RefreshAll; } HitTestPreview TrackPanelResizeHandle::Preview (const TrackPanelMouseState &, AudacityProject *) { return HitPreview(mMode == IsResizingBetweenLinkedTracks); } UIHandle::Result TrackPanelResizeHandle::Release (const TrackPanelMouseEvent &, AudacityProject *pProject, wxWindow *) { /// This happens when the button is released from a drag. /// Since we actually took care of resizing the track when /// we got drag events, all we have to do here is clean up. /// We also modify the undo state (the action doesn't become /// undo-able, but it gets merged with the previous undo-able /// event). ProjectHistory::Get( *pProject ).ModifyState(false); return RefreshCode::FixScrollbars; } UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject) { auto &tracks = TrackList::Get( *pProject ); auto pTrack = tracks.Lock(mpTrack); if ( !pTrack ) return RefreshCode::Cancelled; switch (mMode) { case IsResizing: { auto &view = TrackView::Get( *pTrack ); view.SetHeight(mInitialActualHeight); view.SetMinimized( mInitialMinimized ); } break; case IsResizingBetweenLinkedTracks: { Track *const next = * ++ tracks.Find(pTrack.get()); auto &view = TrackView::Get( *pTrack ), &nextView = TrackView::Get( *next ); view.SetHeight(mInitialUpperActualHeight); view.SetMinimized( mInitialMinimized ); nextView.SetHeight(mInitialActualHeight); nextView.SetMinimized( mInitialMinimized ); } break; case IsResizingBelowLinkedTracks: { Track *const prev = * -- tracks.Find(pTrack.get()); auto &view = TrackView::Get( *pTrack ), &prevView = TrackView::Get( *prev ); view.SetHeight(mInitialActualHeight); view.SetMinimized( mInitialMinimized ); prevView.SetHeight(mInitialUpperActualHeight); prevView.SetMinimized(mInitialMinimized); } break; } return RefreshCode::RefreshAll; }