Addition, deletion, sort of Labels communicated by events...

... and LabelTrack listens to its own events, to update certain state.

This is roundabout for now, but that state is view-related and will move into
another class.
This commit is contained in:
Paul Licameli 2018-12-05 14:10:05 -05:00
parent aa5f9550bd
commit 604fbd0a2c
5 changed files with 183 additions and 40 deletions

View File

@ -76,6 +76,10 @@ for drawing different aspects of the label and its text box.
#include "widgets/AudacityMessageBox.h"
#include "widgets/ErrorDialog.h"
wxDEFINE_EVENT(EVT_LABELTRACK_ADDITION, LabelTrackEvent);
wxDEFINE_EVENT(EVT_LABELTRACK_DELETION, LabelTrackEvent);
wxDEFINE_EVENT(EVT_LABELTRACK_PERMUTED, LabelTrackEvent);
enum
{
OnCutSelectedTextID = 1, // OSX doesn't like a 0 menu id
@ -131,6 +135,10 @@ LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
// reset flags
ResetFlags();
Bind( EVT_LABELTRACK_ADDITION, &LabelTrack::OnLabelAdded, this );
Bind( EVT_LABELTRACK_DELETION, &LabelTrack::OnLabelDeleted, this );
Bind( EVT_LABELTRACK_PERMUTED, &LabelTrack::OnLabelPermuted, this );
}
LabelTrack::LabelTrack(const LabelTrack &orig) :
@ -806,7 +814,7 @@ namespace {
if (target) {
auto handle = dynamic_cast<LabelGlyphHandle*>( target.get() );
if (handle)
return &handle->mHit;
return &*handle->mpHit;
}
return nullptr;
}
@ -1581,7 +1589,7 @@ bool LabelTrack::HandleGlyphDragRelease
//the NEW size of the label.
*newSel = mLabels[mSelIndex].selectedRegion;
}
SortLabels( &hit );
SortLabels();
}
return false;
@ -2802,8 +2810,17 @@ int LabelTrackView::AddLabel(const SelectedRegion &selectedRegion,
return pos;
}
void LabelTrack::OnLabelAdded( const wxString &title, int pos )
void LabelTrack::OnLabelAdded( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock().get() != this )
return;
const auto &title = e.mTitle;
const auto pos = e.mPresentPosition;
mInitialCursorPos = mCurrentCursorPos = title.length();
// restoreFocus is -2 e.g. from Nyquist label creation, when we should not
// even lose the focus and open the label to edit in the first place.
// -1 means we don't need to restore it to anywhere.
@ -2824,8 +2841,6 @@ void LabelTrack::OnLabelAdded( const wxString &title, int pos )
// If the label is added during actions like import, then the
// mDrawCursor flag will be reset once the action is complete.
mDrawCursor = true;
mInitialCursorPos = mCurrentCursorPos = title.length();
}
@ -2842,7 +2857,10 @@ int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
mLabels.insert(mLabels.begin() + pos, l);
OnLabelAdded( title, pos );
// wxWidgets will own the event object
QueueEvent( safenew LabelTrackEvent{
EVT_LABELTRACK_ADDITION, SharedPointer<LabelTrack>(), title, -1, pos
} );
return pos;
}
@ -2850,7 +2868,24 @@ int LabelTrack::AddLabel(const SelectedRegion &selectedRegion,
void LabelTrack::DeleteLabel(int index)
{
wxASSERT((index < (int)mLabels.size()));
mLabels.erase(mLabels.begin() + index);
auto iter = mLabels.begin() + index;
const auto title = iter->title;
mLabels.erase(iter);
// wxWidgets will own the event object
QueueEvent( safenew LabelTrackEvent{
EVT_LABELTRACK_DELETION, SharedPointer<LabelTrack>(), title, index, -1
} );
}
void LabelTrack::OnLabelDeleted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock().get() != this )
return;
auto index = e.mFormerPosition;
// IF we've deleted the selected label
// THEN set no label selected.
if( mSelIndex== index )
@ -2866,6 +2901,23 @@ void LabelTrack::DeleteLabel(int index)
}
}
void LabelTrack::OnLabelPermuted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock().get() != this )
return;
auto former = e.mFormerPosition;
auto present = e.mPresentPosition;
if ( mSelIndex == former )
mSelIndex = present;
else if ( former < mSelIndex && mSelIndex <= present )
-- mSelIndex;
else if ( former > mSelIndex && mSelIndex >= present )
++ mSelIndex;
}
wxBitmap & LabelTrack::GetGlyph( int i)
{
return theTheme.Bitmap( i + bmpLabelGlyph0);
@ -3013,7 +3065,7 @@ static bool IsGoodLabelEditKey(const wxKeyEvent & evt)
/// This function is called often (whilst dragging a label)
/// We expect them to be very nearly in order, so insertion
/// sort (with a linear search) is a reasonable choice.
void LabelTrack::SortLabels( LabelTrackHit *pHit )
void LabelTrack::SortLabels()
{
const auto begin = mLabels.begin();
const auto nn = (int)mLabels.size();
@ -3039,20 +3091,12 @@ void LabelTrack::SortLabels( LabelTrackHit *pHit )
begin + i + 1
);
// Various indices need to be updated with the moved items...
auto update = [=](int &index) {
if( index <= i ) {
if( index == i )
index = j;
else if( index >= j)
++index;
}
};
if ( pHit ) {
update( pHit->mMouseOverLabelLeft );
update( pHit->mMouseOverLabelRight );
}
update(mSelIndex);
// Let listeners update their stored indices
// wxWidgets will own the event object
QueueEvent( safenew LabelTrackEvent{
EVT_LABELTRACK_PERMUTED, SharedPointer<LabelTrack>(),
mLabels[j].title, i, j
} );
}
}

