Audacity 3.2.0
TrackPanelAx.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 TrackPanelAx.cpp
6
7 Leland Lucius
8 and lots of other contributors
9
10******************************************************************//*******************************************************************/
16#include "TrackPanelAx.h"
17
18// For compilers that support precompilation, includes "wx/wx.h".
19#include <wx/wxprec.h>
20
21#include <wx/setup.h> // for wxUSE_* macros
22
23#ifndef WX_PRECOMP
24// Include your minimal set of headers here, or wx.h
25#include <wx/wx.h>
26#endif
27
28#include "SyncLock.h"
29#include "LabelTrack.h"
30#include "NoteTrack.h"
31#include "TimeTrack.h"
32#include "Viewport.h"
33
35
37 const TranslatableString& message)
38{
39 if (mwAx)
40 mwAx->MessageForScreenReader(message);
41}
42
44{
45 if (mwAx)
46 mwAx->BeginChangeFocus();
47}
48
50 const std::shared_ptr<Track> &track)
51{
52 if (mwAx)
53 mwAx->EndChangeFocus(track);
54}
55
57{
58 if (mwAx)
59 mwAx->UpdateAccessibility();
60}
61
62
63TrackPanelAx::TrackPanelAx(std::weak_ptr<Viewport> wViewport,
64 std::weak_ptr<TrackFocus> wFocus, RectangleFinder finder
65) :
66#if wxUSE_ACCESSIBILITY
67 // window pointer must be set after construction
68 WindowAccessible(nullptr),
69#endif
70 mwViewport{ move(wViewport) },
71 mwFocus{ move(wFocus) }
72 , mFinder{ move(finder) }
73{
74}
75
77{
78}
79
81{
82 mTrackName = true;
83#if wxUSE_ACCESSIBILITY
84 auto pFocus = mwFocus.lock();
85 if (!pFocus)
86 return;
87
88 auto focusedTrack = pFocus->PeekFocus();
89 if (focusedTrack && !focusedTrack->GetSelected()) {
90 NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE,
91 GetWindow(),
92 wxOBJID_CLIENT,
93 pFocus->TrackNum(focusedTrack));
94 }
95#endif
96}
97
98void TrackPanelAx::EndChangeFocus(const std::shared_ptr<Track> &track)
99{
100#if wxUSE_ACCESSIBILITY
101 auto pFocus = mwFocus.lock();
102 if (!pFocus)
103 return;
104
105 if (track) {
106 if (GetWindow() == wxWindow::FindFocus()) {
107 NotifyEvent( wxACC_EVENT_OBJECT_FOCUS,
108 GetWindow(),
109 wxOBJID_CLIENT,
110 pFocus->NumFocusedTrack());
111 }
112
113 if (track->GetSelected()) {
114 NotifyEvent( wxACC_EVENT_OBJECT_SELECTION,
115 GetWindow(),
116 wxOBJID_CLIENT,
117 pFocus->NumFocusedTrack());
118 }
119 }
120 else {
121 NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
122 GetWindow(),
123 wxOBJID_CLIENT,
124 wxACC_SELF);
125 }
126
127#endif
128}
129
131{
132 Updated();
133}
134
136{
137#if wxUSE_ACCESSIBILITY
138 auto pFocus = mwFocus.lock();
139 if (!pFocus)
140 return;
141
142 auto t = pFocus->GetFocus();
143 mTrackName = true;
144
145 // The object_focus event is only needed by Window-Eyes
146 // and can be removed when we cease to support this screen reader.
147 NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
148 GetWindow(),
149 wxOBJID_CLIENT,
150 pFocus->TrackNum(t));
151
152 NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE,
153 GetWindow(),
154 wxOBJID_CLIENT,
155 pFocus->TrackNum(t));
156#endif
157}
158
160{
161#if wxUSE_ACCESSIBILITY
162 auto pFocus = mwFocus.lock();
163 if (!pFocus)
164 return;
165
167 {
168 auto t = pFocus->GetFocus();
169 int childId = t ? pFocus->TrackNum(t) : 0;
170
171 mMessage = message.Translation();
172
173 // append \a alternatively, so that the string is never the same as the previous string.
174 // This ensures that screen readers read it.
175 if (mMessageCount % 2 == 0)
176 mMessage.Append('\a');
178
179 mTrackName = false;
180 NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE,
181 GetWindow(),
182 wxOBJID_CLIENT,
183 childId);
184 }
185
186#endif
187}
188
189#if wxUSE_ACCESSIBILITY
190
191// Retrieves the address of an IDispatch interface for the specified child.
192// All objects must support this property.
193wxAccStatus TrackPanelAx::GetChild( int childId, wxAccessible** child )
194{
195 if( childId == wxACC_SELF )
196 {
197 *child = this;
198 }
199 else
200 {
201 *child = NULL;
202 }
203
204 return wxACC_OK;
205}
206
207// Gets the number of children.
208wxAccStatus TrackPanelAx::GetChildCount( int* childCount )
209{
210 auto pFocus = mwFocus.lock();
211 if (!pFocus)
212 return wxACC_FAIL;
213
214 *childCount = as_const(*pFocus).GetTracks().Any().size();
215 return wxACC_OK;
216}
217
218// Gets the default action for this object (0) or > 0 (the action for a child).
219// Return wxACC_OK even if there is no action. actionName is the action, or the empty
220// string if there is no action.
221// The retrieved string describes the action that is performed on an object,
222// not what the object does as a result. For example, a toolbar button that prints
223// a document has a default action of "Press" rather than "Prints the current document."
224wxAccStatus TrackPanelAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName )
225{
226 actionName->clear();
227
228 return wxACC_OK;
229}
230
231// Returns the description for this object or a child.
232wxAccStatus TrackPanelAx::GetDescription( int WXUNUSED(childId), wxString *description )
233{
234 description->clear();
235
236 return wxACC_OK;
237}
238
239// Returns help text for this object or a child, similar to tooltip text.
240wxAccStatus TrackPanelAx::GetHelpText( int WXUNUSED(childId), wxString *helpText )
241{
242 helpText->clear();
243
244 return wxACC_OK;
245}
246
247// Returns the keyboard shortcut for this object or child.
248// Return e.g. ALT+K
249wxAccStatus TrackPanelAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut )
250{
251 shortcut->clear();
252
253 return wxACC_OK;
254}
255
256// Returns the rectangle for this object (id = 0) or a child element (id > 0).
257// rect is in screen coordinates.
258wxAccStatus TrackPanelAx::GetLocation( wxRect& rect, int elementId )
259{
260 auto pFocus = mwFocus.lock();
261 if (!pFocus)
262 return wxACC_FAIL;
263
264 wxRect client;
265
266 if( elementId == wxACC_SELF )
267 {
268 rect = GetWindow()->GetScreenRect();
269 }
270 else
271 {
272 auto t = pFocus->FindTrack(elementId);
273
274 if( t == NULL )
275 {
276 return wxACC_FAIL;
277 }
278
279 rect = mFinder ? mFinder( *t ) : wxRect{};
280 // Inflate the screen reader's rectangle so it overpaints Audacity's own
281 // yellow focus rectangle.
282#ifdef __WXMAC__
283 const int dx = 2;
284#else
285 const int dx = 1;
286#endif
287 rect.Inflate(dx, dx);
288 rect.SetPosition(GetWindow()->ClientToScreen(rect.GetPosition()));
289 }
290
291 return wxACC_OK;
292}
293
294// Gets the name of the specified object.
295wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
296{
297#if defined(__WXMSW__) || defined(__WXMAC__)
298 if (mTrackName)
299 {
300 if( childId == wxACC_SELF )
301 {
302 *name = _("TrackView");
303 }
304 else
305 {
306 auto pFocus = mwFocus.lock();
307 if (!pFocus)
308 return wxACC_FAIL;
309
310 auto t = pFocus->FindTrack(childId);
311
312 if (!t)
313 return wxACC_FAIL;
314
315 name->Printf("%d %s", pFocus->TrackNum(t), t->GetName());
316
317 if(dynamic_cast<LabelTrack*>(t.get()))
318 {
319 const auto trackNameLower = t->GetName().Lower();
320 //Prior to version 3.2 "Label Track" was the default
321 //name for label tracks, don't append type part to the
322 //text to avoid verbosity.
323 if(trackNameLower.Find(wxString(_("Label Track")).Lower()) == wxNOT_FOUND &&
324 trackNameLower.Find(LabelTrack::GetDefaultName().Lower()) == wxNOT_FOUND)
325 /* i18n-hint: This is for screen reader software and indicates that
326 this is a Label track.*/
327 name->Append( wxT(" ") + wxString(_("Label Track")));
328 }
329 else if(dynamic_cast<TimeTrack*>(t.get()))
330 {
331 if(t->GetName().Lower().Find(TimeTrack::GetDefaultName().Lower()) == wxNOT_FOUND)
332 /* i18n-hint: This is for screen reader software and indicates that
333 this is a Time track.*/
334 name->Append(wxT(" ") + wxString(_("Time Track")));
335 }
336#ifdef USE_MIDI
337 else if(dynamic_cast<NoteTrack*>(t.get()))
338 /* i18n-hint: This is for screen reader software and indicates that
339 this is a Note track.*/
340 name->Append( wxT(" ") + wxString(_("Note Track")));
341#endif
342
343 // LLL: Remove these during "refactor"
344 auto pt = dynamic_cast<PlayableTrack *>(t.get());
345 if( pt && pt->GetMute() )
346 {
347 // The following comment also applies to the solo, selected,
348 // and synclockselected states.
349 // Many of translations of the strings with a leading space omitted
350 // the leading space. Therefore a space has been added using wxT(" ").
351 // Because screen readers won't be affected by multiple spaces, the
352 // leading spaces have not been removed, so that no NEW translations are needed.
353 /* i18n-hint: This is for screen reader software and indicates that
354 this track is muted. (The mute button is on.)*/
355 name->Append( wxT(" ") + wxString(_( " Muted" )) );
356 }
357
358 if( pt && pt->GetSolo() )
359 {
360 /* i18n-hint: This is for screen reader software and indicates that
361 this track is soloed. (The Solo button is on.)*/
362 name->Append( wxT(" ") + wxString(_( " Soloed" )) );
363 }
364 if( t->GetSelected() )
365 {
366 /* i18n-hint: This is for screen reader software and indicates that
367 this track is selected.*/
368 name->Append( wxT(" ") + wxString(_( " Selected" )) );
369 }
371 {
372 /* i18n-hint: This is for screen reader software and indicates that
373 this track is shown with a sync-locked icon.*/
374 // The absence of a dash between Sync and Locked is deliberate -
375 // if present, Jaws reads it as "dash".
376 name->Append( wxT(" ") + wxString(_( " Sync Locked" )) );
377 }
378 }
379 }
380 else
381 {
382 *name = mMessage;
383 }
384
385 return wxACC_OK;
386#endif
387
388#if defined(__WXMAC__)
389 return wxACC_NOT_IMPLEMENTED;
390#endif
391}
392
393// Returns a role constant.
394wxAccStatus TrackPanelAx::GetRole( int childId, wxAccRole* role )
395{
396#if defined(__WXMSW__)
397 if (mTrackName)
398 {
399 if( childId == wxACC_SELF )
400 {
401 *role = wxROLE_SYSTEM_TABLE;
402 }
403 else
404 {
405 *role = wxROLE_SYSTEM_ROW;
406 }
407 }
408 else
409 {
410 *role = wxROLE_NONE;
411 }
412#endif
413
414#if defined(__WXMAC__)
415 if( childId == wxACC_SELF )
416 {
417 *role = wxROLE_SYSTEM_PANE;
418 }
419 else
420 {
421 *role = wxROLE_SYSTEM_STATICTEXT;
422 }
423#endif
424
425 return wxACC_OK;
426}
427
428// Gets a variant representing the selected children
429// of this object.
430// Acceptable values:
431// - a null variant (IsNull() returns TRUE)
432// - a list variant (GetType() == wxT("list"))
433// - an integer representing the selected child element,
434// or 0 if this object is selected (GetType() == wxT("long"))
435// - a "void*" pointer to a wxAccessible child object
436wxAccStatus TrackPanelAx::GetSelections( wxVariant * WXUNUSED(selections) )
437{
438 return wxACC_NOT_IMPLEMENTED;
439}
440
441// Returns a state constant.
442wxAccStatus TrackPanelAx::GetState( int childId, long* state )
443{
444#if defined(__WXMSW__)
445 auto pFocus = mwFocus.lock();
446 if (!pFocus)
447 return wxACC_FAIL;
448
449 if( childId > 0 )
450 {
451 auto t = pFocus->FindTrack(childId);
452
453 *state = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
454 if (t)
455 {
456 if (t == pFocus->PeekFocus() && GetWindow() == wxWindow::FindFocus())
457 {
458 *state |= wxACC_STATE_SYSTEM_FOCUSED;
459 }
460
461 if( t->GetSelected() && mTrackName )
462 {
463 *state |= wxACC_STATE_SYSTEM_SELECTED;
464 }
465 }
466 }
467 else // childId == wxACC_SELF
468 {
469 // let wxWidgets use a standard accessible object for the state
470 return wxACC_NOT_IMPLEMENTED;
471 }
472#endif
473
474#if defined(__WXMAC__)
475 auto pFocus = mwFocus.lock();
476 if (!pFocus)
477 return wxACC_FAIL;
478
479 *state = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
480
481 if( childId > 0 )
482 {
483 auto t = pFocus->FindTrack(childId);
484
485 if (t)
486 {
487 if (t == pFocus->PeekFocus())
488 {
489 *state |= wxACC_STATE_SYSTEM_FOCUSED;
490 }
491
492 if( t->GetSelected() )
493 {
494 *state |= wxACC_STATE_SYSTEM_SELECTED;
495 }
496 }
497 }
498#endif
499
500 return wxACC_OK;
501}
502
503// Returns a localized string representing the value for the object
504// or child.
505#if defined(__WXMAC__)
506wxAccStatus TrackPanelAx::GetValue( int childId, wxString* strValue )
507#else
508wxAccStatus TrackPanelAx::GetValue( int WXUNUSED(childId), wxString* WXUNUSED(strValue) )
509#endif
510{
511#if defined(__WXMSW__)
512 return wxACC_NOT_IMPLEMENTED;
513#endif
514
515#if defined(__WXMAC__)
516 if( childId == wxACC_SELF )
517 {
518 *strValue = _("TrackView");
519 }
520 else
521 {
522 auto pFocus = mwFocus.lock();
523 if (!pFocus)
524 return wxACC_FAIL;
525
526 auto t = pFocus->FindTrack(childId);
527
528 if( t == NULL )
529 {
530 return wxACC_FAIL;
531 }
532 else
533 {
534 /* i18n-hint: The %d is replaced by the number of the track.*/
535 strValue->Printf(_("Track %d"), pFocus->TrackNum(t));
536 strValue->Append(" " + t->GetName());
537
538 // LLL: Remove these during "refactor"
539 auto pt = dynamic_cast<PlayableTrack *>(t.get());
540 if( pt && pt->GetMute() )
541 {
542 strValue->Append( _( " Mute On" ) );
543 }
544
545 if( pt && pt->GetSolo() )
546 {
547 strValue->Append( _( " Solo On" ) );
548 }
549 if( t->GetSelected() )
550 {
551 strValue->Append( _( " Select On" ) );
552 }
553 }
554 }
555 return wxACC_OK;
556#endif
557}
558
559// Gets the window with the keyboard focus.
560// If childId is 0 and child is NULL, no object in
561// this subhierarchy has the focus.
562// If this object has the focus, child should be 'this'.
563wxAccStatus TrackPanelAx::GetFocus( int *childId, wxAccessible **child )
564{
565 auto pFocus = mwFocus.lock();
566 if (!pFocus)
567 return wxACC_FAIL;
568
569#if defined(__WXMSW__)
570
572 {
573 auto focusedTrack = pFocus->PeekFocus();
574 if (focusedTrack)
575 {
576 *childId = pFocus->TrackNum(focusedTrack);
577 }
578 else
579 {
580 *child = this;
581 }
582 }
583
584 return wxACC_OK;
585#endif
586
587#if defined(__WXMAC__)
588 if( GetWindow() == wxWindow::FindFocus() )
589 {
590 auto focusedTrack = pFocus->PeekFocus();
591 if( focusedTrack )
592 {
593 *childId = pFocus->TrackNum(focusedTrack);
594 }
595 else
596 {
597 *childId = wxACC_SELF;
598 }
599
600 return wxACC_OK;
601 }
602
603 return wxACC_NOT_IMPLEMENTED;
604#endif
605}
606
607// Navigates from fromId to toId/toObject
608wxAccStatus TrackPanelAx::Navigate(wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject)
609{
610 int childCount;
611 GetChildCount( &childCount );
612
613 if (fromId > childCount)
614 return wxACC_FAIL;
615
616 switch (navDir) {
617 case wxNAVDIR_FIRSTCHILD:
618 if (fromId == wxACC_SELF && childCount > 0 )
619 *toId = 1;
620 else
621 return wxACC_FALSE;
622 break;
623
624 case wxNAVDIR_LASTCHILD:
625 if (fromId == wxACC_SELF && childCount > 0 )
626 *toId = childCount;
627 else
628 return wxACC_FALSE;
629 break;
630
631 case wxNAVDIR_NEXT:
632 case wxNAVDIR_DOWN:
633 if (fromId != wxACC_SELF) {
634 *toId = fromId + 1;
635 if (*toId > childCount)
636 return wxACC_FALSE;
637 }
638 else
639 return wxACC_NOT_IMPLEMENTED;
640 break;
641
642 case wxNAVDIR_PREVIOUS:
643 case wxNAVDIR_UP:
644 if (fromId != wxACC_SELF) {
645 *toId = fromId - 1;
646 if (*toId < 1)
647 return wxACC_FALSE;
648 }
649 else
650 return wxACC_NOT_IMPLEMENTED;
651 break;
652
653 case wxNAVDIR_LEFT:
654 case wxNAVDIR_RIGHT:
655 if (fromId != wxACC_SELF)
656 return wxACC_FALSE;
657 else
658 return wxACC_NOT_IMPLEMENTED;
659 break;
660 }
661
662 *toObject = nullptr;
663 return wxACC_OK;
664}
665
666// Modify focus or selection
667wxAccStatus TrackPanelAx::Select(int childId, wxAccSelectionFlags selectFlags)
668{
669 auto pFocus = mwFocus.lock();
670 if (!pFocus)
671 return wxACC_FAIL;
672
673 // Only support change of focus
674 if (selectFlags != wxACC_SEL_TAKEFOCUS)
675 return wxACC_NOT_IMPLEMENTED;
676
677 if (childId != wxACC_SELF) {
678 int childCount;
679 GetChildCount( &childCount );
680 if (childId > childCount)
681 return wxACC_FAIL;
682
683 Track* t = pFocus->FindTrack(childId).get();
684 if (t) {
685 pFocus->SetFocus(t->SharedPointer());
686 auto pViewport = mwViewport.lock();
687 if (pViewport)
688 pViewport->ShowTrack(*t);
689 }
690 }
691 else
692 return wxACC_NOT_IMPLEMENTED;
693
694 return wxACC_OK;
695}
696
697#endif // wxUSE_ACCESSIBILITY
wxT("CloseDown"))
#define _(s)
Definition: Internat.h:73
wxString name
Definition: TagsEditor.cpp:166
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:98
static wxString GetDefaultName()
Definition: LabelTrack.cpp:96
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:78
AudioTrack subclass that can also be audibly replayed by the program.
Definition: PlayableTrack.h:40
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:24
static wxString GetDefaultName()
Definition: TimeTrack.cpp:34
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:146
RectangleFinder mFinder
Definition: TrackPanelAx.h:142
void BeginChangeFocus() override
TrackPanelAx(std::weak_ptr< Viewport > wViewport, std::weak_ptr< TrackFocus > wFocus, RectangleFinder finder)
std::weak_ptr< Viewport > mwViewport
Definition: TrackPanelAx.h:135
std::function< wxRect(Track &) > RectangleFinder
Definition: TrackPanelAx.h:50
std::weak_ptr< TrackFocus > mwFocus
Definition: TrackPanelAx.h:136
wxString mMessage
Definition: TrackPanelAx.h:144
void UpdateAccessibility() override
wxWindow * GetWindow() const
Definition: TrackPanelAx.h:126
void MessageForScreenReader(const TranslatableString &message) override
void EndChangeFocus(const std::shared_ptr< Track > &track) override
~TrackPanelAx() override
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:383
void BeginChangeFocus() override
void MessageForScreenReader(const TranslatableString &message) override
const wxWeakRef< TrackPanelAx > mwAx
Definition: TrackPanelAx.h:47
void UpdateAccessibility() override
void EndChangeFocus(const std::shared_ptr< Track > &track) override