1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 CommonTrackControls.cpp
6
7 Paul Licameli split from TrackControls.cpp
8
9 **********************************************************************/
10
11 #include "CommonTrackControls.h"
12
13 #include "TrackButtonHandles.h"
14 #include "TrackSelectHandle.h"
15 #include "AColor.h"
16 #include "../../RefreshCode.h"
17 #include "Project.h"
18 #include "../../ProjectHistory.h"
19 #include "../../ProjectWindows.h"
20 #include "../../TrackArtist.h"
21 #include "../../TrackInfo.h"
22 #include "../../TrackPanelDrawingContext.h"
23 #include "../../TrackPanelMouseEvent.h"
24 #include "../../TrackUtilities.h"
25 #include <wx/textdlg.h>
26 #include "../../commands/AudacityCommand.h"
27 #include "../../commands/CommandManager.h"
28 #include "../../ShuttleGui.h"
29 #include "../../Track.h"
30 #include "../../widgets/PopupMenuTable.h"
31
32 #include <wx/dc.h>
33 #include <wx/frame.h>
34
HitTest(const TrackPanelMouseState & st,const AudacityProject * WXUNUSED (project))35 std::vector<UIHandlePtr> CommonTrackControls::HitTest
36 (const TrackPanelMouseState &st,
37 const AudacityProject *WXUNUSED(project))
38 {
39 // Hits are mutually exclusive, results single
40
41 const wxMouseState &state = st.state;
42 const wxRect &rect = st.rect;
43 UIHandlePtr result;
44 std::vector<UIHandlePtr> results;
45
46 auto sThis = shared_from_this();
47
48 if (NULL != (result = CloseButtonHandle::HitTest(
49 mCloseHandle, state, rect, this)))
50 results.push_back(result);
51
52 if (NULL != (result = MenuButtonHandle::HitTest(
53 mMenuHandle, state, rect, sThis)))
54 results.push_back(result);
55
56 if (NULL != (result = MinimizeButtonHandle::HitTest(
57 mMinimizeHandle, state, rect, this)))
58 results.push_back(result);
59
60 if (NULL != (result = SelectButtonHandle::HitTest(
61 mSelectButtonHandle, state, rect, this)))
62 results.push_back(result);
63
64 if (results.empty()) {
65 if (NULL != (result = TrackSelectHandle::HitAnywhere(
66 mSelectHandle, FindTrack())))
67 results.push_back(result);
68 }
69
70 return results;
71 }
72
73 enum
74 {
75 OnSetNameID = 2000,
76 OnMoveUpID,
77 OnMoveDownID,
78 OnMoveTopID,
79 OnMoveBottomID,
80 };
81
82 class TrackMenuTable
83 : public PopupMenuTable
84 , private PrefsListener
85 {
TrackMenuTable()86 TrackMenuTable()
87 : PopupMenuTable{ "Track" }
88 {}
89 DECLARE_POPUP_MENU(TrackMenuTable);
90
91 public:
92 static TrackMenuTable &Instance();
93
94 private:
95 void OnSetName(wxCommandEvent &);
96 void OnMoveTrack(wxCommandEvent &event);
97
98 void InitUserData(void *pUserData) override;
99
100 CommonTrackControls::InitMenuData *mpData{};
101
UpdatePrefs()102 void UpdatePrefs() override
103 {
104 // Because labels depend on keyboard preferences
105 PopupMenuTable::Clear();
106 }
107 };
108
Instance()109 TrackMenuTable &TrackMenuTable::Instance()
110 {
111 static TrackMenuTable instance;
112 return instance;
113 }
114
InitUserData(void * pUserData)115 void TrackMenuTable::InitUserData(void *pUserData)
116 {
117 mpData = static_cast<CommonTrackControls::InitMenuData*>(pUserData);
118 }
119
120 BEGIN_POPUP_MENU(TrackMenuTable)
__anon2562f2f00202(bool up)121 static const auto enableIfCanMove = [](bool up){ return
122 [up]( PopupMenuHandler &handler, wxMenu &menu, int id ){
123 auto pData = static_cast<TrackMenuTable&>( handler ).mpData;
124 const auto &tracks = TrackList::Get( pData->project );
125 Track *const pTrack = pData->pTrack;
126 menu.Enable( id,
127 up ? tracks.CanMoveUp(pTrack) : tracks.CanMoveDown(pTrack) );
128 };
129 };
130
131 BeginSection( "Basic" );
132 AppendItem( "Name", OnSetNameID, XXO("&Name..."), POPUP_MENU_FN( OnSetName ) );
133 EndSection();
134 BeginSection( "Move" );
135 AppendItem( "Up",
136 // It is not correct to use NormalizedKeyString::Display here --
137 // wxWidgets will apply its equivalent to the key names passed to menu
138 // functions.
139 OnMoveUpID,
140 XXO("Move Track &Up").Join(
141 Verbatim(
142 CommandManager::Get( mpData->project ).
143 // using GET to compose menu item name for wxWidgets
144 GetKeyFromName(wxT("TrackMoveUp")).GET() ),
145 wxT("\t")
146 ),
147 POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(true) );
148 AppendItem( "Down",
149 OnMoveDownID,
150 XXO("Move Track &Down").Join(
151 Verbatim(
152 CommandManager::Get( mpData->project ).
153 // using GET to compose menu item name for wxWidgets
154 GetKeyFromName(wxT("TrackMoveDown")).GET() ),
155 wxT("\t")
156 ),
157 POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(false) );
158 AppendItem( "Top",
159 OnMoveTopID,
160 XXO("Move Track to &Top").Join(
161 Verbatim(
162 CommandManager::Get( mpData->project ).
163 // using GET to compose menu item name for wxWidgets
164 GetKeyFromName(wxT("TrackMoveTop")).GET() ),
165 wxT("\t")
166 ),
167 POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(true) );
168 AppendItem( "Bottom",
169 OnMoveBottomID,
170 XXO("Move Track to &Bottom").Join(
171 Verbatim(
172 CommandManager::Get( mpData->project ).
173 // using GET to compose menu item name for wxWidgets
174 GetKeyFromName(wxT("TrackMoveBottom")).GET() ),
175 wxT("\t")
176 ),
177 POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(false) );
178 EndSection();
179 END_POPUP_MENU()
180
181
182
183
184 // An example of using an AudacityCommand simply to create a dialog.
185 // We can add additional functions later, if we want to make it
186 // available to scripting.
187 // However there is no reason to, as SetTrackStatus is already provided.
188 class SetTrackNameCommand : public AudacityCommand
189 {
190 public:
191 static const ComponentInterfaceSymbol Symbol;
192
193 // ComponentInterface overrides
GetSymbol()194 ComponentInterfaceSymbol GetSymbol() override
195 { return Symbol; }
196 //TranslatableString GetDescription() override {return XO("Sets the track name.");};
197 //bool DefineParams( ShuttleParams & S ) override;
198 void PopulateOrExchange(ShuttleGui & S) override;
199 //bool Apply(const CommandContext & context) override;
200
201 // Provide an override, if we want the help button.
202 // ManualPageID ManualPage() override {return {};}
203 public:
204 wxString mName;
205 };
206
207 const ComponentInterfaceSymbol SetTrackNameCommand::Symbol
208 { XO("Set Track Name") };
209
PopulateOrExchange(ShuttleGui & S)210 void SetTrackNameCommand::PopulateOrExchange(ShuttleGui & S)
211 {
212 S.AddSpace(0, 5);
213
214 S.StartMultiColumn(2, wxALIGN_CENTER);
215 {
216 S.TieTextBox(XXO("Name:"),mName,60);
217 }
218 S.EndMultiColumn();
219 }
220
OnSetName(wxCommandEvent &)221 void TrackMenuTable::OnSetName(wxCommandEvent &)
222 {
223 Track *const pTrack = mpData->pTrack;
224 if (pTrack)
225 {
226 AudacityProject *const proj = &mpData->project;
227 const wxString oldName = pTrack->GetName();
228
229 SetTrackNameCommand Command;
230 Command.mName = oldName;
231 // Bug 1837 : We need an OK/Cancel result if we are to enter a blank string.
232 bool bResult = Command.PromptUser( &GetProjectFrame( *proj ) );
233 if (bResult)
234 {
235 wxString newName = Command.mName;
236 for (auto channel : TrackList::Channels(pTrack))
237 channel->SetName(newName);
238
239 ProjectHistory::Get( *proj )
240 .PushState(
241 XO("Renamed '%s' to '%s'").Format( oldName, newName ),
242 XO("Name Change"));
243
244 mpData->result = RefreshCode::RefreshAll;
245 }
246 }
247 }
248
OnMoveTrack(wxCommandEvent & event)249 void TrackMenuTable::OnMoveTrack(wxCommandEvent &event)
250 {
251 AudacityProject *const project = &mpData->project;
252 TrackUtilities::MoveChoice choice;
253 switch (event.GetId()) {
254 default:
255 wxASSERT(false);
256 case OnMoveUpID:
257 choice = TrackUtilities::OnMoveUpID; break;
258 case OnMoveDownID:
259 choice = TrackUtilities::OnMoveDownID; break;
260 case OnMoveTopID:
261 choice = TrackUtilities::OnMoveTopID; break;
262 case OnMoveBottomID:
263 choice = TrackUtilities::OnMoveBottomID; break;
264 }
265
266 TrackUtilities::DoMoveTrack(*project, mpData->pTrack, choice);
267
268 // MoveTrack already refreshed TrackPanel, which means repaint will happen.
269 // This is a harmless redundancy:
270 mpData->result = RefreshCode::RefreshAll;
271 }
272
DoContextMenu(const wxRect & rect,wxWindow * pParent,const wxPoint *,AudacityProject * pProject)273 unsigned CommonTrackControls::DoContextMenu(
274 const wxRect &rect, wxWindow *pParent, const wxPoint *,
275 AudacityProject *pProject)
276 {
277 using namespace RefreshCode;
278 wxRect buttonRect;
279 TrackInfo::GetTitleBarRect(rect, buttonRect);
280
281 auto track = FindTrack();
282 if (!track)
283 return RefreshNone;
284
285 InitMenuData data{ *pProject, track.get(), pParent, RefreshNone };
286
287 const auto pTable = &TrackMenuTable::Instance();
288 auto pMenu = PopupMenuTable::BuildMenu(pTable, &data);
289
290 PopupMenuTable *const pExtension = GetMenuExtension(track.get());
291 if (pExtension)
292 PopupMenuTable::ExtendMenu( *pMenu, *pExtension );
293
294 pMenu->Popup( *pParent,
295 { buttonRect.x + 1, buttonRect.y + buttonRect.height + 1 } );
296
297 return data.result;
298 }
299
300 // Some old cut-and-paste legacy from TrackPanel.cpp here:
301 #if 0
302 void TrackInfo::DrawBordersWithin
303 ( wxDC* dc, const wxRect & rect, const Track &track ) const
304 {
305 AColor::Dark(dc, false); // same color as border of toolbars (ToolBar::OnPaint())
306
307 // below close box and title bar
308 wxRect buttonRect;
309 GetTitleBarRect( rect, buttonRect );
310 AColor::Line
311 (*dc, rect.x, buttonRect.y + buttonRect.height,
312 rect.width - 1, buttonRect.y + buttonRect.height);
313
314 // between close box and title bar
315 AColor::Line
316 (*dc, buttonRect.x, buttonRect.y,
317 buttonRect.x, buttonRect.y + buttonRect.height - 1);
318
319 GetMuteSoloRect( rect, buttonRect, false, true, &track );
320
321 bool bHasMuteSolo = dynamic_cast<const PlayableTrack*>( &track ) != NULL;
322 if( bHasMuteSolo && !TrackInfo::HideTopItem( rect, buttonRect ) )
323 {
324 // above mute/solo
325 AColor::Line
326 (*dc, rect.x, buttonRect.y,
327 rect.width - 1, buttonRect.y);
328
329 // between mute/solo
330 // Draw this little line; if there is no solo, wide mute button will
331 // overpaint it later:
332 AColor::Line
333 (*dc, buttonRect.x + buttonRect.width, buttonRect.y,
334 buttonRect.x + buttonRect.width, buttonRect.y + buttonRect.height - 1);
335
336 // below mute/solo
337 AColor::Line
338 (*dc, rect.x, buttonRect.y + buttonRect.height,
339 rect.width - 1, buttonRect.y + buttonRect.height);
340 }
341
342 // left of and above minimize button
343 wxRect minimizeRect;
344 this->GetMinimizeRect(rect, minimizeRect);
345 AColor::Line
346 (*dc, minimizeRect.x - 1, minimizeRect.y,
347 minimizeRect.x - 1, minimizeRect.y + minimizeRect.height - 1);
348 AColor::Line
349 (*dc, minimizeRect.x, minimizeRect.y - 1,
350 minimizeRect.x + minimizeRect.width - 1, minimizeRect.y - 1);
351 }
352 #endif
353
Draw(TrackPanelDrawingContext & context,const wxRect & rect_,unsigned iPass)354 void CommonTrackControls::Draw(
355 TrackPanelDrawingContext &context,
356 const wxRect &rect_, unsigned iPass )
357 {
358 if ( iPass == TrackArtist::PassMargins ) {
359 // fill in label
360 auto dc = &context.dc;
361 const auto pTrack = FindTrack();
362 AColor::MediumTrackInfo( dc, pTrack && pTrack->GetSelected() );
363 dc->DrawRectangle( rect_ );
364 }
365
366 if ( iPass == TrackArtist::PassControls ) {
367 // Given rectangle excludes left and right margins, and encompasses a
368 // channel group of tracks, plus the resizer area below
369 auto pTrack = FindTrack();
370 // First counteract DrawingArea() correction
371 wxRect rect{ rect_.x, rect_.y, rect_.width - 1, rect_.height };
372
373 // Vaughan, 2010-08-24: No longer doing this.
374 // Draw sync-lock tiles in ruler area.
375 //if (t->IsSyncLockSelected()) {
376 // wxRect tileFill = rect;
377 // tileFill.x = mViewInfo->GetVRulerOffset();
378 // tileFill.width = mViewInfo->GetVRulerWidth();
379 // TrackArt::DrawSyncLockTiles(dc, tileFill);
380 //}
381
382 if (pTrack)
383 // Draw things within the track control panel
384 TrackInfo::DrawItems( context, rect, *pTrack );
385
386 //mTrackInfo.DrawBordersWithin( dc, rect, *t );
387 }
388
389 // Some old cut-and-paste legacy from TrackPanel.cpp here:
390 #undef USE_BEVELS
391 #ifdef USE_BEVELS
392 // This branch is not now used
393 // PRL: todo: banish magic numbers.
394 // PRL: vrul was the x coordinate of left edge of the vertical ruler.
395 // PRL: bHasMuteSolo was true iff the track was WaveTrack.
396 if( bHasMuteSolo )
397 {
398 int ylast = rect.height-20;
399 int ybutton = wxMin(32,ylast-17);
400 int ybuttonEnd = 67;
401
402 fill=wxRect( rect.x+1, rect.y+17, vrul-6, ybutton);
403 AColor::BevelTrackInfo( *dc, true, fill );
404
405 if( ybuttonEnd < ylast ){
406 fill=wxRect( rect.x+1, rect.y+ybuttonEnd, fill.width, ylast - ybuttonEnd);
407 AColor::BevelTrackInfo( *dc, true, fill );
408 }
409 }
410 else
411 {
412 fill=wxRect( rect.x+1, rect.y+17, vrul-6, rect.height-37);
413 AColor::BevelTrackInfo( *dc, true, fill );
414 }
415 #endif
416
417 }
418
DrawingArea(TrackPanelDrawingContext &,const wxRect & rect,const wxRect &,unsigned iPass)419 wxRect CommonTrackControls::DrawingArea(
420 TrackPanelDrawingContext &,
421 const wxRect &rect, const wxRect &, unsigned iPass )
422 {
423 if ( iPass == TrackArtist::PassControls )
424 // Some bevels spill out right
425 return { rect.x, rect.y, rect.width + 1, rect.height };
426 else
427 return rect;
428 }
429