View File

@ -31,6 +31,7 @@ class DirManager;
class TimeWarper;
class ZoomInfo;
class LabelTrackEvent;
struct LabelTrackHit;
struct TrackPanelDrawingContext;
@ -102,7 +103,9 @@ const int NUM_GLYPH_CONFIGS = 3;
const int NUM_GLYPH_HIGHLIGHTS = 4;
const int MAX_NUM_ROWS =80;
class AUDACITY_DLL_API LabelTrack final : public Track
class AUDACITY_DLL_API LabelTrack final
: public Track
, public wxEvtHandler
{
friend class LabelTrackView;
friend class LabelStruct;
@ -253,7 +256,8 @@ public:
int FindPrevLabel(const SelectedRegion& currentSelection);
public:
void SortLabels(LabelTrackHit *pHit = nullptr);
void SortLabels();
private:
TrackKind GetKind() const override { return TrackKind::Label; }
@ -296,6 +300,10 @@ private:
void calculateFontHeight(wxDC & dc) const;
void RemoveSelectedText();
void OnLabelAdded( LabelTrackEvent& );
void OnLabelDeleted( LabelTrackEvent& );
void OnLabelPermuted( LabelTrackEvent& );
static wxFont msFont;
protected:
@ -303,4 +311,44 @@ protected:
std::shared_ptr<TrackControls> DoGetControls() override;
};
struct LabelTrackEvent : TrackListEvent
{
explicit
LabelTrackEvent(
wxEventType commandType, const std::shared_ptr<LabelTrack> &pTrack,
const wxString &title,
int formerPosition,
int presentPosition
)
: TrackListEvent{ commandType, pTrack }
, mTitle{ title }
, mFormerPosition{ formerPosition }
, mPresentPosition{ presentPosition }
{}
LabelTrackEvent( const LabelTrackEvent& ) = default;
wxEvent *Clone() const override {
// wxWidgets will own the event object
return safenew LabelTrackEvent(*this); }
wxString mTitle;
// invalid for addition event
int mFormerPosition{ -1 };
// invalid for deletion event
int mPresentPosition{ -1 };
};
// Posted when a label is added.
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
EVT_LABELTRACK_ADDITION, LabelTrackEvent);
// Posted when a label is deleted.
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
EVT_LABELTRACK_DELETION, LabelTrackEvent);
// Posted when a label is repositioned in the sequence of labels.
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
EVT_LABELTRACK_PERMUTED, LabelTrackEvent);
#endif

View File

@ -1081,7 +1081,9 @@ struct TrackListEvent : public wxCommandEvent
TrackListEvent( const TrackListEvent& ) = default;
wxEvent *Clone() const override { return new TrackListEvent(*this); }
wxEvent *Clone() const override {
// wxWidgets will own the event object
return safenew TrackListEvent(*this); }
std::weak_ptr<Track> mpTrack;
int mCode;

View File

