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