1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-17 16:50:26 +02:00

Add two commands to move the cursor/selection to next/prev label

The two commands are "selection to next label" and "selection to previous label".
They have default shortcuts alt+right and alt+left.

A label track does not have to be the focus. If there is a single label track in the project, that it used. If there is more than one label track, then the first label track, if any, starting at the focused track is used.

If the commands are used during playback of the project, playback continues from the new cursor/selection.

The commands provide feedback to screen readers: the name of the label, and position in the form of "i of n".
This commit is contained in:
David Bailes 2016-10-18 15:50:42 +01:00
parent e239efe486
commit ff9763f984
8 changed files with 261 additions and 68 deletions

View File

@ -105,7 +105,8 @@ LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
mMouseOverLabelRight(-1),
mRestoreFocus(-1),
mClipLen(0.0),
mIsAdjustingLabel(false)
mIsAdjustingLabel(false),
miLastLabel(-1)
{
SetDefaultName(_("Label Track"));
SetName(GetDefaultName());
@ -2849,3 +2850,55 @@ wxString LabelTrack::GetTextOfLabels(double t0, double t1) const
return retVal;
}
int LabelTrack::FindNextLabel(const SelectedRegion& currentRegion)
{
int i = -1;
if (!mLabels.empty()) {
int len = (int) mLabels.size();
if (miLastLabel >= 0 && miLastLabel + 1 < len
&& currentRegion.t0() == mLabels[miLastLabel].getT0()
&& currentRegion.t0() == mLabels[miLastLabel + 1].getT0() ) {
i = miLastLabel + 1;
}
else {
i = 0;
if (currentRegion.t0() < mLabels[len - 1].getT0()) {
while (i < len &&
mLabels[i].getT0() <= currentRegion.t0()) {
i++;
}
}
}
}
miLastLabel = i;
return i;
}
int LabelTrack::FindPrevLabel(const SelectedRegion& currentRegion)
{
int i = -1;
if (!mLabels.empty()) {
int len = (int) mLabels.size();
if (miLastLabel > 0 && miLastLabel < len
&& currentRegion.t0() == mLabels[miLastLabel].getT0()
&& currentRegion.t0() == mLabels[miLastLabel - 1].getT0() ) {
i = miLastLabel - 1;
}
else {
i = len - 1;
if (currentRegion.t0() > mLabels[0].getT0()) {
while (i >=0 &&
mLabels[i].getT0() >= currentRegion.t0()) {
i--;
}
}
}
}
miLastLabel = i;
return i;
}

View File

@ -232,6 +232,9 @@ class AUDACITY_DLL_API LabelTrack final : public Track
// Returns tab-separated text of all labels completely within given region
wxString GetTextOfLabels(double t0, double t1) const;
int FindNextLabel(const SelectedRegion& currentSelection);
int FindPrevLabel(const SelectedRegion& currentSelection);
public:
void SortLabels();
//These two are used by a TrackPanel KLUDGE, which is why they are public.
@ -268,6 +271,8 @@ class AUDACITY_DLL_API LabelTrack final : public Track
// Set in copied label tracks
double mClipLen;
int miLastLabel; // used by FindNextLabel and FindPrevLabel
void ComputeLayout(const wxRect & r, const ZoomInfo &zoomInfo) const;
void ComputeTextPosition(const wxRect & r, int index) const;

View File

