Audacity 3.2.0
WaveTrackAffordanceControls.cpp
Go to the documentation of this file.
1/*!********************************************************************
2*
3 Audacity: A Digital Audio Editor
4
5 WaveTrackAffordanceControls.cpp
6
7 Vitaly Sverchinsky
8
9 **********************************************************************/
10
12
13#include <wx/dc.h>
14#include <wx/frame.h>
15
16#include "AllThemeResources.h"
17#include "../../../../commands/CommandContext.h"
18#include "../../../../commands/CommandFlag.h"
19#include "../../../../commands/CommandFunctors.h"
20#include "../../../../commands/CommandManager.h"
21#include "../../../../TrackPanelMouseEvent.h"
22#include "../../../../TrackArt.h"
23#include "../../../../TrackArtist.h"
24#include "../../../../TrackPanelDrawingContext.h"
25#include "../../../../TrackPanelResizeHandle.h"
26#include "ViewInfo.h"
27#include "../../../../WaveTrack.h"
28#include "../../../../WaveClip.h"
29#include "UndoManager.h"
30#include "../../../../ShuttleGui.h"
31#include "../../../../ProjectWindows.h"
32#include "../../../../commands/AudacityCommand.h"
33
34#include "../../../ui/TextEditHelper.h"
35#include "../../../ui/SelectHandle.h"
36#include "WaveTrackView.h"//need only ClipParameters
38
39#include "ProjectHistory.h"
40#include "../../../../ProjectSettings.h"
41#include "../../../../SelectionState.h"
42#include "../../../../RefreshCode.h"
43#include "Theme.h"
44#include "../../../../../images/Cursors.h"
45#include "../../../../HitTestResult.h"
46#include "../../../../TrackPanel.h"
47#include "../../../../TrackPanelAx.h"
48
49#include "../WaveTrackUtils.h"
50
51#include "WaveClipTrimHandle.h"
52
53
54
56{
57public:
59
61 {
62 return Symbol;
63 }
65 {
66 S.AddSpace(0, 5);
67
68 S.StartMultiColumn(2, wxALIGN_CENTER);
69 {
70 S.TieTextBox(XXO("Name:"), mName, 60);
71 }
72 S.EndMultiColumn();
73 }
74public:
75 wxString mName;
76};
77
79{ XO("Set Wave Clip Name") };
80
81//Handle which is used to send mouse events to TextEditHelper
83{
84 std::shared_ptr<TextEditHelper> mHelper;
85public:
86
87 WaveClipTitleEditHandle(const std::shared_ptr<TextEditHelper>& helper)
88 : mHelper(helper)
89 { }
90
92 {
93 }
94
95 Result Click(const TrackPanelMouseEvent& event, AudacityProject* project) override
96 {
97 if (mHelper->OnClick(event.event, project))
100 }
101
102 Result Drag(const TrackPanelMouseEvent& event, AudacityProject* project) override
103 {
104 if (mHelper->OnDrag(event.event, project))
107 }
108
110 {
111 static auto ibeamCursor =
112 ::MakeCursor(wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
113 return {
114 XO("Click and drag to select text"),
115 ibeamCursor.get()
116 };
117 }
118
119 Result Release(const TrackPanelMouseEvent& event, AudacityProject* project, wxWindow*) override
120 {
121 if (mHelper->OnRelease(event.event, project))
124 }
125
126 Result Cancel(AudacityProject* project) override
127 {
128 if (mHelper)
129 {
130 mHelper->Cancel(project);
131 mHelper.reset();
132 }
134 }
135};
136
138 : CommonTrackCell(pTrack), mClipNameFont(wxFont(wxFontInfo()))
139{
140 if (auto trackList = pTrack->GetOwner())
141 {
142 mSubscription = trackList->Subscribe(
144 }
145}
146
147std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
148{
149 std::vector<UIHandlePtr> results;
150
151 auto px = state.state.m_x;
152 auto py = state.state.m_y;
153
154 const auto rect = state.rect;
155
156 auto track = std::static_pointer_cast<WaveTrack>(FindTrack());
157
158 {
161 track,
162 pProject,
163 state);
164
165 if (handle)
166 results.push_back(handle);
167 }
168
169 auto trackList = track->GetOwner();
170 if ((std::abs(rect.GetTop() - py) <= WaveTrackView::kChannelSeparatorThickness / 2)
171 && trackList
172 && !track->IsLeader())
173 {
174 //given that track is not a leader there always should be
175 //another track before this one
176 auto prev = std::prev(trackList->Find(track.get()));
177 results.push_back(
180 std::make_shared<TrackPanelResizeHandle>((*prev)->shared_from_this(), py)
181 )
182 );
183 }
184
185 if (mTextEditHelper && mTextEditHelper->GetBBox().Contains(px, py))
186 {
187 results.push_back(
190 std::make_shared<WaveClipTitleEditHandle>(mTextEditHelper)
191 )
192 );
193 }
194
195 auto editClipLock = mEditedClip.lock();
196 const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
197 auto& zoomInfo = ViewInfo::Get(*pProject);
198 for (const auto& clip : waveTrack->GetClips())
199 {
200 if (clip == editClipLock)
201 continue;
202
203 if (WaveTrackView::HitTest(*clip, zoomInfo, state.rect, {px, py}))
204 {
205 results.push_back(
208 std::make_shared<WaveTrackAffordanceHandle>(track, clip)
209 )
210 );
211 mFocusClip = clip;
212 break;
213 }
214 }
215
216 const auto& settings = ProjectSettings::Get(*pProject);
217 const auto currentTool = settings.GetTool();
218 if (currentTool == ToolCodes::multiTool || currentTool == ToolCodes::selectTool)
219 {
220 results.push_back(
222 mSelectHandle, state, pProject,
223 TrackView::Get(*track).shared_from_this()
224 )
225 );
226 }
227
228 return results;
229}
230
231void WaveTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
232{
233 if (iPass == TrackArtist::PassBackground) {
234 auto track = FindTrack();
235 const auto artist = TrackArtist::Get(context);
236
237 TrackArt::DrawBackgroundWithSelection(context, rect, track.get(), artist->blankSelectedBrush, artist->blankBrush);
238
239 const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
240 const auto& zoomInfo = *artist->pZoomInfo;
241
242 {
243 wxDCClipper dcClipper(context.dc, rect);
244
245 context.dc.SetTextBackground(wxTransparentColor);
246 context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
247 context.dc.SetFont(mClipNameFont);
248
249 auto px = context.lastState.m_x;
250 auto py = context.lastState.m_y;
251
252 for (const auto& clip : waveTrack->GetClips())
253 {
254 auto affordanceRect
255 = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
256
257 if(!WaveTrackView::ClipDetailsVisible(*clip, zoomInfo, rect))
258 {
259 TrackArt::DrawClipFolded(context.dc, affordanceRect);
260 continue;
261 }
262
263 auto selected = GetSelectedClip().lock() == clip;
264 auto highlight = selected || affordanceRect.Contains(px, py);
265 if (mTextEditHelper && mEditedClip.lock() == clip)
266 {
267 auto titleRect = TrackArt::DrawClipAffordance(context.dc, affordanceRect, wxEmptyString, highlight, selected);
268 mTextEditHelper->Draw(context.dc, titleRect);
269 }
270 else
271 {
272 auto titleRect = TrackArt::DrawClipAffordance(context.dc, affordanceRect, clip->GetName(), highlight, selected);
273 mClipNameVisible = !titleRect.IsEmpty();
274 }
275 }
276 }
277
278 }
279}
280
282{
283 if (auto lock = mFocusClip.lock())
284 {
285 auto clip = lock.get();
286
287 bool useDialog{ false };
288 gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false);
289
290 if (useDialog)
291 {
293 auto oldName = clip->GetName();
294 Command.mName = oldName;
295 auto result = Command.PromptUser(&GetProjectFrame(*project));
296 if (result && Command.mName != oldName)
297 {
298 clip->SetName(Command.mName);
299 ProjectHistory::Get(*project).PushState(XO("Modified Clip Name"),
300 XO("Clip Name Edit"));
301 }
302 }
303 else
304 {
305 if (mTextEditHelper)
306 mTextEditHelper->Finish(project);
307
309 //Clip name isn't visible, there is no point in showing editor then
310 return false;
311
312 mEditedClip = lock;
313 mTextEditHelper = MakeTextEditHelper(clip->GetName());
314 }
315 return true;
316 }
317 return false;
318}
319
320std::weak_ptr<WaveClip> WaveTrackAffordanceControls::GetSelectedClip() const
321{
322 if (auto handle = mAffordanceHandle.lock())
323 {
324 return handle->Clicked() ? mFocusClip : std::weak_ptr<WaveClip>();
325 }
326 return {};
327}
328
329namespace {
330
332{
333 auto &view = TrackView::Get( track );
334 auto pAffordance = view.GetAffordanceControls();
335 return std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
336 pAffordance );
337}
338
339std::pair<WaveTrack *, WaveClip *>
341{
342 // Note that TrackFocus may change its state as a side effect, defining
343 // a track focus if there was none
344 if (auto pWaveTrack =
345 dynamic_cast<WaveTrack *>(TrackFocus::Get(project).Get())) {
346 for (auto pChannel : TrackList::Channels(pWaveTrack)) {
347 if (FindAffordance(*pChannel)) {
348 auto &viewInfo = ViewInfo::Get(project);
349 auto &clips = pChannel->GetClips();
350 auto begin = clips.begin(), end = clips.end(),
351 iter = WaveTrackUtils::SelectedClip(viewInfo, begin, end);
352 if (iter != end)
353 return { pChannel, iter->get() };
354 }
355 }
356 }
357 return { nullptr, nullptr };
358}
359
360// condition for enabling the command
362{
364 [](const AudacityProject &project){
365 return nullptr !=
366 // const_cast isn't pretty but not harmful in this case
367 SelectedClipOfFocusedTrack(const_cast<AudacityProject&>(project))
368 .second;
369 }
370 };
371 return flag;
372}
373
374}
375
376unsigned WaveTrackAffordanceControls::CaptureKey(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
377{
378 if (!mTextEditHelper
379 || !mTextEditHelper->CaptureKey(event.GetKeyCode(), event.GetModifiers()))
380 // Handle the event if it can be processed by the text editor (if any)
381 event.Skip();
383}
384
385
386unsigned WaveTrackAffordanceControls::KeyDown(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow*, AudacityProject* project)
387{
388 auto keyCode = event.GetKeyCode();
389
390 if (mTextEditHelper)
391 {
392 if (!mTextEditHelper->OnKeyDown(keyCode, event.GetModifiers(), project)
394 event.Skip();
395
397 }
399}
400
401unsigned WaveTrackAffordanceControls::Char(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
402{
403 if (mTextEditHelper && mTextEditHelper->OnChar(event.GetUnicodeKey(), project))
406}
407
409{
410 return ExitTextEditing();
411}
412
414{
415 if (auto lock = mEditedClip.lock())
416 {
417 if (text != lock->GetName()) {
418 lock->SetName(text);
419
420 ProjectHistory::Get(*project).PushState(XO("Modified Clip Name"),
421 XO("Clip Name Edit"));
422 }
423 }
425}
426
428{
430}
431
433{
434 //Nothing to do
435}
436
438{
439}
440
442{
443 mTextEditHelper.reset();
444 mEditedClip.reset();
445}
446
448{
451}
452
454{
455 using namespace RefreshCode;
456 if (mTextEditHelper)
457 {
458 if (auto trackList = FindTrack()->GetOwner())
459 {
460 mTextEditHelper->Finish(trackList->GetOwner());
461 }
463 return RefreshCell;
464 }
465 return RefreshNone;
466}
467
468
470 AudacityProject &project, std::function<bool(WaveClip&)> test )
471{
472 //Attempts to invoke name editing if there is a selected clip
473 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(FindTrack());
474 if (!waveTrack)
475 return false;
476 auto clips = waveTrack->GetClips();
477
478 auto it = std::find_if(clips.begin(), clips.end(),
479 [&](auto pClip){ return pClip && test && test(*pClip); });
480 if (it != clips.end())
481 {
482 mFocusClip = *it;
483 return StartEditClipName(&project);
484 }
485 return false;
486}
487
489{
490 if (mTextEditHelper)
491 {
492 mTextEditHelper->CopySelectedText(project);
493 return true;
494 }
495 return false;
496}
497
499{
500 if (mTextEditHelper)
501 {
502 mTextEditHelper->CutSelectedText(project);
503 return true;
504 }
505 return false;
506}
507
509{
510 if (mTextEditHelper)
511 {
512 mTextEditHelper->PasteSelectedText(project);
513 return true;
514 }
515 return false;
516}
517
519{
520 if (mTextEditHelper)
521 {
522 mTextEditHelper->SelectAll();
523 return true;
524 }
525 return false;
526}
527
529{
530 auto& viewInfo = ViewInfo::Get(*project);
531 if (mTextEditHelper)
532 {
533 if (auto lock = mEditedClip.lock())
534 {
535 auto affordanceRect = ClipParameters::GetClipRect(*lock.get(), viewInfo, event.rect);
536 if (!affordanceRect.Contains(event.event.GetPosition()))
537 return ExitTextEditing();
538 }
539 }
540 else if (auto lock = mFocusClip.lock())
541 {
542 if (event.event.LeftDClick())
543 {
544 auto affordanceRect = ClipParameters::GetClipRect(*lock.get(), viewInfo, event.rect);
545 if (affordanceRect.Contains(event.event.GetPosition()) &&
546 StartEditClipName(project))
547 {
548 event.event.Skip(false);
550 }
551 }
552 }
554}
555
556std::shared_ptr<TextEditHelper> WaveTrackAffordanceControls::MakeTextEditHelper(const wxString& text)
557{
558 auto helper = std::make_shared<TextEditHelper>(shared_from_this(), text, mClipNameFont);
559 helper->SetTextColor(theTheme.Colour(clrClipNameText));
560 helper->SetTextSelectionColor(theTheme.Colour(clrClipNameTextSelection));
561 return helper;
562}
563
564// Register a menu item
565
566namespace {
567
568// Menu handler functions
569
571
572void OnEditClipName(const CommandContext &context)
573{
574 auto &project = context.project;
575 const auto [pTrack, pClip] = SelectedClipOfFocusedTrack(project);
576 if (pTrack && pClip) {
577 if (auto pAffordance = FindAffordance(*pTrack)) {
578 pAffordance->StartEditNameOfMatchingClip(project,
579 [pClip = pClip](auto &clip){ return &clip == pClip; });
580 // Refresh so the cursor appears
581 TrackPanel::Get(project).RefreshTrack(pTrack);
582 }
583 }
584}
585
586};
587
588#define FN(X) (& Handler :: X)
589
591 // Handler is not stateful. Doesn't need a factory registered with
592 // AudacityProject.
593 static Handler instance;
594 return instance;
595};
596
597using namespace MenuTable;
598
599// Register menu items
600
601AttachedItem sAttachment{ wxT("Edit/Other"),
603 Command( L"RenameClip", XXO("Rename Clip..."),
604 &Handler::OnEditClipName, SomeClipIsSelectedFlag(),
605 wxT("Ctrl+F2") ) )
606};
607
608}
609
610#undef FN
wxEvtHandler CommandHandlerObject
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
FileConfig * gPrefs
Definition: Prefs.cpp:71
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:87
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:185
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
static std::once_flag flag
Base class for command in Audacity.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
std::shared_ptr< Track > FindTrack()
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static UIHandlePtr HitTest(std::weak_ptr< SelectHandle > &holder, const TrackPanelMouseState &state, const AudacityProject *pProject, const std::shared_ptr< TrackView > &pTrackView)
ComponentInterfaceSymbol GetSymbol() const override
static const ComponentInterfaceSymbol Symbol
void PopulateOrExchange(ShuttleGui &S) override
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
static bool IsGoodEditKeyCode(int keyCode)
wxColour & Colour(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Track * Get()
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1539
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:230
void RefreshTrack(Track *trk, bool refreshbacking=true)
Definition: TrackPanel.cpp:748
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
Short-lived drawing and event-handling object associated with a TrackPanelCell.
Definition: UIHandle.h:35
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
Result Release(const TrackPanelMouseEvent &event, AudacityProject *project, wxWindow *) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *project) override
std::shared_ptr< TextEditHelper > mHelper
Result Cancel(AudacityProject *project) override
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *project) override
WaveClipTitleEditHandle(const std::shared_ptr< TextEditHelper > &helper)
static UIHandlePtr HitAnywhere(std::weak_ptr< WaveClipTrimHandle > &holder, const std::shared_ptr< WaveTrack > &waveTrack, const AudacityProject *pProject, const TrackPanelMouseState &state)
bool OnTextCopy(AudacityProject &project)
void OnTextEditCancelled(AudacityProject *project) override
void OnTextContextMenu(AudacityProject *project, const wxPoint &position) override
std::weak_ptr< TrackPanelResizeHandle > mResizeHandle
unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
std::weak_ptr< WaveClipTrimHandle > mClipTrimHandle
void OnTextEditFinished(AudacityProject *project, const wxString &text) override
unsigned LoseFocus(AudacityProject *project) override
WaveTrackAffordanceControls(const std::shared_ptr< Track > &pTrack)
bool OnTextSelect(AudacityProject &project)
std::weak_ptr< WaveTrackAffordanceHandle > mAffordanceHandle
std::shared_ptr< TextEditHelper > mTextEditHelper
std::shared_ptr< TextEditHelper > MakeTextEditHelper(const wxString &text)
bool StartEditClipName(AudacityProject *project)
bool StartEditNameOfMatchingClip(AudacityProject &project, std::function< bool(WaveClip &)> test)
std::weak_ptr< SelectHandle > mSelectHandle
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
void OnTrackChanged(const TrackListEvent &evt)
std::weak_ptr< WaveClip > GetSelectedClip() const
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
unsigned OnAffordanceClick(const TrackPanelMouseEvent &event, AudacityProject *project)
std::weak_ptr< WaveClip > mFocusClip
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
bool OnTextCut(AudacityProject &project)
void OnTextModified(AudacityProject *project, const wxString &text) override
bool OnTextPaste(AudacityProject &project)
std::weak_ptr< WaveClip > mEditedClip
std::weak_ptr< WaveClipTitleEditHandle > mTitleEditHandle
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
static bool HitTest(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &rect, const wxPoint &pos)
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
static constexpr int kChannelSeparatorThickness
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:26
std::unique_ptr< CommandItem > Command(const CommandID &name, const TranslatableString &label_in, void(Handler::*pmf)(const CommandContext &), CommandFlag flags, const CommandManager::Options &options={}, CommandHandlerFinder finder=FinderScope::DefaultFinder())
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
Definition: TrackArt.cpp:290
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:419
AUDACITY_DLL_API wxRect DrawClipAffordance(wxDC &dc, const wxRect &affordanceRect, const wxString &title, bool highlight=false, bool selected=false)
Definition: TrackArt.cpp:191
Iter SelectedClip(const ViewInfo &viewInfo, Iter begin, Iter end)
std::pair< WaveTrack *, WaveClip * > SelectedClipOfFocusedTrack(AudacityProject &project)
CommandHandlerObject & findCommandHandler(AudacityProject &)
static wxRect GetClipRect(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
Notification of changes in individual tracks of TrackList, or of TrackList's composition.
Definition: Track.h:1288
const Type mType
Definition: Track.h:1323
@ SELECTION_CHANGE
Posted when the set of selected tracks changes.
Definition: Track.h:1291