1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-25 16:48:44 +02:00

Splitting mSelIndex

Using mTextEditIndex for tracking active text box index, and mNavigationIndex used to track selection for keyboard navigation purposes
See #1195
This commit is contained in:
Vitaly Sverchinsky 2021-07-21 19:48:32 +03:00
parent 068ef4c36f
commit b90b5cfd47
11 changed files with 307 additions and 296 deletions

View File

@ -426,7 +426,7 @@ bool LabelDialog::TransferDataFromWindow()
// Add the label to it // Add the label to it
lt->AddLabel(rd.selectedRegion, rd.title); lt->AddLabel(rd.selectedRegion, rd.title);
LabelTrackView::Get( *lt ).SetSelectedIndex( -1 ); LabelTrackView::Get( *lt ).ResetTextSelection();
} }
return true; return true;

View File

@ -114,13 +114,13 @@ bool SetLabelCommand::Apply(const CommandContext & context)
auto &view = LabelTrackView::Get( *labelTrack ); auto &view = LabelTrackView::Get( *labelTrack );
if( mbSelected ) if( mbSelected )
{ {
view.SetSelectedIndex( ii ); view.SetNavigationIndex( ii );
double t0 = pLabel->selectedRegion.t0(); double t0 = pLabel->selectedRegion.t0();
double t1 = pLabel->selectedRegion.t1(); double t1 = pLabel->selectedRegion.t1();
selectedRegion.setTimes( t0, t1); selectedRegion.setTimes( t0, t1);
} }
else if( view.GetSelectedIndex( context.project ) == ii ) else if( view.GetNavigationIndex( context.project ) == ii )
view.SetSelectedIndex( -1 ); view.SetNavigationIndex( -1 );
} }
labelTrack->SortLabels(); labelTrack->SortLabels();

View File

@ -46,7 +46,7 @@ bool DoPasteText(AudacityProject &project)
for (auto pLabelTrack : tracks.Any<LabelTrack>()) for (auto pLabelTrack : tracks.Any<LabelTrack>())
{ {
// Does this track have an active label? // Does this track have an active label?
if (LabelTrackView::Get( *pLabelTrack ).HasSelection( project )) { if (LabelTrackView::Get( *pLabelTrack ).GetTextEditIndex(project) != -1) {
// Yes, so try pasting into it // Yes, so try pasting into it
auto &view = LabelTrackView::Get( *pLabelTrack ); auto &view = LabelTrackView::Get( *pLabelTrack );

View File

@ -346,7 +346,7 @@ void OnPasteNewLabel(const CommandContext &context)
// Unselect the last label, so we'll have just one active label when // Unselect the last label, so we'll have just one active label when
// we're done // we're done
if (plt) if (plt)
LabelTrackView::Get( *plt ).SetSelectedIndex( -1 ); LabelTrackView::Get( *plt ).ResetTextSelection();
// Add a NEW label, paste into it // Add a NEW label, paste into it
// Paul L: copy whatever defines the selected region, not just times // Paul L: copy whatever defines the selected region, not just times

View File

@ -72,7 +72,6 @@ UIHandle::Result LabelDefaultClickHandle::Click
if (pLT != &TrackView::Get( *lt )) { if (pLT != &TrackView::Get( *lt )) {
auto &view = LabelTrackView::Get( *lt ); auto &view = LabelTrackView::Get( *lt );
view.ResetFlags(); view.ResetFlags();
view.SetSelectedIndex( -1 );
} }
} }
} }

View File

