1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 TrackPanelResizeHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 
12 #include "TrackPanelResizeHandle.h"
13 
14 #include <wx/cursor.h>
15 #include <wx/translation.h>
16 
17 #include "HitTestResult.h"
18 #include "ProjectHistory.h"
19 #include "RefreshCode.h"
20 #include "Track.h"
21 #include "TrackPanelMouseEvent.h"
22 #include "tracks/ui/TrackView.h"
23 
HitPreview(bool bLinked)24 HitTestPreview TrackPanelResizeHandle::HitPreview(bool bLinked)
25 {
26    // TODO: more-than-two-channels-message
27 
28    static wxCursor resizeCursor{ wxCURSOR_SIZENS };
29 
30    /// When in the resize area we can adjust size or relative size.
31    // Check to see whether it is the first channel of a stereo track
32    if (bLinked) {
33       // If we are in the label we got here 'by mistake' and we're
34       // not actually in the resize area at all.  (The resize area
35       // is shorter when it is between stereo tracks).
36 
37       return {
38          XO(
39 "Click and drag to adjust relative size of stereo tracks, double-click to make heights equal"),
40          &resizeCursor
41       };
42    }
43    else {
44       return {
45          XO("Click and drag to resize the track."),
46          &resizeCursor
47       };
48    }
49 }
50 
~TrackPanelResizeHandle()51 TrackPanelResizeHandle::~TrackPanelResizeHandle()
52 {
53 }
54 
Click(const TrackPanelMouseEvent & evt,AudacityProject * pProject)55 UIHandle::Result TrackPanelResizeHandle::Click(
56    const TrackPanelMouseEvent &evt, AudacityProject *pProject )
57 {
58    using namespace RefreshCode;
59    if ( evt.event.LeftDClick() && mMode == IsResizingBetweenLinkedTracks ) {
60       auto &tracks = TrackList::Get( *pProject );
61       auto pTrack = tracks.Lock(mpTrack);
62       if (pTrack &&
63           !TrackView::Get(*pTrack).GetMinimized()) {
64          auto range = TrackList::Channels( pTrack.get() );
65          auto size = range.size();
66          auto height = range.sum( [](const Track *pTrack){
67             return TrackView::Get(*pTrack).GetHeight(); } );
68          int ii = 1;
69          int coord = 0;
70          for ( const auto channel : range ) {
71             int newCoord = ((double)ii++ /size) * height;
72             TrackView::Get(*channel).SetExpandedHeight( newCoord - coord );
73             coord = newCoord;
74          }
75          ProjectHistory::Get( *pProject ).ModifyState(false);
76          // Do not start a drag
77          return Cancelled | RefreshAll;
78       }
79    }
80    return RefreshNone;
81 }
82 
TrackPanelResizeHandle(const std::shared_ptr<Track> & track,int y)83 TrackPanelResizeHandle::TrackPanelResizeHandle
84 ( const std::shared_ptr<Track> &track, int y )
85    : mpTrack{ track }
86    , mMouseClickY( y )
87 {
88    // TODO: more-than-two-channels
89 
90    //STM:  Determine whether we should rescale one or two tracks
91    auto channels = TrackList::Channels(track.get());
92    auto last = *channels.rbegin();
93    auto &lastView = TrackView::Get( *last );
94    mInitialTrackHeight = lastView.GetHeight();
95    mInitialExpandedHeight = lastView.GetExpandedHeight();
96    mInitialMinimized = lastView.GetMinimized();
97 
98    if (channels.size() > 1) {
99       auto first = *channels.begin();
100       auto &firstView = TrackView::Get( *first );
101 
102       mInitialUpperTrackHeight = firstView.GetHeight();
103       mInitialUpperExpandedHeight = firstView.GetExpandedHeight();
104 
105       if (track.get() == *channels.rbegin())
106          // capturedTrack is the lowest track
107          mMode = IsResizingBelowLinkedTracks;
108       else
109          // capturedTrack is not the lowest track
110          mMode = IsResizingBetweenLinkedTracks;
111    }
112    else
113       mMode = IsResizing;
114 }
115 
Drag(const TrackPanelMouseEvent & evt,AudacityProject * pProject)116 UIHandle::Result TrackPanelResizeHandle::Drag
117 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
118 {
119    auto &tracks = TrackList::Get( *pProject );
120    auto pTrack = tracks.Lock(mpTrack);
121    if ( !pTrack )
122       return RefreshCode::Cancelled;
123 
124    auto &view = TrackView::Get( *pTrack );
125 
126    const wxMouseEvent &event = evt.event;
127 
128    int delta = (event.m_y - mMouseClickY);
129 
130    // On first drag, jump out of minimized mode.  Initial height
131    // will be height of minimized track.
132    //
133    // This used to be in HandleResizeClick(), but simply clicking
134    // on a resize border would switch the minimized state.
135    auto &data = TrackView::Get( *pTrack );
136    if (data.GetMinimized()) {
137       auto channels = TrackList::Channels( pTrack.get() );
138       for (auto channel : channels) {
139          auto &channelView = TrackView::Get( *channel );
140          channelView.SetExpandedHeight(channelView.GetHeight());
141          channelView.SetMinimized( false );
142       }
143 
144       if (channels.size() > 1) {
145          // Initial values must be reset since they weren't based on the
146          // minimized heights.
147          auto &channelView = TrackView::Get( **channels.begin() );
148          mInitialUpperTrackHeight = channelView.GetHeight();
149          mInitialTrackHeight = channelView.GetHeight();
150       }
151    }
152 
153    // Common pieces of code for MONO_WAVE_PAN and otherwise.
154    auto doResizeBelow = [&] (Track *prev, bool WXUNUSED(vStereo)) {
155       // TODO: more-than-two-channels
156 
157       auto &prevView = TrackView::Get( *prev );
158 
159       double proportion = static_cast < double >(mInitialTrackHeight)
160       / (mInitialTrackHeight + mInitialUpperTrackHeight);
161 
162       int newTrackHeight = static_cast < int >
163       (mInitialTrackHeight + delta * proportion);
164 
165       int newUpperTrackHeight = static_cast < int >
166       (mInitialUpperTrackHeight + delta * (1.0 - proportion));
167 
168       //make sure neither track is smaller than its minimum height
169       if (newTrackHeight < view.GetMinimizedHeight())
170          newTrackHeight = view.GetMinimizedHeight();
171       if (newUpperTrackHeight < prevView.GetMinimizedHeight())
172          newUpperTrackHeight = prevView.GetMinimizedHeight();
173 
174       view.SetExpandedHeight(newTrackHeight);
175       prevView.SetExpandedHeight(newUpperTrackHeight);
176    };
177 
178    auto doResizeBetween = [&] (Track *next, bool WXUNUSED(vStereo)) {
179       // TODO: more-than-two-channels
180 
181       auto &nextView = TrackView::Get( *next );
182       int newUpperTrackHeight = mInitialUpperTrackHeight + delta;
183       int newTrackHeight = mInitialTrackHeight - delta;
184 
185       // make sure neither track is smaller than its minimum height
186       if (newTrackHeight < nextView.GetMinimizedHeight()) {
187          newTrackHeight = nextView.GetMinimizedHeight();
188          newUpperTrackHeight =
189          mInitialUpperTrackHeight + mInitialTrackHeight - nextView.GetMinimizedHeight();
190       }
191       if (newUpperTrackHeight < view.GetMinimizedHeight()) {
192          newUpperTrackHeight = view.GetMinimizedHeight();
193          newTrackHeight =
194          mInitialUpperTrackHeight + mInitialTrackHeight - view.GetMinimizedHeight();
195       }
196 
197       view.SetExpandedHeight(newUpperTrackHeight);
198       nextView.SetExpandedHeight(newTrackHeight);
199    };
200 
201    auto doResize = [&] {
202       int newTrackHeight = mInitialTrackHeight + delta;
203       if (newTrackHeight < view.GetMinimizedHeight())
204          newTrackHeight = view.GetMinimizedHeight();
205       view.SetExpandedHeight(newTrackHeight);
206    };
207 
208    //STM: We may be dragging one or two (stereo) tracks.
209    // If two, resize proportionally if we are dragging the lower track, and
210    // adjust compensatively if we are dragging the upper track.
211 
212    switch( mMode )
213    {
214       case IsResizingBelowLinkedTracks:
215       {
216          auto prev = * -- tracks.Find(pTrack.get());
217          doResizeBelow(prev, false);
218          break;
219       }
220       case IsResizingBetweenLinkedTracks:
221       {
222          auto next = * ++ tracks.Find(pTrack.get());
223          doResizeBetween(next, false);
224          break;
225       }
226       case IsResizing:
227       {
228          doResize();
229          break;
230       }
231       default:
232          // don't refresh in this case.
233          return RefreshCode::RefreshNone;
234    }
235 
236    return RefreshCode::RefreshAll;
237 }
238 
Preview(const TrackPanelMouseState &,AudacityProject *)239 HitTestPreview TrackPanelResizeHandle::Preview
240 (const TrackPanelMouseState &, AudacityProject *)
241 {
242    return HitPreview(mMode == IsResizingBetweenLinkedTracks);
243 }
244 
Release(const TrackPanelMouseEvent &,AudacityProject * pProject,wxWindow *)245 UIHandle::Result TrackPanelResizeHandle::Release
246 (const TrackPanelMouseEvent &, AudacityProject *pProject,
247  wxWindow *)
248 {
249    ///  This happens when the button is released from a drag.
250    ///  Since we actually took care of resizing the track when
251    ///  we got drag events, all we have to do here is clean up.
252    ///  We also modify the undo state (the action doesn't become
253    ///  undo-able, but it gets merged with the previous undo-able
254    ///  event).
255    ProjectHistory::Get( *pProject ).ModifyState(false);
256    return RefreshCode::FixScrollbars;
257 }
258 
Cancel(AudacityProject * pProject)259 UIHandle::Result TrackPanelResizeHandle::Cancel(AudacityProject *pProject)
260 {
261    auto &tracks = TrackList::Get( *pProject );
262    auto pTrack = tracks.Lock(mpTrack);
263    if ( !pTrack )
264       return RefreshCode::Cancelled;
265 
266 
267    switch (mMode) {
268    case IsResizing:
269    {
270       auto &view = TrackView::Get( *pTrack );
271       view.SetExpandedHeight(mInitialExpandedHeight);
272       view.SetMinimized( mInitialMinimized );
273    }
274    break;
275    case IsResizingBetweenLinkedTracks:
276    {
277       Track *const next = * ++ tracks.Find(pTrack.get());
278       auto
279          &view = TrackView::Get( *pTrack ), &nextView = TrackView::Get( *next );
280       view.SetExpandedHeight(mInitialUpperExpandedHeight);
281       view.SetMinimized( mInitialMinimized );
282       nextView.SetExpandedHeight(mInitialExpandedHeight);
283       nextView.SetMinimized( mInitialMinimized );
284    }
285    break;
286    case IsResizingBelowLinkedTracks:
287    {
288       Track *const prev = * -- tracks.Find(pTrack.get());
289       auto
290          &view = TrackView::Get( *pTrack ), &prevView = TrackView::Get( *prev );
291       view.SetExpandedHeight(mInitialExpandedHeight);
292       view.SetMinimized( mInitialMinimized );
293       prevView.SetExpandedHeight(mInitialUpperExpandedHeight);
294       prevView.SetMinimized(mInitialMinimized);
295    }
296    break;
297    }
298 
299    return RefreshCode::RefreshAll;
300 }
301