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