@ -22,10 +22,46 @@ Paul Licameli split from TrackPanel.cpp
#include <wx/cursor.h>
#include <wx/translation.h>
LabelTrackHit::LabelTrackHit( const std::shared_ptr<LabelTrack> &pLT )
: mpLT{ pLT }
{
pLT->Bind(
EVT_LABELTRACK_PERMUTED, &LabelTrackHit::OnLabelPermuted, this );
}
LabelTrackHit::~LabelTrackHit()
{
// Must do this because this sink isn't wxEvtHandler
mpLT->Unbind(
EVT_LABELTRACK_PERMUTED, &LabelTrackHit::OnLabelPermuted, this );
}
void LabelTrackHit::OnLabelPermuted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpLT )
return;
auto former = e.mFormerPosition;
auto present = e.mPresentPosition;
auto update = [=]( int &index ){
if ( index == former )
index = present;
else if ( former < index && index <= present )
-- index;
else if ( former > index && index >= present )
++ index;
};
update( mMouseOverLabelLeft );
update( mMouseOverLabelRight );
}
LabelGlyphHandle::LabelGlyphHandle
(const std::shared_ptr<LabelTrack> &pLT,
const wxRect &rect, const LabelTrackHit &hit)
: mHit{ hit }
const wxRect &rect, const std::shared_ptr<LabelTrackHit> &pHit)
: mpHit{ pHit }
, mpLT{ pLT }
, mRect{ rect }
{
@ -39,7 +75,7 @@ void LabelGlyphHandle::Enter(bool)
UIHandle::Result LabelGlyphHandle::NeedChangeHighlight
(const LabelGlyphHandle &oldState, const LabelGlyphHandle &newState)
{
if (oldState.mHit.mEdge != newState.mHit.mEdge)
if (oldState.mpHit->mEdge != newState.mpHit->mEdge)
// pointer moves between the circle and the chevron
return RefreshCode::RefreshCell;
return 0;
@ -61,14 +97,18 @@ UIHandlePtr LabelGlyphHandle::HitTest
const wxMouseState &state,
const std::shared_ptr<LabelTrack> &pLT, const wxRect &rect)
{
LabelTrackHit hit{};
pLT->OverGlyph(hit, state.m_x, state.m_y);
// Allocate on heap because there are pointers to it when it is bound as
// an event sink, therefore it's not copyable; make it shared so
// LabelGlyphHandle can be copyable:
auto pHit = std::make_shared<LabelTrackHit>( pLT );
pLT->OverGlyph(*pHit, state.m_x, state.m_y);
// IF edge!=0 THEN we've set the cursor and we're done.
// signal this by setting the tip.
if ( hit.mEdge & 3 )
if ( pHit->mEdge & 3 )
{
auto result = std::make_shared<LabelGlyphHandle>( pLT, rect, hit );
auto result = std::make_shared<LabelGlyphHandle>( pLT, rect, pHit );
result = AssignUIHandlePtr(holder, result);
return result;
}
@ -89,9 +129,9 @@ UIHandle::Result LabelGlyphHandle::Click
auto &viewInfo = ViewInfo::Get( *pProject );
mpLT->HandleGlyphClick
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
(*mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
if (! mHit.mIsAdjustingLabel )
if (! mpHit->mIsAdjustingLabel )
{
// The positive hit test should have ensured otherwise
//wxASSERT(false);
@ -119,7 +159,7 @@ UIHandle::Result LabelGlyphHandle::Drag
const wxMouseEvent &event = evt.event;
auto &viewInfo = ViewInfo::Get( *pProject );
mpLT->HandleGlyphDragRelease
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
(*mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
// Refresh all so that the change of selection is redrawn in all tracks
return result | RefreshCode::RefreshAll | RefreshCode::DrawOverlays;
@ -128,7 +168,7 @@ UIHandle::Result LabelGlyphHandle::Drag
HitTestPreview LabelGlyphHandle::Preview
(const TrackPanelMouseState &, const AudacityProject *)
{
return HitPreview( (mHit.mEdge & 4 )!=0);
return HitPreview( (mpHit->mEdge & 4 )!=0);
}
UIHandle::Result LabelGlyphHandle::Release
@ -140,7 +180,7 @@ UIHandle::Result LabelGlyphHandle::Release
const wxMouseEvent &event = evt.event;
auto &viewInfo = ViewInfo::Get( *pProject );
if (mpLT->HandleGlyphDragRelease
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion)) {
(*mpHit, event, mRect, viewInfo, &viewInfo.selectedRegion)) {
ProjectHistory::Get( *pProject ).PushState(_("Modified Label"),
_("Label Edit"),
UndoPush::CONSOLIDATE);

View File

@ -15,6 +15,7 @@ Paul Licameli split from TrackPanel.cpp
class wxMouseState;
class LabelTrack;
class LabelTrackEvent;
/// mEdge:
/// 0 if not over a glyph,
@ -26,12 +27,20 @@ class LabelTrack;
/// mMouseLabelLeft - index of any left label hit
/// mMouseLabelRight - index of any right label hit
///
struct LabelTrackHit {
struct LabelTrackHit
{
LabelTrackHit( const std::shared_ptr<LabelTrack> &pLT );
~LabelTrackHit();
int mEdge{};
int mMouseOverLabelLeft{ -1 }; /// Keeps track of which left label the mouse is currently over.
int mMouseOverLabelRight{ -1 }; /// Keeps track of which right label the mouse is currently over.
bool mbIsMoving {};
bool mIsAdjustingLabel {};
std::shared_ptr<LabelTrack> mpLT {};
void OnLabelPermuted( LabelTrackEvent &e );
};
class LabelGlyphHandle final : public LabelDefaultClickHandle
@ -41,7 +50,7 @@ class LabelGlyphHandle final : public LabelDefaultClickHandle
public:
explicit LabelGlyphHandle
(const std::shared_ptr<LabelTrack> &pLT,
const wxRect &rect, const LabelTrackHit &hit);
const wxRect &rect, const std::shared_ptr<LabelTrackHit> &pHit);
LabelGlyphHandle &operator=(const LabelGlyphHandle&) = default;
@ -72,7 +81,7 @@ public:
bool StopsOnKeystroke() override { return true; }
LabelTrackHit mHit{};
std::shared_ptr<LabelTrackHit> mpHit{};
static UIHandle::Result NeedChangeHighlight
(const LabelGlyphHandle &oldState, const LabelGlyphHandle &newState);