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 mTrackListEventSubscription = trackList->Subscribe(
144 if(auto project = trackList->GetOwner())
145 {
146 auto& viewInfo = ViewInfo::Get(*project);
148 viewInfo.selectedRegion.Subscribe(
149 *this,
151 }
152 }
153}
154
155std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
156{
157 std::vector<UIHandlePtr> results;
158
159 auto px = state.state.m_x;
160 auto py = state.state.m_y;
161
162 const auto rect = state.rect;
163
164 auto track = std::static_pointer_cast<WaveTrack>(FindTrack());
165
166 {
169 track,
170 pProject,
171 state);
172
173 if (handle)
174 results.push_back(handle);
175 }
176
177 auto trackList = track->GetOwner();
178 if ((std::abs(rect.GetTop() - py) <= WaveTrackView::kChannelSeparatorThickness / 2)
179 && trackList
180 && !track->IsLeader())
181 {
182 //given that track is not a leader there always should be
183 //another track before this one
184 auto prev = std::prev(trackList->Find(track.get()));
185 results.push_back(
188 std::make_shared<TrackPanelResizeHandle>((*prev)->shared_from_this(), py)
189 )
190 );
191 }
192
193 if (mTextEditHelper && mTextEditHelper->GetBBox().Contains(px, py))
194 {
195 results.push_back(
198 std::make_shared<WaveClipTitleEditHandle>(mTextEditHelper)
199 )
200 );
201 }
202
203 auto editClipLock = mEditedClip.lock();
204 const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
205 auto& zoomInfo = ViewInfo::Get(*pProject);
206 for (const auto& clip : waveTrack->GetClips())
207 {
208 if (clip == editClipLock)
209 continue;
210
211 if (WaveTrackView::HitTest(*clip, zoomInfo, state.rect, {px, py}))
212 {
213 results.push_back(
216 std::make_shared<WaveTrackAffordanceHandle>(track, clip)
217 )
218 );
219 mFocusClip = clip;
220 break;
221 }
222 }
223
224 const auto& settings = ProjectSettings::Get(*pProject);
225 const auto currentTool = settings.GetTool();
226 if (currentTool == ToolCodes::multiTool || currentTool == ToolCodes::selectTool)
227 {
228 results.push_back(
230 mSelectHandle, state, pProject,
231 TrackView::Get(*track).shared_from_this()
232 )
233 );
234 }
235
236 return results;
237}
238
239void WaveTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
240{
241 if (iPass == TrackArtist::PassBackground) {
242 auto track = FindTrack();
243 const auto artist = TrackArtist::Get(context);
244
245 TrackArt::DrawBackgroundWithSelection(context, rect, track.get(), artist->blankSelectedBrush, artist->blankBrush);
246
247 mLastVisibleClips.clear();
248
249 const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
250 const auto& zoomInfo = *artist->pZoomInfo;
251 {
252 wxDCClipper dcClipper(context.dc, rect);
253
254 context.dc.SetTextBackground(wxTransparentColor);
255 context.dc.SetTextForeground(theTheme.Colour(clrClipNameText));
256 context.dc.SetFont(mClipNameFont);
257
258 auto px = context.lastState.m_x;
259 auto py = context.lastState.m_y;
260
261 for (const auto& clip : waveTrack->GetClips())
262 {
263 auto affordanceRect
264 = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
265
266 if(!WaveTrackView::ClipDetailsVisible(*clip, zoomInfo, rect))
267 {
268 TrackArt::DrawClipFolded(context.dc, affordanceRect);
269 continue;
270 }
271
272 const auto selected = GetSelectedClip().lock() == clip;
273 const auto highlight = selected || affordanceRect.Contains(px, py);
274 const auto titleRect = TrackArt::DrawClipAffordance(context.dc, affordanceRect, highlight, selected);
275 if (mTextEditHelper && mEditedClip.lock() == clip)
276 {
277 if(!mTextEditHelper->Draw(context.dc, titleRect))
278 {
279 mTextEditHelper->Cancel(nullptr);
280 TrackArt::DrawClipTitle(context.dc, titleRect, clip->GetName());
281 }
282 }
283 else if(TrackArt::DrawClipTitle(context.dc, titleRect, clip->GetName()))
284 mLastVisibleClips.push_back(clip.get());
285 }
286 }
287
288 }
289}
290
292{
293 return std::find(mLastVisibleClips.begin(),
294 mLastVisibleClips.end(),
295 &clip) != mLastVisibleClips.end();
296}
297
298bool WaveTrackAffordanceControls::StartEditClipName(AudacityProject& project, const std::shared_ptr<WaveClip>& clip)
299{
300 bool useDialog{ false };
301 gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false);
302
303 if (useDialog)
304 {
306 auto oldName = clip->GetName();
307 Command.mName = oldName;
308 auto result = Command.PromptUser(&GetProjectFrame(project));
309 if (result && Command.mName != oldName)
310 {
311 clip->SetName(Command.mName);
312 ProjectHistory::Get(project).PushState(XO("Modified Clip Name"),
313 XO("Clip Name Edit"));
314 }
315 }
316 else if(clip != mEditedClip.lock())
317 {
318 if(!IsClipNameVisible(*clip))
319 return false;
320
321 if (mTextEditHelper)
322 mTextEditHelper->Finish(&project);
323
324 mEditedClip = clip;
325 mTextEditHelper = MakeTextEditHelper(clip->GetName());
326 }
327
328 return true;
329}
330
331std::weak_ptr<WaveClip> WaveTrackAffordanceControls::GetSelectedClip() const
332{
333 if (auto handle = mAffordanceHandle.lock())
334 {
335 return handle->Clicked() ? mFocusClip : std::weak_ptr<WaveClip>();
336 }
337 return {};
338}
339
340namespace {
341
343{
344 auto &view = TrackView::Get( track );
345 auto pAffordance = view.GetAffordanceControls();
346 return std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
347 pAffordance );
348}
349
350std::pair<WaveTrack *, std::shared_ptr<WaveClip>>
352{
353 // Note that TrackFocus may change its state as a side effect, defining
354 // a track focus if there was none
355 if (auto pWaveTrack =
356 dynamic_cast<WaveTrack *>(TrackFocus::Get(project).Get())) {
357 for (auto pChannel : TrackList::Channels(pWaveTrack)) {
358 if (FindAffordance(*pChannel)) {
359 auto &viewInfo = ViewInfo::Get(project);
360 auto &clips = pChannel->GetClips();
361 auto begin = clips.begin(), end = clips.end(),
362 iter = WaveTrackUtils::SelectedClip(viewInfo, begin, end);
363 if (iter != end)
364 return { pChannel, *iter };
365 }
366 }
367 }
368 return { nullptr, nullptr };
369}
370
371// condition for enabling the command
373{
375 [](const AudacityProject &project){
376 return nullptr !=
377 // const_cast isn't pretty but not harmful in this case
378 SelectedClipOfFocusedTrack(const_cast<AudacityProject&>(project))
379 .second;
380 }
381 };
382 return flag;
383}
384
385}
386
387unsigned WaveTrackAffordanceControls::CaptureKey(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
388{
389 if (!mTextEditHelper
390 || !mTextEditHelper->CaptureKey(event.GetKeyCode(), event.GetModifiers()))
391 // Handle the event if it can be processed by the text editor (if any)
392 event.Skip();
394}
395
396
397unsigned WaveTrackAffordanceControls::KeyDown(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow*, AudacityProject* project)
398{
399 auto keyCode = event.GetKeyCode();
400
401 if (mTextEditHelper)
402 {
403 if (!mTextEditHelper->OnKeyDown(keyCode, event.GetModifiers(), project)
405 event.Skip();
406
408 }
410}
411
412unsigned WaveTrackAffordanceControls::Char(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
413{
414 if (mTextEditHelper && mTextEditHelper->OnChar(event.GetUnicodeKey(), project))
417}
418
420{
421 return ExitTextEditing();
422}
423
425{
426 if (auto lock = mEditedClip.lock())
427 {
428 if (text != lock->GetName()) {
429 lock->SetName(text);
430
431 ProjectHistory::Get(*project).PushState(XO("Modified Clip Name"),
432 XO("Clip Name Edit"));
433 }
434 }
436}
437
439{
441}
442
444{
445 //Nothing to do
446}
447
449{
450}
451
453{
454 mTextEditHelper.reset();
455 mEditedClip.reset();
456}
457
459{
462}
463
465{
467}
468
470{
471 using namespace RefreshCode;
472 if (mTextEditHelper)
473 {
474 if (auto trackList = FindTrack()->GetOwner())
475 {
476 mTextEditHelper->Finish(trackList->GetOwner());
477 }
479 return RefreshCell;
480 }
481 return RefreshNone;
482}
483
485{
486 if (mTextEditHelper)
487 {
488 mTextEditHelper->CopySelectedText(project);
489 return true;
490 }
491 return false;
492}
493
495{
496 if (mTextEditHelper)
497 {
498 mTextEditHelper->CutSelectedText(project);
499 return true;
500 }
501 return false;
502}
503
505{
506 if (mTextEditHelper)
507 {
508 mTextEditHelper->PasteSelectedText(project);
509 return true;
510 }
511 return false;
512}
513
515{
516 if (mTextEditHelper)
517 {
518 mTextEditHelper->SelectAll();
519 return true;
520 }
521 return false;
522}
523
525{
526 auto& viewInfo = ViewInfo::Get(*project);
527 if (mTextEditHelper)
528 {
529 if (auto lock = mEditedClip.lock())
530 {
531 auto affordanceRect = ClipParameters::GetClipRect(*lock.get(), viewInfo, event.rect);
532 if (!affordanceRect.Contains(event.event.GetPosition()))
533 return ExitTextEditing();
534 }
535 }
536 else if (auto lock = mFocusClip.lock())
537 {
538 if (event.event.LeftDClick())
539 {
540 auto affordanceRect = ClipParameters::GetClipRect(*lock.get(), viewInfo, event.rect);
541 if (affordanceRect.Contains(event.event.GetPosition()) &&
542 StartEditClipName(*project, lock))
543 {
544 event.event.Skip(false);
546 }
547 }
548 }
550}
551
553{
554 const auto [track, clip] = SelectedClipOfFocusedTrack(project);
555 if(track == nullptr || track != FindTrack().get() || clip == nullptr)
556 return;
557 StartEditClipName(project, clip);
558}
559
560std::shared_ptr<TextEditHelper> WaveTrackAffordanceControls::MakeTextEditHelper(const wxString& text)
561{
562 auto helper = std::make_shared<TextEditHelper>(shared_from_this(), text, mClipNameFont);
563 helper->SetTextColor(theTheme.Colour(clrClipNameText));
564 helper->SetTextSelectionColor(theTheme.Colour(clrClipNameTextSelection));
565 return helper;
566}
567
568// Register a menu item
569
570namespace {
571
572// Menu handler functions
573
574void OnEditClipName(const CommandContext &context)
575{
576 auto &project = context.project;
577
578 if(auto pWaveTrack = dynamic_cast<WaveTrack *>(TrackFocus::Get(project).Get()))
579 {
580 if(auto pAffordance = FindAffordance(*pWaveTrack))
581 {
582 pAffordance->StartEditSelectedClipName(project);
583 // Refresh so the cursor appears
584 TrackPanel::Get(project).RefreshTrack(pWaveTrack);
585 }
586 }
587}
588
589using namespace MenuTable;
590
591// Register menu items
592
594 Command( L"RenameClip", XXO("Rename Clip..."),
596};
597
598}
wxT("CloseDown"))
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
FileConfig * gPrefs
Definition: Prefs.cpp:70
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:186
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:90
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:625
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:1544
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:231
void RefreshTrack(Track *trk, bool refreshbacking=true)
Definition: TrackPanel.cpp:743
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 OnTrackListEvent(const TrackListEvent &evt)
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
bool StartEditClipName(AudacityProject &project, const std::shared_ptr< WaveClip > &clip)
Starts in-place clip name editing or shows a Clip Name Edit dialog, depending on prefs.
void OnTextEditFinished(AudacityProject *project, const wxString &text) override
bool IsClipNameVisible(const WaveClip &clip) const noexcept
unsigned LoseFocus(AudacityProject *project) override
WaveTrackAffordanceControls(const std::shared_ptr< Track > &pTrack)
void OnSelectionChange(NotifyingSelectedRegionMessage)
bool OnTextSelect(AudacityProject &project)
std::weak_ptr< WaveTrackAffordanceHandle > mAffordanceHandle
Observer::Subscription mSelectionChangeSubscription
std::shared_ptr< TextEditHelper > mTextEditHelper
std::vector< const WaveClip * > mLastVisibleClips
void StartEditSelectedClipName(AudacityProject &project)
std::shared_ptr< TextEditHelper > MakeTextEditHelper(const wxString &text)
std::weak_ptr< SelectHandle > mSelectHandle
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
std::weak_ptr< WaveClip > GetSelectedClip() const
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
unsigned OnAffordanceClick(const TrackPanelMouseEvent &event, AudacityProject *project)
Observer::Subscription mTrackListEventSubscription
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:51
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:196
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:293
AUDACITY_DLL_API wxRect DrawClipAffordance(wxDC &dc, const wxRect &affordanceRect, bool highlight=false, bool selected=false)
Definition: TrackArt.cpp:188
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:422
AUDACITY_DLL_API bool DrawClipTitle(wxDC &dc, const wxRect &titleRect, const wxString &title)
Definition: TrackArt.cpp:239
Iter SelectedClip(const ViewInfo &viewInfo, Iter begin, Iter end)
std::pair< WaveTrack *, std::shared_ptr< WaveClip > > SelectedClipOfFocusedTrack(AudacityProject &project)
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:1289
const Type mType
Definition: Track.h:1326
@ SELECTION_CHANGE
Posted when the set of selected tracks changes.
Definition: Track.h:1292