@ -19,6 +19,9 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../TrackPanelMouseEvent.h" #include "../../../TrackPanelMouseEvent.h"
#include "../../../UndoManager.h" #include "../../../UndoManager.h"
#include "../../../ViewInfo.h" #include "../../../ViewInfo.h"
#include "../../../SelectionState.h"
#include "../../../ProjectAudioIO.h"
#include "../../../tracks/ui/TimeShiftHandle.h"
#include <wx/cursor.h> #include <wx/cursor.h>
#include <wx/translation.h> #include <wx/translation.h>
@ -125,18 +128,21 @@ LabelGlyphHandle::~LabelGlyphHandle()
void LabelGlyphHandle::HandleGlyphClick void LabelGlyphHandle::HandleGlyphClick
(LabelTrackHit &hit, const wxMouseEvent & evt, (LabelTrackHit &hit, const wxMouseEvent & evt,
const wxRect & r, const ZoomInfo &zoomInfo, const wxRect & r, const ZoomInfo &zoomInfo,
NotifyingSelectedRegion &WXUNUSED(newSel)) NotifyingSelectedRegion &newSel)
{ {
if (evt.ButtonDown()) if (evt.ButtonDown())
{ {
//OverGlyph sets mMouseOverLabel to be the chosen label. //OverGlyph sets mMouseOverLabel to be the chosen label.
const auto pTrack = mpLT; const auto pTrack = mpLT;
LabelTrackView::OverGlyph(*pTrack, hit, evt.m_x, evt.m_y); LabelTrackView::OverGlyph(*pTrack, hit, evt.m_x, evt.m_y);
hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) && hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
( hit.mEdge & 3 ) != 0; ( hit.mEdge & 3 ) != 0;
if (hit.mIsAdjustingLabel) if (hit.mIsAdjustingLabel)
{ {
auto& view = LabelTrackView::Get(*pTrack);
view.ResetTextSelection();
double t = 0.0; double t = 0.0;
@ -204,6 +210,8 @@ UIHandle::Result LabelGlyphHandle::Click
auto result = LabelDefaultClickHandle::Click( evt, pProject ); auto result = LabelDefaultClickHandle::Click( evt, pProject );
const wxMouseEvent &event = evt.event; const wxMouseEvent &event = evt.event;
auto& selectionState = SelectionState::Get(*pProject);
auto& tracks = TrackList::Get(*pProject);
auto &viewInfo = ViewInfo::Get( *pProject ); auto &viewInfo = ViewInfo::Get( *pProject );
HandleGlyphClick( HandleGlyphClick(
@ -310,6 +318,47 @@ bool LabelGlyphHandle::HandleGlyphDragRelease
pTrack->SetLabel( hit.mMouseOverLabelRight, labelStruct ); pTrack->SetLabel( hit.mMouseOverLabelRight, labelStruct );
} }
if (hit.mMouseOverLabel >= 0)
{
auto labelStruct = mLabels[hit.mMouseOverLabel];
if (!labelStruct.updated)
{
//happens on click over bar between handles (without moving a cursor)
newSel = labelStruct.selectedRegion;
// IF the user clicked a label, THEN select all other tracks by Label
// do nothing if at least one other track is selected
auto& selectionState = SelectionState::Get(project);
auto& tracks = TrackList::Get(project);
bool done = tracks.Selected().any_of(
[&](const Track* track) { return track != static_cast<Track*>(pTrack.get()); }
);
if (!done) {
//otherwise, select all tracks
for (auto t : tracks.Any())
selectionState.SelectTrack(*t, true, true);
}
// Do this after, for its effect on TrackPanel's memory of last selected
// track (which affects shift-click actions)
selectionState.SelectTrack(*pTrack.get(), true, true);
// PRL: bug1659 -- make selection change undo correctly
updated = !ProjectAudioIO::Get(project).IsAudioActive();
auto& view = LabelTrackView::Get(*pTrack);
view.SetNavigationIndex(hit.mMouseOverLabel);
}
else
{
labelStruct.updated = false;
pTrack->SetLabel(hit.mMouseOverLabel, labelStruct);
updated = true;
}
}
hit.mIsAdjustingLabel = false; hit.mIsAdjustingLabel = false;
hit.mMouseOverLabelLeft = -1; hit.mMouseOverLabelLeft = -1;
hit.mMouseOverLabelRight = -1; hit.mMouseOverLabelRight = -1;
@ -350,12 +399,15 @@ bool LabelGlyphHandle::HandleGlyphDragRelease
} }
const auto &view = LabelTrackView::Get( *pTrack ); const auto &view = LabelTrackView::Get( *pTrack );
if( view.HasSelection( project ) ) auto navigationIndex = view.GetNavigationIndex(project);
if(navigationIndex != -1 &&
(navigationIndex == hit.mMouseOverLabel ||
navigationIndex == hit.mMouseOverLabelLeft ||
navigationIndex == hit.mMouseOverLabelRight))
{ {
auto selIndex = view.GetSelectedIndex( project );
//Set the selection region to be equal to //Set the selection region to be equal to
//the NEW size of the label. //the NEW size of the label.
newSel = mLabels[ selIndex ].selectedRegion; newSel = mLabels[navigationIndex].selectedRegion;
} }
pTrack->SortLabels(); pTrack->SortLabels();
} }

View File

@ -70,34 +70,21 @@ LabelTextHandle::~LabelTextHandle()
{ {
} }
void LabelTextHandle::HandleTextClick(AudacityProject & void LabelTextHandle::HandleTextClick(AudacityProject &project, const wxMouseEvent & evt)
#if defined(__WXGTK__) && (HAVE_GTK)
project
#endif
,
const wxMouseEvent & evt,
const wxRect & r, const ZoomInfo &zoomInfo,
NotifyingSelectedRegion &newSel)
{ {
auto pTrack = mpLT.lock(); auto pTrack = mpLT.lock();
if (!pTrack) if (!pTrack)
return; return;
auto &view = LabelTrackView::Get( *pTrack ); auto &view = LabelTrackView::Get( *pTrack );
static_cast<void>(r);//compiler food.
static_cast<void>(zoomInfo);//compiler food.
if (evt.ButtonDown()) if (evt.ButtonDown())
{ {
const auto selIndex = LabelTrackView::OverATextBox( *pTrack, evt.m_x, evt.m_y ); const auto selIndex = LabelTrackView::OverATextBox( *pTrack, evt.m_x, evt.m_y );
view.SetSelectedIndex( selIndex );
if ( selIndex != -1 ) { if ( selIndex != -1 ) {
const auto &mLabels = pTrack->GetLabels();
const auto &labelStruct = mLabels[ selIndex ];
newSel = labelStruct.selectedRegion;
if (evt.LeftDown()) { if (evt.LeftDown()) {
mRightDragging = false;
// Find the NEW drag end // Find the NEW drag end
auto position = view.FindCursorPosition( evt.m_x ); auto position = view.FindCursorPosition(selIndex, evt.m_x );
// Anchor shift-drag at the farther end of the previous highlight // Anchor shift-drag at the farther end of the previous highlight
// that is farther from the click, on Mac, for consistency with // that is farther from the click, on Mac, for consistency with
@ -118,13 +105,18 @@ void LabelTextHandle::HandleTextClick(AudacityProject &
else else
initial = position; initial = position;
view.SetTextHighlight( initial, position ); view.SetTextSelection(selIndex, initial, position );
mRightDragging = false;
} }
else else
{
if (!view.IsTextSelected(project))
{
auto position = view.FindCursorPosition(selIndex, evt.m_x);
view.SetTextSelection(selIndex, position, position);
}
// Actually this might be right or middle down // Actually this might be right or middle down
mRightDragging = true; mRightDragging = true;
}
// Middle click on GTK: paste from primary selection // Middle click on GTK: paste from primary selection
#if defined(__WXGTK__) && (HAVE_GTK) #if defined(__WXGTK__) && (HAVE_GTK)
if (evt.MiddleDown()) { if (evt.MiddleDown()) {
@ -132,7 +124,7 @@ void LabelTextHandle::HandleTextClick(AudacityProject &
// case PasteSelectedText() will start a NEW label at the click // case PasteSelectedText() will start a NEW label at the click
// location // location
if (!LabelTrackView::OverTextBox(&labelStruct, evt.m_x, evt.m_y)) if (!LabelTrackView::OverTextBox(&labelStruct, evt.m_x, evt.m_y))
view.SetSelectedIndex( -1 ); view.ResetTextSelection();
double t = zoomInfo.PositionToTime(evt.m_x, r.x); double t = zoomInfo.PositionToTime(evt.m_x, r.x);
newSel = SelectedRegion(t, t); newSel = SelectedRegion(t, t);
} }
@ -158,41 +150,10 @@ UIHandle::Result LabelTextHandle::Click
auto result = LabelDefaultClickHandle::Click( evt, pProject ); auto result = LabelDefaultClickHandle::Click( evt, pProject );
auto &selectionState = SelectionState::Get( *pProject );
auto &tracks = TrackList::Get( *pProject );
mChanger =
std::make_shared< SelectionStateChanger >( selectionState, tracks );
const wxMouseEvent &event = evt.event; const wxMouseEvent &event = evt.event;
auto &viewInfo = ViewInfo::Get( *pProject ); auto &viewInfo = ViewInfo::Get( *pProject );
mSelectedRegion = viewInfo.selectedRegion; HandleTextClick(*pProject, event);
HandleTextClick( *pProject,
event, evt.rect, viewInfo, viewInfo.selectedRegion );
{
// IF the user clicked a label, THEN select all other tracks by Label
//do nothing if at least one other track is selected
bool done = tracks.Selected().any_of(
[&](const Track *pTrack){ return pTrack != pLT.get(); }
);
if (!done) {
//otherwise, select all tracks
for (auto t : tracks.Any())
selectionState.SelectTrack( *t, true, true );
}
// Do this after, for its effect on TrackPanel's memory of last selected
// track (which affects shift-click actions)
selectionState.SelectTrack( *pLT, true, true );
}
// PRL: bug1659 -- make selection change undo correctly
const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
if (!unsafe)
ProjectHistory::Get( *pProject ).ModifyState(false);
return result | RefreshCode::RefreshCell; return result | RefreshCode::RefreshCell;
} }
@ -228,19 +189,19 @@ void LabelTextHandle::HandleTextDragRelease(
if(evt.Dragging()) if(evt.Dragging())
{ {
if (!mRightDragging) auto index = view.GetTextEditIndex(project);
if (!mRightDragging && index != -1)
// Update drag end // Update drag end
view.SetCurrentCursorPosition( view.SetCurrentCursorPosition(view.FindCursorPosition(index, evt.m_x ));
view.FindCursorPosition( evt.m_x ) );
return; return;
} }
if (evt.RightUp()) { if (evt.RightUp())
const auto selIndex = view.GetSelectedIndex( project ); {
if ( selIndex != -1 && auto index = view.GetTextEditIndex(project);
LabelTrackView::OverTextBox( if(index != -1 &&
pTrack->GetLabel( selIndex ), evt.m_x, evt.m_y ) ) { LabelTrackView::OverTextBox(pTrack->GetLabel(index), evt.m_x, evt.m_y))
{
// popup menu for editing // popup menu for editing
// TODO: handle context menus via CellularPanel? // TODO: handle context menus via CellularPanel?
view.ShowContextMenu( project ); view.ShowContextMenu( project );
@ -269,10 +230,9 @@ UIHandle::Result LabelTextHandle::Drag
mLabelTrackStartYPos = event.m_y; mLabelTrackStartYPos = event.m_y;
auto pView = pLT ? &LabelTrackView::Get( *pLT ) : nullptr; auto pView = pLT ? &LabelTrackView::Get( *pLT ) : nullptr;
if (pLT && if (pLT && (pView->GetTextEditIndex( project ) != -1) &&
(pView->GetSelectedIndex( project ) != -1) &&
LabelTrackView::OverTextBox( LabelTrackView::OverTextBox(
pLT->GetLabel(pView->GetSelectedIndex( project )), pLT->GetLabel(pView->GetTextEditIndex( project )),
mLabelTrackStartXPos, mLabelTrackStartXPos,
mLabelTrackStartYPos)) mLabelTrackStartYPos))
mLabelTrackStartYPos = -1; mLabelTrackStartYPos = -1;
@ -301,11 +261,6 @@ UIHandle::Result LabelTextHandle::Release
// Only selected a part of a text string and changed track selectedness. // Only selected a part of a text string and changed track selectedness.
// No undoable effects. // No undoable effects.
if (mChanger) {
mChanger->Commit();
mChanger.reset();
}
const wxMouseEvent &event = evt.event; const wxMouseEvent &event = evt.event;
auto pLT = TrackList::Get( *pProject ).Lock(mpLT); auto pLT = TrackList::Get( *pProject ).Lock(mpLT);
if (pLT) if (pLT)
@ -320,11 +275,6 @@ UIHandle::Result LabelTextHandle::Release
UIHandle::Result LabelTextHandle::Cancel( AudacityProject *pProject ) UIHandle::Result LabelTextHandle::Cancel( AudacityProject *pProject )
{ {
// Restore the selection states of tracks
// Note that we are also relying on LabelDefaultClickHandle::Cancel
// to restore the selection state of the labels in the tracks.
auto &viewInfo = ViewInfo::Get( *pProject );
viewInfo.selectedRegion = mSelectedRegion;
auto result = LabelDefaultClickHandle::Cancel( pProject ); auto result = LabelDefaultClickHandle::Cancel( pProject );
return result | RefreshCode::RefreshAll; return result | RefreshCode::RefreshAll;
} }

View File

@ -58,9 +58,7 @@ public:
private: private:
void HandleTextClick void HandleTextClick
(AudacityProject &project, (AudacityProject &project, const wxMouseEvent & evt);
const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo,
NotifyingSelectedRegion &newSel);
void HandleTextDragRelease( void HandleTextDragRelease(
AudacityProject &project, const wxMouseEvent & evt); AudacityProject &project, const wxMouseEvent & evt);
@ -68,8 +66,6 @@ private:
int mLabelNum{ -1 }; int mLabelNum{ -1 };
int mLabelTrackStartXPos { -1 }; int mLabelTrackStartXPos { -1 };
int mLabelTrackStartYPos { -1 }; int mLabelTrackStartYPos { -1 };
SelectedRegion mSelectedRegion{};
std::shared_ptr<SelectionStateChanger> mChanger;
/// flag to tell if it's a valid dragging /// flag to tell if it's a valid dragging
bool mRightDragging{ false }; bool mRightDragging{ false };

View File

@ -60,7 +60,7 @@ public:
iLabel = iLabel =
LabelTrackView::OverATextBox(*mpTrack, pParams->xx, pParams->yy); LabelTrackView::OverATextBox(*mpTrack, pParams->xx, pParams->yy);
if (iLabel == -1) if (iLabel == -1)
iLabel = LabelTrackView::Get(*mpTrack).GetSelectedIndex(mProject); iLabel = LabelTrackView::Get(*mpTrack).GetNavigationIndex(mProject);
if (iLabel != -1) { if (iLabel != -1) {
UnfixIntervals([&](const auto &myInterval){ UnfixIntervals([&](const auto &myInterval){
return GetIndex( myInterval ) == iLabel; return GetIndex( myInterval ) == iLabel;

View File

@ -147,10 +147,10 @@ void LabelTrackView::CopyTo( Track &track ) const
auto &other = TrackView::Get( track ); auto &other = TrackView::Get( track );
if ( const auto pOther = dynamic_cast< const LabelTrackView* >( &other ) ) { if ( const auto pOther = dynamic_cast< const LabelTrackView* >( &other ) ) {
pOther->mSelIndex = mSelIndex; pOther->mNavigationIndex = mNavigationIndex;
pOther->mInitialCursorPos = mInitialCursorPos; pOther->mInitialCursorPos = mInitialCursorPos;
pOther->mCurrentCursorPos = mCurrentCursorPos; pOther->mCurrentCursorPos = mCurrentCursorPos;
pOther->mDrawCursor = mDrawCursor; pOther->mTextEditIndex = mTextEditIndex;
pOther->mUndoLabel = mUndoLabel; pOther->mUndoLabel = mUndoLabel;
} }
} }
@ -218,15 +218,24 @@ void LabelTrackView::ResetFlags()
{ {
mInitialCursorPos = 1; mInitialCursorPos = 1;
mCurrentCursorPos = 1; mCurrentCursorPos = 1;
mDrawCursor = false; mTextEditIndex = -1;
mNavigationIndex = -1;
}
LabelTrackView::Flags LabelTrackView::SaveFlags() const
{
return {
mInitialCursorPos, mCurrentCursorPos, mNavigationIndex,
mTextEditIndex, mUndoLabel
};
} }
void LabelTrackView::RestoreFlags( const Flags& flags ) void LabelTrackView::RestoreFlags( const Flags& flags )
{ {
mInitialCursorPos = flags.mInitialCursorPos; mInitialCursorPos = flags.mInitialCursorPos;
mCurrentCursorPos = flags.mCurrentCursorPos; mCurrentCursorPos = flags.mCurrentCursorPos;
mSelIndex = flags.mSelIndex; mNavigationIndex = flags.mNavigationIndex;
mDrawCursor = flags.mDrawCursor; mTextEditIndex = flags.mTextEditIndex;
} }
wxFont LabelTrackView::GetFont(const wxString &faceName, int size) wxFont LabelTrackView::GetFont(const wxString &faceName, int size)
@ -705,7 +714,7 @@ void getXPos( const LabelStruct &ls, wxDC & dc, int * xPos1, int cursorPos)
bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const
{ {
if ( HasSelection( project ) ) { if (IsValidIndex(mTextEditIndex, project)) {
wxMemoryDC dc; wxMemoryDC dc;
if (msFont.Ok()) { if (msFont.Ok()) {
@ -715,7 +724,7 @@ bool LabelTrackView::CalcCursorX( AudacityProject &project, int * x) const
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
getXPos(mLabels[mSelIndex], dc, x, mCurrentCursorPos); getXPos(mLabels[mTextEditIndex], dc, x, mCurrentCursorPos);
*x += mIconWidth / 2; *x += mIconWidth / 2;
return true; return true;
} }
@ -737,7 +746,7 @@ void LabelTrackView::CalcHighlightXs(int *x1, int *x2) const
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
const auto &labelStruct = mLabels[mSelIndex]; const auto &labelStruct = mLabels[mTextEditIndex];
// find the left X pos of highlighted area // find the left X pos of highlighted area
getXPos(labelStruct, dc, x1, pos1); getXPos(labelStruct, dc, x1, pos1);
@ -850,44 +859,47 @@ void LabelTrackView::Draw
#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
highlight = highlightTrack && target->GetLabelNum() == i; highlight = highlightTrack && target->GetLabelNum() == i;
#endif #endif
bool selected = GetSelectedIndex( project ) == i;
dc.SetBrush(pHit && pHit->mMouseOverLabel == i dc.SetBrush(mNavigationIndex == i || (pHit && pHit->mMouseOverLabel == i)
? AColor::labelTextEditBrush : AColor::labelTextNormalBrush); ? AColor::labelTextEditBrush : AColor::labelTextNormalBrush);
DrawBar(dc, labelStruct, r); DrawBar(dc, labelStruct, r);
if( selected ) bool selected = mTextEditIndex == i;
dc.SetBrush( AColor::labelTextEditBrush );
else if ( highlight ) if (selected)
dc.SetBrush( AColor::uglyBrush ); dc.SetBrush(AColor::labelTextEditBrush);
DrawTextBox( dc, labelStruct, r ); else if (highlight)
dc.SetBrush(AColor::uglyBrush);
else
dc.SetBrush(AColor::labelTextNormalBrush);
DrawTextBox(dc, labelStruct, r);
if (highlight || selected)
dc.SetBrush(AColor::labelTextNormalBrush); dc.SetBrush(AColor::labelTextNormalBrush);
} }
} }
// Draw highlights // Draw highlights
if ( (mInitialCursorPos != mCurrentCursorPos) && HasSelection( project ) ) if ( (mInitialCursorPos != mCurrentCursorPos) && IsValidIndex(mTextEditIndex, project))
{ {
int xpos1, xpos2; int xpos1, xpos2;
CalcHighlightXs(&xpos1, &xpos2); CalcHighlightXs(&xpos1, &xpos2);
DrawHighlight(dc, mLabels[mSelIndex], DrawHighlight(dc, mLabels[mTextEditIndex],
xpos1, xpos2, dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent); xpos1, xpos2, dc.GetFontMetrics().ascent + dc.GetFontMetrics().descent);
} }
// Draw the text and the label boxes. // Draw the text and the label boxes.
{ int i = -1; for (const auto &labelStruct : mLabels) { ++i; { int i = -1; for (const auto &labelStruct : mLabels) { ++i;
if( GetSelectedIndex( project ) == i ) if(mTextEditIndex == i )
dc.SetBrush(AColor::labelTextEditBrush); dc.SetBrush(AColor::labelTextEditBrush);
DrawText( dc, labelStruct, r ); DrawText( dc, labelStruct, r );
if( GetSelectedIndex( project ) == i ) if(mTextEditIndex == i )
dc.SetBrush(AColor::labelTextNormalBrush); dc.SetBrush(AColor::labelTextNormalBrush);
}} }}
// Draw the cursor, if there is one. // Draw the cursor, if there is one.
if( mDrawCursor && HasSelection( project ) ) if(mInitialCursorPos == mCurrentCursorPos && IsValidIndex(mTextEditIndex, project))
{ {
const auto &labelStruct = mLabels[mSelIndex]; const auto &labelStruct = mLabels[mTextEditIndex];
int xPos = labelStruct.xText; int xPos = labelStruct.xText;
if( mCurrentCursorPos > 0) if( mCurrentCursorPos > 0)
@ -918,17 +930,9 @@ void LabelTrackView::Draw(
CommonTrackView::Draw( context, rect, iPass ); CommonTrackView::Draw( context, rect, iPass );
} }
void LabelTrackView::SetSelectedIndex( int index )
{
if ( index >= 0 && index < (int)FindLabelTrack()->GetLabels().size() )
mSelIndex = index;
else
mSelIndex = -1;
}
/// uses GetTextExtent to find the character position /// uses GetTextExtent to find the character position
/// corresponding to the x pixel position. /// corresponding to the x pixel position.
int LabelTrackView::FindCursorPosition(wxCoord xPos) int LabelTrackView::FindCursorPosition(int labelIndex, wxCoord xPos)
{ {
int result = -1; int result = -1;
wxMemoryDC dc; wxMemoryDC dc;
@ -945,7 +949,7 @@ int LabelTrackView::FindCursorPosition(wxCoord xPos)
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
const auto &labelStruct = mLabels[mSelIndex]; const auto &labelStruct = mLabels[labelIndex];
const auto &title = labelStruct.title; const auto &title = labelStruct.title;
const int length = title.length(); const int length = title.length();
while (!finished && (charIndex < length + 1)) while (!finished && (charIndex < length + 1))
@ -986,13 +990,33 @@ void LabelTrackView::SetCurrentCursorPosition(int pos)
{ {
mCurrentCursorPos = pos; mCurrentCursorPos = pos;
} }
void LabelTrackView::SetTextSelection(int labelIndex, int start, int end)
void LabelTrackView::SetTextHighlight(
int initialPosition, int currentPosition )
{ {
mInitialCursorPos = initialPosition; mTextEditIndex = labelIndex;
mCurrentCursorPos = currentPosition; mInitialCursorPos = start;
mDrawCursor = true; mCurrentCursorPos = end;
}
int LabelTrackView::GetTextEditIndex(AudacityProject& project) const
{
if (IsValidIndex(mTextEditIndex, project))
return mTextEditIndex;
return -1;
}
void LabelTrackView::ResetTextSelection()
{
mTextEditIndex = -1;
mCurrentCursorPos = 1;
mInitialCursorPos = 1;
}
void LabelTrackView::SetNavigationIndex(int index)
{
mNavigationIndex = index;
}
int LabelTrackView::GetNavigationIndex(AudacityProject& project) const
{
if (IsValidIndex(mNavigationIndex, project))
return mNavigationIndex;
return -1;
} }
void LabelTrackView::calculateFontHeight(wxDC & dc) void LabelTrackView::calculateFontHeight(wxDC & dc)
@ -1013,13 +1037,20 @@ void LabelTrackView::calculateFontHeight(wxDC & dc)
mFontHeight += CursorExtraHeight - (charLeading+charDescent); mFontHeight += CursorExtraHeight - (charLeading+charDescent);
} }
bool LabelTrackView::IsValidIndex(const Index& index, AudacityProject& project) const
{
if (index == -1)
return false;
// may make delayed update of mutable mSelIndex after track selection change
auto track = FindLabelTrack();
if (track->GetSelected() || (TrackFocus::Get(project).Get() == track.get()))
return index >= 0 && index < static_cast<int>(track->GetLabels().size());
return false;
}
bool LabelTrackView::IsTextSelected( AudacityProject &project ) const bool LabelTrackView::IsTextSelected( AudacityProject &project ) const
{ {
if ( !HasSelection( project ) ) return mCurrentCursorPos != mInitialCursorPos && IsValidIndex(mTextEditIndex, project);
return false;
if (mCurrentCursorPos == mInitialCursorPos)
return false;
return true;
} }
/// Cut the selected text in the text box /// Cut the selected text in the text box
@ -1033,10 +1064,10 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
wxString left, right; wxString left, right;
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &text = labelStruct.title; auto &text = labelStruct.title;
if (!mSelIndex.IsModified()) { if (!mTextEditIndex.IsModified()) {
mUndoLabel = text; mUndoLabel = text;
} }
@ -1059,7 +1090,7 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
// set title to the combination of the two remainders // set title to the combination of the two remainders
text = left + right; text = left + right;
pTrack->SetLabel( mSelIndex, labelStruct ); pTrack->SetLabel( mTextEditIndex, labelStruct );
// copy data onto clipboard // copy data onto clipboard
if (wxTheClipboard->Open()) { if (wxTheClipboard->Open()) {
@ -1071,7 +1102,7 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
// set cursor positions // set cursor positions
mInitialCursorPos = mCurrentCursorPos = left.length(); mInitialCursorPos = mCurrentCursorPos = left.length();
mSelIndex.SetModified(true); mTextEditIndex.SetModified(true);
return true; return true;
} }
@ -1079,13 +1110,13 @@ bool LabelTrackView::CutSelectedText( AudacityProject &project )
/// @return true if text is selected in text box, false otherwise /// @return true if text is selected in text box, false otherwise
bool LabelTrackView::CopySelectedText( AudacityProject &project ) bool LabelTrackView::CopySelectedText( AudacityProject &project )
{ {
if ( !HasSelection( project ) ) if (!IsTextSelected(project))
return false; return false;
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
const auto &labelStruct = mLabels[mSelIndex]; const auto &labelStruct = mLabels[mTextEditIndex];
int init = mInitialCursorPos; int init = mInitialCursorPos;
int cur = mCurrentCursorPos; int cur = mCurrentCursorPos;
@ -1116,8 +1147,8 @@ bool LabelTrackView::PasteSelectedText(
{ {
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
if ( !HasSelection( project ) ) if (!IsValidIndex(mTextEditIndex, project))
AddLabel(SelectedRegion(sel0, sel1)); SetTextSelection(AddLabel(SelectedRegion(sel0, sel1)));
wxString text, left, right; wxString text, left, right;
@ -1130,7 +1161,7 @@ bool LabelTrackView::PasteSelectedText(
text = data.GetText(); text = data.GetText();
} }
if (!mSelIndex.IsModified()) { if (!mTextEditIndex.IsModified()) {
mUndoLabel = text; mUndoLabel = text;
} }
@ -1143,7 +1174,7 @@ bool LabelTrackView::PasteSelectedText(
} }
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title; auto &title = labelStruct.title;
int cur = mCurrentCursorPos, init = mInitialCursorPos; int cur = mCurrentCursorPos, init = mInitialCursorPos;
if (init > cur) if (init > cur)
@ -1154,11 +1185,11 @@ bool LabelTrackView::PasteSelectedText(
title = left + text + right; title = left + text + right;
pTrack->SetLabel( mSelIndex, labelStruct ); pTrack->SetLabel(mTextEditIndex, labelStruct );
mInitialCursorPos = mCurrentCursorPos = left.length() + text.length(); mInitialCursorPos = mCurrentCursorPos = left.length() + text.length();
mSelIndex.SetModified(true); mTextEditIndex.SetModified(true);
return true; return true;
} }
@ -1168,20 +1199,6 @@ bool LabelTrackView::IsTextClipSupported()
return wxTheClipboard->IsSupported(wxDF_UNICODETEXT); return wxTheClipboard->IsSupported(wxDF_UNICODETEXT);
} }
int LabelTrackView::GetSelectedIndex( AudacityProject &project ) const
{
// may make delayed update of mutable mSelIndex after track selection change
auto track = FindLabelTrack();
if ( track->GetSelected() ||
TrackFocus::Get( project ).Get() == track.get()
)
return mSelIndex = std::max( -1,
std::min<int>( track->GetLabels().size() - 1, mSelIndex ) );
else
return mSelIndex = -1;
}
/// TODO: Investigate what happens with large /// TODO: Investigate what happens with large
/// numbers of labels, might need a binary search /// numbers of labels, might need a binary search
/// rather than a linear one. /// rather than a linear one.
@ -1331,7 +1348,7 @@ bool LabelTrackView::DoCaptureKey(
!mLabels.empty()) !mLabels.empty())
return true; return true;
if ( HasSelection( project ) ) { if (IsValidIndex(mTextEditIndex, project) || IsValidIndex(mNavigationIndex, project)) {
if (IsGoodLabelEditKey(event)) { if (IsGoodLabelEditKey(event)) {
return true; return true;
} }
@ -1390,10 +1407,10 @@ unsigned LabelTrackView::KeyDown(
double bkpSel0 = viewInfo.selectedRegion.t0(), double bkpSel0 = viewInfo.selectedRegion.t0(),
bkpSel1 = viewInfo.selectedRegion.t1(); bkpSel1 = viewInfo.selectedRegion.t1();
if (!mSelIndex.IsModified() && HasSelection( *project )) { if (IsValidIndex(mTextEditIndex, *project) && !mTextEditIndex.IsModified()) {
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title; auto &title = labelStruct.title;
mUndoLabel = title; mUndoLabel = title;
} }
@ -1403,12 +1420,12 @@ unsigned LabelTrackView::KeyDown(
if (DoKeyDown( *project, viewInfo.selectedRegion, event )) { if (DoKeyDown( *project, viewInfo.selectedRegion, event )) {
ProjectHistory::Get( *project ).PushState(XO("Modified Label"), ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
XO("Label Edit"), XO("Label Edit"),
mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE); mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
mSelIndex.SetModified(true); mTextEditIndex.SetModified(true);
} }
if (!mSelIndex.IsModified()) { if (!mTextEditIndex.IsModified()) {
mUndoLabel.clear(); mUndoLabel.clear();
} }
@ -1436,10 +1453,10 @@ unsigned LabelTrackView::Char(
// Pass keystroke to labeltrack's handler and add to history if any // Pass keystroke to labeltrack's handler and add to history if any
// updates were done // updates were done
if (!mSelIndex.IsModified() && HasSelection( *project )) { if (IsValidIndex(mTextEditIndex, *project) && !mTextEditIndex.IsModified()) {
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title; auto &title = labelStruct.title;
mUndoLabel = title; mUndoLabel = title;
} }
@ -1447,12 +1464,12 @@ unsigned LabelTrackView::Char(
if (DoChar( *project, viewInfo.selectedRegion, event )) { if (DoChar( *project, viewInfo.selectedRegion, event )) {
ProjectHistory::Get( *project ).PushState(XO("Modified Label"), ProjectHistory::Get( *project ).PushState(XO("Modified Label"),
XO("Label Edit"), XO("Label Edit"),
mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE); mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
mSelIndex.SetModified(true); mTextEditIndex.SetModified(true);
} }
if (!mSelIndex.IsModified()) { if (!mTextEditIndex.IsModified()) {
mUndoLabel.clear(); mUndoLabel.clear();
} }
@ -1487,8 +1504,8 @@ bool LabelTrackView::DoKeyDown(
// All editing keys are only active if we're currently editing a label // All editing keys are only active if we're currently editing a label
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
if ( HasSelection( project ) ) { if (IsValidIndex(mTextEditIndex, project)) {
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title; auto &title = labelStruct.title;
wxUniChar wchar; wxUniChar wchar;
bool more=true; bool more=true;
@ -1513,7 +1530,7 @@ bool LabelTrackView::DoKeyDown(
title.erase(mCurrentCursorPos-1, 1); title.erase(mCurrentCursorPos-1, 1);
mCurrentCursorPos--; mCurrentCursorPos--;
if( ((int)wchar > 0xDFFF) || ((int)wchar <0xDC00)){ if( ((int)wchar > 0xDFFF) || ((int)wchar <0xDC00)){
pTrack->SetLabel(mSelIndex, labelStruct); pTrack->SetLabel(mTextEditIndex, labelStruct);
more = false; more = false;
} }
} }
@ -1522,7 +1539,8 @@ bool LabelTrackView::DoKeyDown(
else else
{ {
// ELSE no text in text box, so DELETE whole label. // ELSE no text in text box, so DELETE whole label.
pTrack->DeleteLabel( mSelIndex ); pTrack->DeleteLabel(mTextEditIndex);
ResetTextSelection();
} }
mInitialCursorPos = mCurrentCursorPos; mInitialCursorPos = mCurrentCursorPos;
updated = true; updated = true;
@ -1547,7 +1565,7 @@ bool LabelTrackView::DoKeyDown(
wchar = title.at( mCurrentCursorPos ); wchar = title.at( mCurrentCursorPos );
title.erase(mCurrentCursorPos, 1); title.erase(mCurrentCursorPos, 1);
if( ((int)wchar > 0xDBFF) || ((int)wchar <0xD800)){ if( ((int)wchar > 0xDBFF) || ((int)wchar <0xD800)){
pTrack->SetLabel(mSelIndex, labelStruct); pTrack->SetLabel(mTextEditIndex, labelStruct);
more = false; more = false;
} }
} }
@ -1556,7 +1574,8 @@ bool LabelTrackView::DoKeyDown(
else else
{ {
// DELETE whole label if no text in text box // DELETE whole label if no text in text box
pTrack->DeleteLabel( mSelIndex ); pTrack->DeleteLabel(mTextEditIndex);
ResetTextSelection();
} }
mInitialCursorPos = mCurrentCursorPos; mInitialCursorPos = mCurrentCursorPos;
updated = true; updated = true;
@ -1616,17 +1635,18 @@ bool LabelTrackView::DoKeyDown(
break; break;
case WXK_ESCAPE: case WXK_ESCAPE:
if (mSelIndex.IsModified()) { if (mTextEditIndex.IsModified()) {
title = mUndoLabel; title = mUndoLabel;
pTrack->SetLabel(mSelIndex, labelStruct); pTrack->SetLabel(mTextEditIndex, labelStruct);
ProjectHistory::Get( project ).PushState(XO("Modified Label"), ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"), XO("Label Edit"),
mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE); mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
} }
case WXK_RETURN: case WXK_RETURN:
case WXK_NUMPAD_ENTER: case WXK_NUMPAD_ENTER:
case WXK_TAB:
if (mRestoreFocus >= 0) { if (mRestoreFocus >= 0) {
auto track = *TrackList::Get( project ).Any() auto track = *TrackList::Get( project ).Any()
.begin().advance(mRestoreFocus); .begin().advance(mRestoreFocus);
@ -1634,27 +1654,9 @@ bool LabelTrackView::DoKeyDown(
TrackFocus::Get( project ).Set(track); TrackFocus::Get( project ).Set(track);
mRestoreFocus = -2; mRestoreFocus = -2;
} }
mSelIndex = -1; SetNavigationIndex(mTextEditIndex);
ResetTextSelection();
break; break;
case WXK_TAB:
case WXK_NUMPAD_TAB:
if (event.ShiftDown()) {
--mSelIndex;
} else {
++mSelIndex;
}
mSelIndex = (mSelIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
{
const auto &newLabel = mLabels[mSelIndex];
mCurrentCursorPos = newLabel.title.length();
mInitialCursorPos = mCurrentCursorPos;
//Set the selection region to be equal to the selection bounds of the tabbed-to label.
newSel = newLabel.selectedRegion;
}
break;
case '\x10': // OSX case '\x10': // OSX
case WXK_MENU: case WXK_MENU:
case WXK_WINDOWS_MENU: case WXK_WINDOWS_MENU:
@ -1676,37 +1678,61 @@ bool LabelTrackView::DoKeyDown(
case WXK_NUMPAD_TAB: case WXK_NUMPAD_TAB:
if (!mLabels.empty()) { if (!mLabels.empty()) {
int len = (int) mLabels.size(); int len = (int) mLabels.size();
if (IsValidIndex(mNavigationIndex, project))
{
if (event.ShiftDown()) { if (event.ShiftDown()) {
mSelIndex = len - 1; --mNavigationIndex;
}
else {
++mNavigationIndex;
}
mNavigationIndex = (mNavigationIndex + (int)mLabels.size()) % (int)mLabels.size(); // wrap round if necessary
}
else
{
// no valid navigation index, then
if (event.ShiftDown()) {
//search for the first label starting from the end (and before selection)
mNavigationIndex = len - 1;
if (newSel.t0() > mLabels[0].getT0()) { if (newSel.t0() > mLabels[0].getT0()) {
while (mSelIndex >= 0 && while (mNavigationIndex >= 0 &&
mLabels[mSelIndex].getT0() > newSel.t0()) { mLabels[mNavigationIndex].getT0() > newSel.t0()) {
--mSelIndex; --mNavigationIndex;
} }
} }
} else { }
mSelIndex = 0; else {
//search for the first label starting from the beginning (and after selection)
mNavigationIndex = 0;
if (newSel.t0() < mLabels[len - 1].getT0()) { if (newSel.t0() < mLabels[len - 1].getT0()) {
while (mSelIndex < len && while (mNavigationIndex < len &&
mLabels[mSelIndex].getT0() < newSel.t0()) { mLabels[mNavigationIndex].getT0() < newSel.t0()) {
++mSelIndex; ++mNavigationIndex;
}
} }
} }
} }
if (mSelIndex >= 0 && mSelIndex < len) { if (mNavigationIndex >= 0 && mNavigationIndex < len) {
const auto &labelStruct = mLabels[mSelIndex]; const auto &labelStruct = mLabels[mNavigationIndex];
mCurrentCursorPos = labelStruct.title.length(); mCurrentCursorPos = labelStruct.title.length();
mInitialCursorPos = mCurrentCursorPos; mInitialCursorPos = mCurrentCursorPos;
//Set the selection region to be equal to the selection bounds of the tabbed-to label. //Set the selection region to be equal to the selection bounds of the tabbed-to label.
newSel = labelStruct.selectedRegion; newSel = labelStruct.selectedRegion;
} }
else { else {
mSelIndex = -1; mNavigationIndex = -1;
} }
} }
break; break;
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
//pressing Enter key activates editing of the label
//pointed to by mNavigationIndex (if valid)
if (IsValidIndex(mNavigationIndex, project)) {
SetTextSelection(mNavigationIndex);
}
break;
default: default:
if (!IsGoodLabelFirstKey(event)) { if (!IsGoodLabelFirstKey(event)) {
event.Skip(); event.Skip();
@ -1715,9 +1741,6 @@ bool LabelTrackView::DoKeyDown(
} }
} }
// Make sure the caret is visible
mDrawCursor = true;
return updated; return updated;
} }
@ -1738,7 +1761,7 @@ bool LabelTrackView::DoChar(
} }
// Only track true changes to the label // Only track true changes to the label
bool updated = false; //bool updated = false;
// Cache the character // Cache the character
wxChar charCode = event.GetUnicodeKey(); wxChar charCode = event.GetUnicodeKey();
@ -1751,7 +1774,7 @@ bool LabelTrackView::DoChar(
// If we've reached this point and aren't currently editing, add NEW label // If we've reached this point and aren't currently editing, add NEW label
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
if ( !HasSelection( project ) ) { if (!IsValidIndex(mTextEditIndex, project)) {
// Don't create a NEW label for a space // Don't create a NEW label for a space
if (wxIsspace(charCode)) { if (wxIsspace(charCode)) {
event.Skip(); event.Skip();
@ -1781,6 +1804,9 @@ bool LabelTrackView::DoChar(
} }
} }
if (!IsValidIndex(mTextEditIndex, project))
return false;
// //
// Now we are definitely in a label; append the incoming character // Now we are definitely in a label; append the incoming character
// //
@ -1790,7 +1816,7 @@ bool LabelTrackView::DoChar(
RemoveSelectedText(); RemoveSelectedText();
const auto& mLabels = pTrack->GetLabels(); const auto& mLabels = pTrack->GetLabels();
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto& title = labelStruct.title; auto& title = labelStruct.title;
if (mCurrentCursorPos < (int)title.length()) { if (mCurrentCursorPos < (int)title.length()) {
@ -1807,16 +1833,12 @@ bool LabelTrackView::DoChar(
//append charCode //append charCode
title += charCode; title += charCode;
pTrack->SetLabel( mSelIndex, labelStruct ); pTrack->SetLabel(mTextEditIndex, labelStruct );
//moving cursor position forward //moving cursor position forward
mInitialCursorPos = ++mCurrentCursorPos; mInitialCursorPos = ++mCurrentCursorPos;
updated = true;
// Make sure the caret is visible return true;
mDrawCursor = true;
return updated;
} }
enum enum
@ -1856,13 +1878,12 @@ void LabelTrackView::ShowContextMenu( AudacityProject &project )
menu.Enable(OnDeleteSelectedLabelID, true); menu.Enable(OnDeleteSelectedLabelID, true);
menu.Enable(OnEditSelectedLabelID, true); menu.Enable(OnEditSelectedLabelID, true);
if( !HasSelection( project ) ) { if(!IsValidIndex(mTextEditIndex, project)) {
wxASSERT( false );
return; return;
} }
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const LabelStruct *ls = pTrack->GetLabel(mSelIndex); const LabelStruct *ls = pTrack->GetLabel(mTextEditIndex);
wxClientDC dc(parent); wxClientDC dc(parent);
@ -1902,7 +1923,7 @@ void LabelTrackView::OnContextMenu(
{ {
ProjectHistory::Get( project ).PushState(XO("Modified Label"), ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"), XO("Label Edit"),
mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE); mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
} }
break; break;
@ -1918,17 +1939,16 @@ void LabelTrackView::OnContextMenu(
{ {
ProjectHistory::Get( project ).PushState(XO("Modified Label"), ProjectHistory::Get( project ).PushState(XO("Modified Label"),
XO("Label Edit"), XO("Label Edit"),
mSelIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE); mTextEditIndex.IsModified() ? UndoPush::CONSOLIDATE : UndoPush::NONE);
} }
break; break;
/// DELETE selected label /// DELETE selected label
case OnDeleteSelectedLabelID: { case OnDeleteSelectedLabelID: {
int ndx = GetLabelIndex(selectedRegion.t0(), selectedRegion.t1()); if (IsValidIndex(mTextEditIndex, project))
if (ndx != -1)
{ {
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
pTrack->DeleteLabel(ndx); pTrack->DeleteLabel(mTextEditIndex);
ProjectHistory::Get( project ).PushState(XO("Deleted Label"), ProjectHistory::Get( project ).PushState(XO("Deleted Label"),
XO("Label Edit"), XO("Label Edit"),
UndoPush::CONSOLIDATE); UndoPush::CONSOLIDATE);
@ -1938,7 +1958,8 @@ void LabelTrackView::OnContextMenu(
case OnEditSelectedLabelID: { case OnEditSelectedLabelID: {
// Bug #2571: See above // Bug #2571: See above
mEditIndex = GetLabelIndex(selectedRegion.t0(), selectedRegion.t1()); if (IsValidIndex(mTextEditIndex, project))
mEditIndex = mTextEditIndex;
} }
break; break;
} }
@ -1955,7 +1976,7 @@ void LabelTrackView::RemoveSelectedText()
const auto pTrack = FindLabelTrack(); const auto pTrack = FindLabelTrack();
const auto &mLabels = pTrack->GetLabels(); const auto &mLabels = pTrack->GetLabels();
auto labelStruct = mLabels[mSelIndex]; auto labelStruct = mLabels[mTextEditIndex];
auto &title = labelStruct.title; auto &title = labelStruct.title;
if (init > 0) if (init > 0)
@ -1965,16 +1986,16 @@ void LabelTrackView::RemoveSelectedText()
right = title.Mid(cur); right = title.Mid(cur);
title = left + right; title = left + right;
pTrack->SetLabel( mSelIndex, labelStruct ); pTrack->SetLabel( mTextEditIndex, labelStruct );
mInitialCursorPos = mCurrentCursorPos = left.length(); mInitialCursorPos = mCurrentCursorPos = left.length();
} }
/*
bool LabelTrackView::HasSelection( AudacityProject &project ) const bool LabelTrackView::HasSelectedLabel( AudacityProject &project ) const
{ {
const auto selIndex = GetSelectedIndex( project ); const auto selIndex = GetSelectionIndex( project );
return (selIndex >= 0 && return (selIndex >= 0 &&
selIndex < (int)FindLabelTrack()->GetLabels().size()); selIndex < (int)FindLabelTrack()->GetLabels().size());
} }*/
int LabelTrackView::GetLabelIndex(double t, double t1) int LabelTrackView::GetLabelIndex(double t, double t1)
{ {
@ -2026,21 +2047,10 @@ void LabelTrackView::OnLabelAdded( LabelTrackEvent &e )
// -1 means we don't need to restore it to anywhere. // -1 means we don't need to restore it to anywhere.
// 0 or above is the track to restore to after editing the label is complete. // 0 or above is the track to restore to after editing the label is complete.
if( mRestoreFocus >= -1 ) if( mRestoreFocus >= -1 )
mSelIndex = pos; mTextEditIndex = pos;
if( mRestoreFocus < 0 ) if( mRestoreFocus < 0 )
mRestoreFocus = -2; mRestoreFocus = -2;
// Make sure the caret is visible
//
// LLL: The cursor will not be drawn when the first label
// is added since mDrawCursor will be false. Presumably,
// if the user adds a label, then a cursor should be drawn
// to indicate that typing is expected.
//
// If the label is added during actions like import, then the
// mDrawCursor flag will be reset once the action is complete.
mDrawCursor = true;
} }
void LabelTrackView::OnLabelDeleted( LabelTrackEvent &e ) void LabelTrackView::OnLabelDeleted( LabelTrackEvent &e )
@ -2053,17 +2063,13 @@ void LabelTrackView::OnLabelDeleted( LabelTrackEvent &e )
// IF we've deleted the selected label // IF we've deleted the selected label
// THEN set no label selected. // THEN set no label selected.
if( mSelIndex== index ) if (mTextEditIndex == index)
{ ResetTextSelection();
mSelIndex = -1;
mCurrentCursorPos = 1;
}
// IF we removed a label before the selected label // IF we removed a label before the selected label
// THEN the NEW selected label number is one less. // THEN the NEW selected label number is one less.
else if( index < mSelIndex ) else if( index < mTextEditIndex)
{ --mTextEditIndex;//NB: Keep cursor selection region
--mSelIndex;
}
} }
void LabelTrackView::OnLabelPermuted( LabelTrackEvent &e ) void LabelTrackView::OnLabelPermuted( LabelTrackEvent &e )
@ -2075,12 +2081,16 @@ void LabelTrackView::OnLabelPermuted( LabelTrackEvent &e )
auto former = e.mFormerPosition; auto former = e.mFormerPosition;
auto present = e.mPresentPosition; auto present = e.mPresentPosition;
if ( mSelIndex == former ) auto fix = [&](Index& index) {
mSelIndex = present; if (index == former)
else if ( former < mSelIndex && mSelIndex <= present ) index = present;
-- mSelIndex; else if (former < index && index <= present)
else if ( former > mSelIndex && mSelIndex >= present ) --index;
++ mSelIndex; else if (former > index && index >= present)
++index;
};
fix(mNavigationIndex);
fix(mTextEditIndex);
} }
void LabelTrackView::OnSelectionChange( LabelTrackEvent &e ) void LabelTrackView::OnSelectionChange( LabelTrackEvent &e )
@ -2089,8 +2099,11 @@ void LabelTrackView::OnSelectionChange( LabelTrackEvent &e )
if ( e.mpTrack.lock() != FindTrack() ) if ( e.mpTrack.lock() != FindTrack() )
return; return;
if ( !FindTrack()->GetSelected() ) if (!FindTrack()->GetSelected())
mSelIndex = -1; {
SetNavigationIndex(-1);
ResetTextSelection();
}
} }
wxBitmap & LabelTrackView::GetGlyph( int i) wxBitmap & LabelTrackView::GetGlyph( int i)

View File

@ -112,9 +112,6 @@ public:
void Draw( TrackPanelDrawingContext &context, const wxRect & r ) const; void Draw( TrackPanelDrawingContext &context, const wxRect & r ) const;
int GetSelectedIndex( AudacityProject &project ) const;
void SetSelectedIndex( int index );
bool CutSelectedText( AudacityProject &project ); bool CutSelectedText( AudacityProject &project );
bool CopySelectedText( AudacityProject &project ); bool CopySelectedText( AudacityProject &project );
bool PasteSelectedText( bool PasteSelectedText(
@ -146,19 +143,13 @@ private:
public: public:
struct Flags { struct Flags {
int mInitialCursorPos, mCurrentCursorPos; int mInitialCursorPos, mCurrentCursorPos;
Index mSelIndex{-1}; Index mNavigationIndex;
bool mDrawCursor; Index mTextEditIndex;
wxString mUndoLabel; wxString mUndoLabel;
}; };
void ResetFlags(); void ResetFlags();
Flags SaveFlags() const Flags SaveFlags() const;
{
return {
mInitialCursorPos, mCurrentCursorPos, mSelIndex,
mDrawCursor, mUndoLabel
};
}
void RestoreFlags( const Flags& flags ); void RestoreFlags( const Flags& flags );
static int OverATextBox( const LabelTrack &track, int xx, int yy ); static int OverATextBox( const LabelTrack &track, int xx, int yy );
@ -190,7 +181,11 @@ public:
private: private:
void OnContextMenu( AudacityProject &project, wxCommandEvent & evt); void OnContextMenu( AudacityProject &project, wxCommandEvent & evt);
mutable Index mSelIndex{-1}; /// Keeps track of the currently selected label /// Keeps track of the currently selected label (not same as selection region)
/// used for navigation between labels
mutable Index mNavigationIndex{ -1 };
/// Index of the current label text beeing edited
mutable Index mTextEditIndex{ -1 };
mutable wxString mUndoLabel; mutable wxString mUndoLabel;
@ -205,8 +200,7 @@ private:
mutable int mCurrentCursorPos; /// current cursor position mutable int mCurrentCursorPos; /// current cursor position
mutable int mInitialCursorPos; /// initial cursor position mutable int mInitialCursorPos; /// initial cursor position
mutable bool mDrawCursor; /// flag to tell if drawing the
/// cursor or not
int mRestoreFocus{-2}; /// Restore focus to this track int mRestoreFocus{-2}; /// Restore focus to this track
/// when done editing /// when done editing
@ -224,11 +218,19 @@ private:
public: public:
/// convert pixel coordinate to character position in text box /// convert pixel coordinate to character position in text box
int FindCursorPosition(wxCoord xPos); int FindCursorPosition(int labelIndex, wxCoord xPos);
int GetCurrentCursorPosition() const { return mCurrentCursorPos; } int GetCurrentCursorPosition() const { return mCurrentCursorPos; }
void SetCurrentCursorPosition(int pos); void SetCurrentCursorPosition(int pos);
int GetInitialCursorPosition() const { return mInitialCursorPos; } int GetInitialCursorPosition() const { return mInitialCursorPos; }
void SetTextHighlight( int initialPosition, int currentPosition );
/// Sets the label with specified index for editing,
/// optionaly selection may be specified with [start, end]
void SetTextSelection(int labelIndex, int start = 1, int end = 1);
int GetTextEditIndex(AudacityProject& project) const;
void ResetTextSelection();
void SetNavigationIndex(int index);
int GetNavigationIndex(AudacityProject& project) const;
private: private:
@ -239,8 +241,7 @@ private:
static void calculateFontHeight(wxDC & dc); static void calculateFontHeight(wxDC & dc);
public: bool IsValidIndex(const Index& index, AudacityProject& project) const;
bool HasSelection( AudacityProject &project ) const;
private: private:
void RemoveSelectedText(); void RemoveSelectedText();