@ -1251,6 +1251,11 @@ void AudacityProject::CreateMenusAndCommands()
c->AddCommand(wxT("PlaySpeedInc"), _("Increase playback speed"), FN(OnPlaySpeedInc));
c->AddCommand(wxT("PlaySpeedDec"), _("Decrease playback speed"), FN(OnPlaySpeedDec));
c->AddCommand(wxT("SelToNextLabel"), _("Selection to next label"), FN(OnSelToNextLabel), wxT("Alt+Right"),
CaptureNotBusyFlag | TrackPanelHasFocus, CaptureNotBusyFlag | TrackPanelHasFocus);
c->AddCommand(wxT("SelToPrevLabel"), _("Selection to previous label"), FN(OnSelToPrevLabel), wxT("Alt+Left"),
CaptureNotBusyFlag | TrackPanelHasFocus, CaptureNotBusyFlag | TrackPanelHasFocus);
#ifdef __WXMAC__
/* i8n-hint: Shrink all project windows to icons on the Macintosh tooldock */
c->AddCommand(wxT("MacMinimizeAll"), _("Minimize all projects"),
@ -2656,6 +2661,72 @@ void AudacityProject::OnSelToEnd()
ModifyState(false);
}
void AudacityProject::OnSelToNextLabel()
{
OnSelToLabel(true);
}
void AudacityProject::OnSelToPrevLabel()
{
OnSelToLabel(false);
}
void AudacityProject::OnSelToLabel(bool next)
{
// Find the number of label tracks, and ptr to last track found
Track* track = nullptr;
int nLabelTrack = 0;
TrackListOfKindIterator iter(Track::Label, &*mTracks);
for (Track* t = iter.First(); t; t = iter.Next()) {
nLabelTrack++;
track = t;
}
if (nLabelTrack == 0 ) {
mTrackPanel->MessageForScreenReader(_("no label track"));
}
else if (nLabelTrack > 1) { // find first label track, if any, starting at the focused track
track = mTrackPanel->GetFocusedTrack();
while (track && track->GetKind() != Track::Label) {
track = mTracks->GetNext(track, true);
if (!track) {
mTrackPanel->MessageForScreenReader(_("no label track at or below focused track"));
}
}
}
// If there is a single label track, or there is a label track at or below the focused track
if (track) {
LabelTrack* lt = static_cast<LabelTrack*>(track);
int i;
if (next)
i = lt->FindNextLabel(GetSelection());
else
i = lt->FindPrevLabel(GetSelection());
if (i >= 0) {
const LabelStruct* label = lt->GetLabel(i);
if (IsAudioActive()) {
OnPlayStop(); // stop
GetViewInfo().selectedRegion = label->selectedRegion;
RedrawProject();
OnPlayStop(); // play
}
else {
GetViewInfo().selectedRegion = label->selectedRegion;
RedrawProject();
}
wxString message;
message.Printf(wxT("%s %d of %d"), label->title, i + 1, lt->GetNumLabels() );
mTrackPanel->MessageForScreenReader(message);
}
else {
mTrackPanel->MessageForScreenReader(_("no labels in label track"));
}
}
}
void AudacityProject::OnCursorUp()
{
mTrackPanel->OnPrevTrack( false );

View File

@ -170,6 +170,10 @@ void OnSetRightSelection();
void OnSelToStart();
void OnSelToEnd();
void OnSelToNextLabel();
void OnSelToPrevLabel();
void OnSelToLabel(bool next);
void OnZeroCrossing();
void OnLockPlayRegion();

View File

@ -2406,6 +2406,12 @@ void TrackPanel::UpdateAccessibility()
mAx->Updated();
}
void TrackPanel::MessageForScreenReader(const wxString& message)
{
if (mAx)
mAx->MessageForScreenReader(message);
}
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
namespace {

View File

@ -308,6 +308,7 @@ protected:
public:
virtual void UpdateAccessibility();
void MessageForScreenReader(const wxString& message);
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
public:

View File

@ -37,6 +37,9 @@ TrackPanelAx::TrackPanelAx( wxWindow *window )
{
mTrackPanel = wxDynamicCast( window, TrackPanel );
mFocusedTrack = NULL;
mTrackName = true;
mMessageCount = 0;
}
TrackPanelAx::~TrackPanelAx()
@ -62,8 +65,11 @@ Track *TrackPanelAx::GetFocus()
// Changes focus to a specified track
void TrackPanelAx::SetFocus( Track *track )
{
mTrackName = true;
#if wxUSE_ACCESSIBILITY
if( mFocusedTrack != NULL )
if( mFocusedTrack != NULL && !mFocusedTrack->GetSelected() )
{
NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE,
mTrackPanel,
@ -164,6 +170,7 @@ void TrackPanelAx::Updated()
{
#if wxUSE_ACCESSIBILITY
Track *t = GetFocus();
mTrackName = true;
// logically, this should be an OBJECT_NAMECHANGE event, but Window eyes 9.1
// does not read out the name with this event type, hence use OBJECT_FOCUS.
@ -174,6 +181,32 @@ void TrackPanelAx::Updated()
#endif
}
void TrackPanelAx::MessageForScreenReader(const wxString& message)
{
#if wxUSE_ACCESSIBILITY
if (mTrackPanel == wxWindow::FindFocus())
{
Track *t = GetFocus();
int childId = t ? TrackNum(t) : 0;
mMessage = message;
// append \a alernatively, so that the string is never the same as the previous string.
// This ensures that screen readers read it.
if (mMessageCount % 2 == 0)
mMessage.Append('\a');
mMessageCount++;
mTrackName = false;
NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
mTrackPanel,
wxOBJID_CLIENT,
childId);
}
#endif
}
#if wxUSE_ACCESSIBILITY
// Retrieves the address of an IDispatch interface for the specified child.
@ -285,6 +318,8 @@ wxAccStatus TrackPanelAx::GetLocation( wxRect& rect, int elementId )
wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
{
#if defined(__WXMSW__) || defined(__WXMAC__)
if (mTrackName)
{
if( childId == wxACC_SELF )
{
*name = _( "TrackView" );
@ -361,6 +396,11 @@ wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
}
}
}
}
else
{
*name = mMessage;
}
return wxACC_OK;
#endif
@ -374,6 +414,8 @@ wxAccStatus TrackPanelAx::GetName( int childId, wxString* name )
wxAccStatus TrackPanelAx::GetRole( int childId, wxAccRole* role )
{
#if defined(__WXMSW__)
if (mTrackName)
{
if( childId == wxACC_SELF )
{
*role = wxROLE_SYSTEM_TABLE;
@ -382,6 +424,11 @@ wxAccStatus TrackPanelAx::GetRole( int childId, wxAccRole* role )
{
*role = wxROLE_SYSTEM_ROW;
}
}
else
{
*role = wxROLE_NONE;
}
#endif
#if defined(__WXMAC__)
@ -427,7 +474,7 @@ wxAccStatus TrackPanelAx::GetState( int childId, long* state )
*state |= wxACC_STATE_SYSTEM_FOCUSED;
}
if( t->GetSelected() )
if( t->GetSelected() && mTrackName )
{
*state |= wxACC_STATE_SYSTEM_SELECTED;
}

View File

@ -41,6 +41,8 @@ public:
// Called to signal changes to a track
void Updated();
void MessageForScreenReader(const wxString& message);
#if wxUSE_ACCESSIBILITY
// Retrieves the address of an IDispatch interface for the specified child.
// All objects must support this property.
@ -108,6 +110,10 @@ private:
TrackPanel *mTrackPanel;
Track *mFocusedTrack;
wxString mMessage;
bool mTrackName;
int mMessageCount;
};
#endif // __AUDACITY_TRACK_PANEL_ACCESSIBILITY__