Rename TrackPanel::HandleCursor as HandleMotion...

Call HitTest in just one place

Can now preserve repeatedly hit UIHandle objects during pre-click motion

Fields of HitTestResult besides the handle pointer are now unused

The need to repaint a track during mouse movement can be indicated when
constructing a UIHandle or when updating it for move; HitPreview no longer
does this

And the last allows simplifications of LabelTrack glyph highlighting

Also move the temporary state for label glyph dragging out of LabelTrack
This commit is contained in:
Paul Licameli 2017-06-17 21:37:41 -04:00
parent 2c1a16f593
commit f5b0afc2fc
10 changed files with 336 additions and 210 deletions

View File

@ -24,17 +24,12 @@ struct HitTestPreview
HitTestPreview()
{}
HitTestPreview(const wxString &message_, wxCursor *cursor_
, unsigned refreshCode_ = 0)
: message(message_), cursor(cursor_), refreshCode(refreshCode_)
HitTestPreview(const wxString &message_, wxCursor *cursor_)
: message(message_), cursor(cursor_)
{}
wxString message {};
wxCursor *cursor {};
// Making this non-zero allows mouse-over highlighting
// See RefreshCode.h for bit flags:
unsigned refreshCode {};
};
struct HitTestResult

View File

@ -102,14 +102,9 @@ LabelTrack::Holder TrackFactory::NewLabelTrack()
LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
Track(projDirManager),
mbHitCenter(false),
mOldEdge(-1),
mSelIndex(-1),
mMouseOverLabelLeft(-1),
mMouseOverLabelRight(-1),
mRestoreFocus(-1),
mClipLen(0.0),
mIsAdjustingLabel(false),
miLastLabel(-1)
{
SetDefaultName(_("Label Track"));
@ -129,13 +124,8 @@ LabelTrack::LabelTrack(const std::shared_ptr<DirManager> &projDirManager):
LabelTrack::LabelTrack(const LabelTrack &orig) :
Track(orig),
mbHitCenter(false),
mOldEdge(-1),
mSelIndex(-1),
mMouseOverLabelLeft(-1),
mMouseOverLabelRight(-1),
mClipLen(0.0),
mIsAdjustingLabel(false)
mClipLen(0.0)
{
for (auto &original: orig.mLabels) {
LabelStruct l { original.selectedRegion, original.title };
@ -766,6 +756,23 @@ void LabelTrack::CalcHighlightXs(int *x1, int *x2) const
labelStruct.getXPos(dc, x2, pos2);
}
#include "tracks/labeltrack/ui/LabelGlyphHandle.h"
// TODO: don't rely on the global ::GetActiveProject() to find this.
// Rather, give TrackPanelCell a drawing function and pass context into it.
namespace {
LabelTrackHit *findHit()
{
// Fetch the highlighting state
auto target = GetActiveProject()->GetTrackPanel()->Target();
if (target) {
auto handle = dynamic_cast<LabelGlyphHandle*>( target->handle.get() );
if (handle)
return &handle->mHit;
}
return nullptr;
}
}
/// Draw calls other functions to draw the LabelTrack.
/// @param dc the device context
/// @param r the LabelTrack rectangle.
@ -773,6 +780,8 @@ void LabelTrack::Draw(wxDC & dc, const wxRect & r,
const SelectedRegion &selectedRegion,
const ZoomInfo &zoomInfo) const
{
auto pHit = findHit();
if(msFont.Ok())
dc.SetFont(msFont);
@ -819,10 +828,10 @@ void LabelTrack::Draw(wxDC & dc, const wxRect & r,
{ int i = -1; for (auto &labelStruct : mLabels) { ++i;
GlyphLeft=0;
GlyphRight=1;
if( i==mMouseOverLabelLeft )
GlyphLeft = mbHitCenter ? 6:9;
if( i==mMouseOverLabelRight )
GlyphRight = mbHitCenter ? 7:4;
if( pHit && i == pHit->mMouseOverLabelLeft )
GlyphLeft = (pHit->mEdge & 4) ? 6:9;
if( pHit && i == pHit->mMouseOverLabelRight )
GlyphRight = (pHit->mEdge & 4) ? 7:4;
labelStruct.DrawGlyphs( dc, r, GlyphLeft, GlyphRight );
}}
@ -1119,20 +1128,10 @@ void LabelTrack::SetSelected(bool s)
Unselect();
}
/// OverGlyph returns 0 if not over a glyph,
/// 1 if over the left-hand glyph, and
/// 2 if over the right-hand glyph on a label.
/// 3 if over both right and left.
///
/// It also sets up member variables:
/// mMouseLabelLeft - index of any left label hit
/// mMouseLabelRight - index of any right label hit
/// mbHitCenter - if (x,y) 'hits the spot'.
///
/// TODO: Investigate what happens with large
/// numbers of labels, might need a binary search
/// rather than a linear one.
int LabelTrack::OverGlyph(int x, int y)
void LabelTrack::OverGlyph(LabelTrackHit &hit, int x, int y) const
{
//Determine the NEW selection.
int result=0;
@ -1140,9 +1139,9 @@ int LabelTrack::OverGlyph(int x, int y)
const int d2=5; //distance in pixels, used for have we hit drag handle center.
//If not over a label, reset it
mMouseOverLabelLeft = -1;
mMouseOverLabelRight = -1;
mbHitCenter = false;
hit.mMouseOverLabelLeft = -1;
hit.mMouseOverLabelRight = -1;
hit.mEdge = 0;
{ int i = -1; for (auto &labelStruct : mLabels) { ++i;
//over left or right selection bound
//Check right bound first, since it is drawn after left bound,
@ -1150,16 +1149,16 @@ int LabelTrack::OverGlyph(int x, int y)
if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
abs(labelStruct.x1 - d2 -x) < d1)
{
mMouseOverLabelRight = i;
hit.mMouseOverLabelRight = i;
if(abs(labelStruct.x1 - x) < d2 )
{
mbHitCenter = true;
result |= 4;
// If left and right co-incident at this resolution, then we drag both.
// We could be a little less stringent about co-incidence here if we liked.
if( abs(labelStruct.x1-labelStruct.x) < 1.0 )
{
result |=1;
mMouseOverLabelLeft = i;
hit.mMouseOverLabelLeft = i;
}
}
result |= 2;
@ -1169,9 +1168,9 @@ int LabelTrack::OverGlyph(int x, int y)
else if( abs(labelStruct.y - (y - (LabelTrack::mTextHeight+3)/2)) < d1 &&
abs(labelStruct.x + d2 - x) < d1 )
{
mMouseOverLabelLeft = i;
hit.mMouseOverLabelLeft = i;
if(abs(labelStruct.x - x) < d2 )
mbHitCenter = true;
result |= 4;
result |= 1;
}
@ -1182,7 +1181,7 @@ int LabelTrack::OverGlyph(int x, int y)
}
}}
return result;
hit.mEdge = result;
}
int LabelTrack::OverATextBox(int xx, int yy) const
@ -1403,7 +1402,8 @@ auto LabelStruct::RegionRelation(
/// @iEdge - which edge is requested to move, -1 for left +1 for right.
/// @bAllowSwapping - if we can switch which edge is being dragged.
/// fNewTime - the NEW time for this edge of the label.
void LabelTrack::MayAdjustLabel( int iLabel, int iEdge, bool bAllowSwapping, double fNewTime)
void LabelTrack::MayAdjustLabel
( LabelTrackHit &hit, int iLabel, int iEdge, bool bAllowSwapping, double fNewTime)
{
if( iLabel < 0 )
return;
@ -1424,9 +1424,7 @@ void LabelTrack::MayAdjustLabel( int iLabel, int iEdge, bool bAllowSwapping, dou
}
// Swap our record of what we are dragging.
int Temp = mMouseOverLabelLeft;
mMouseOverLabelLeft = mMouseOverLabelRight;
mMouseOverLabelRight = Temp;
std::swap( hit.mMouseOverLabelLeft, hit.mMouseOverLabelRight );
}
// If the index is for a real label, adjust its left and right boundary.
@ -1450,27 +1448,28 @@ static int Constrain( int value, int min, int max )
return result;
}
bool LabelTrack::HandleGlyphDragRelease(const wxMouseEvent & evt,
wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel)
bool LabelTrack::HandleGlyphDragRelease
(LabelTrackHit &hit, const wxMouseEvent & evt,
wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel)
{
if(evt.LeftUp())
{
bool lupd = false, rupd = false;
if(mMouseOverLabelLeft>=0) {
auto &labelStruct = mLabels[mMouseOverLabelLeft];
if( hit.mMouseOverLabelLeft >= 0 ) {
auto &labelStruct = mLabels[ hit.mMouseOverLabelLeft ];
lupd = labelStruct.updated;
labelStruct.updated = false;
}
if(mMouseOverLabelRight>=0) {
auto &labelStruct = mLabels[mMouseOverLabelRight];
if( hit.mMouseOverLabelRight >= 0 ) {
auto &labelStruct = mLabels[ hit.mMouseOverLabelRight ];
rupd = labelStruct.updated;
labelStruct.updated = false;
}
mIsAdjustingLabel = false;
mMouseOverLabelLeft = -1;
mMouseOverLabelRight = -1;
hit.mIsAdjustingLabel = false;
hit.mMouseOverLabelLeft = -1;
hit.mMouseOverLabelRight = -1;
return lupd || rupd;
}
@ -1483,23 +1482,25 @@ bool LabelTrack::HandleGlyphDragRelease(const wxMouseEvent & evt,
int x = Constrain( evt.m_x + mxMouseDisplacement - r.x, 0, r.width);
// If exactly one edge is selected we allow swapping
bool bAllowSwapping = (mMouseOverLabelLeft >=0 ) ^ ( mMouseOverLabelRight >= 0);
bool bAllowSwapping =
( hit.mMouseOverLabelLeft >=0 ) !=
( hit.mMouseOverLabelRight >= 0);
// If we're on the 'dot' and nowe're moving,
// Though shift-down inverts that.
// and if both edges the same, then we're always moving the label.
bool bLabelMoving = mbIsMoving;
bool bLabelMoving = hit.mbIsMoving;
bLabelMoving ^= evt.ShiftDown();
bLabelMoving |= mMouseOverLabelLeft==mMouseOverLabelRight;
bLabelMoving |= ( hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight );
double fNewX = zoomInfo.PositionToTime(x, 0);
if( bLabelMoving )
{
MayMoveLabel( mMouseOverLabelLeft, -1, fNewX );
MayMoveLabel( mMouseOverLabelRight, +1, fNewX );
MayMoveLabel( hit.mMouseOverLabelLeft, -1, fNewX );
MayMoveLabel( hit.mMouseOverLabelRight, +1, fNewX );
}
else
{
MayAdjustLabel( mMouseOverLabelLeft, -1, bAllowSwapping, fNewX );
MayAdjustLabel( mMouseOverLabelRight, +1, bAllowSwapping, fNewX );
MayAdjustLabel( hit, hit.mMouseOverLabelLeft, -1, bAllowSwapping, fNewX );
MayAdjustLabel( hit, hit.mMouseOverLabelRight, +1, bAllowSwapping, fNewX );
}
if( mSelIndex >=0 )
@ -1508,7 +1509,7 @@ bool LabelTrack::HandleGlyphDragRelease(const wxMouseEvent & evt,
//the NEW size of the label.
*newSel = mLabels[mSelIndex].selectedRegion;
}
SortLabels();
SortLabels( &hit );
}
return false;
@ -1556,23 +1557,24 @@ void LabelTrack::HandleTextDragRelease(const wxMouseEvent & evt)
return;
}
void LabelTrack::HandleGlyphClick(const wxMouseEvent & evt,
const wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel)
void LabelTrack::HandleGlyphClick
(LabelTrackHit &hit, const wxMouseEvent & evt,
const wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel)
{
if (evt.ButtonDown())
{
//OverGlyph sets mMouseOverLabel to be the chosen label.
int iGlyph = OverGlyph(evt.m_x, evt.m_y);
mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
iGlyph != 0;
OverGlyph(hit, evt.m_x, evt.m_y);
hit.mIsAdjustingLabel = evt.Button(wxMOUSE_BTN_LEFT) &&
( hit.mEdge & 3 ) != 0;
if (mIsAdjustingLabel)
if (hit.mIsAdjustingLabel)
{
float t = 0.0;
// We move if we hit the centre, we adjust one edge if we hit a chevron.
// This is if we are moving just one edge.
mbIsMoving = mbHitCenter;
hit.mbIsMoving = hit.mEdge & 4;
// When we start dragging the label(s) we don't want them to jump.
// so we calculate the displacement of the mouse from the drag center
// and use that in subsequent dragging calculations. The mouse stays
@ -1585,27 +1587,27 @@ void LabelTrack::HandleGlyphClick(const wxMouseEvent & evt,
// position when we start dragging.
// Dragging of three label edges at the same time is not supported (yet).
if( (mMouseOverLabelRight >=0) &&
(mMouseOverLabelLeft >=0)
if( ( hit.mMouseOverLabelRight >= 0 ) &&
( hit.mMouseOverLabelLeft >= 0 )
)
{
t = (mLabels[mMouseOverLabelRight].getT1() +
mLabels[mMouseOverLabelLeft].getT0()) / 2.0f;
t = (mLabels[ hit.mMouseOverLabelRight ].getT1() +
mLabels[ hit.mMouseOverLabelLeft ].getT0()) / 2.0f;
// If we're moving two edges, then it's a move (label size preserved)
// if both edges are the same label, and it's an adjust (label sizes change)
// if we're on a boundary between two different labels.
mbIsMoving = (mMouseOverLabelLeft == mMouseOverLabelRight);
hit.mbIsMoving =
( hit.mMouseOverLabelLeft == hit.mMouseOverLabelRight );
}
else if(mMouseOverLabelRight >=0)
else if( hit.mMouseOverLabelRight >=0)
{
t = mLabels[mMouseOverLabelRight].getT1();
t = mLabels[ hit.mMouseOverLabelRight ].getT1();
}
else if(mMouseOverLabelLeft >=0)
else if( hit.mMouseOverLabelLeft >=0)
{
t = mLabels[mMouseOverLabelLeft].getT0();
t = mLabels[ hit.mMouseOverLabelLeft ].getT0();
}
mxMouseDisplacement = zoomInfo.TimeToPosition(t, r.x) - evt.m_x;
return;
}
}
}
@ -2879,7 +2881,7 @@ bool LabelTrack::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()
void LabelTrack::SortLabels( LabelTrackHit *pHit )
{
const auto begin = mLabels.begin();
const auto nn = (int)mLabels.size();
@ -2914,8 +2916,10 @@ void LabelTrack::SortLabels()
++index;
}
};
update(mMouseOverLabelLeft);
update(mMouseOverLabelRight);
if ( pHit ) {
update( pHit->mMouseOverLabelLeft );
update( pHit->mMouseOverLabelRight );
}
update(mSelIndex);
}
}

View File

@ -39,6 +39,8 @@ class TimeWarper;
class ZoomInfo;
struct LabelTrackHit;
class LabelStruct
{
public:
@ -150,7 +152,6 @@ class AUDACITY_DLL_API LabelTrack final : public Track
const ZoomInfo &zoomInfo) const;
int getSelectedIndex() const { return mSelIndex; }
bool IsAdjustingLabel() const { return mIsAdjustingLabel; }
int GetKind() const override { return Label; }
@ -180,7 +181,7 @@ class AUDACITY_DLL_API LabelTrack final : public Track
void Silence(double t0, double t1) override;
void InsertSilence(double t, double len) override;
int OverGlyph(int x, int y);
void OverGlyph(LabelTrackHit &hit, int x, int y) const;
static wxBitmap & GetGlyph( int i);
@ -206,13 +207,16 @@ class AUDACITY_DLL_API LabelTrack final : public Track
static bool IsTextClipSupported();
void HandleGlyphClick
(const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo,
(LabelTrackHit &hit,
const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel);
void HandleTextClick
(const wxMouseEvent & evt, const wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel);
bool HandleGlyphDragRelease(const wxMouseEvent & evt, wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel);
bool HandleGlyphDragRelease
(LabelTrackHit &hit,
const wxMouseEvent & evt, wxRect & r, const ZoomInfo &zoomInfo,
SelectedRegion *newSel);
void HandleTextDragRelease(const wxMouseEvent & evt);
bool OnKeyDown(SelectedRegion &sel, wxKeyEvent & event);
@ -243,7 +247,9 @@ class AUDACITY_DLL_API LabelTrack final : public Track
void CalcHighlightXs(int *x1, int *x2) const;
void MayAdjustLabel( int iLabel, int iEdge, bool bAllowSwapping, double fNewTime);
void MayAdjustLabel
( LabelTrackHit &hit,
int iLabel, int iEdge, bool bAllowSwapping, double fNewTime);
void MayMoveLabel( int iLabel, int iEdge, double fNewTime);
// This pastes labels without shifting existing ones
@ -266,19 +272,12 @@ class AUDACITY_DLL_API LabelTrack final : public Track
int FindPrevLabel(const SelectedRegion& currentSelection);
public:
void SortLabels();
//These two are used by a TrackPanel KLUDGE, which is why they are public.
bool mbHitCenter;
//The edge variable tells us what state the icon is in.
//mOldEdge is useful for telling us when there has been a state change.
int mOldEdge;
void SortLabels(LabelTrackHit *pHit = nullptr);
private:
void ShowContextMenu();
void OnContextMenu(wxCommandEvent & evt);
int mSelIndex; /// Keeps track of the currently selected label
int mMouseOverLabelLeft; /// Keeps track of which left label the mouse is currently over.
int mMouseOverLabelRight; /// Keeps track of which right label the mouse is currently over.
int mxMouseDisplacement; /// Displacement of mouse cursor from the centre being dragged.
LabelArray mLabels;
@ -314,9 +313,6 @@ private:
void calculateFontHeight(wxDC & dc) const;
void RemoveSelectedText();
bool mIsAdjustingLabel;
bool mbIsMoving;
static wxFont msFont;
std::weak_ptr<LabelGlyphHandle> mGlyphHandle;

View File

@ -758,7 +758,7 @@ void TrackPanel::Uncapture(wxMouseEvent *pEvent)
{
if (HasCapture())
ReleaseMouse();
HandleCursor( pEvent );
HandleMotion( pEvent );
}
void TrackPanel::CancelDragging()
@ -768,9 +768,10 @@ void TrackPanel::CancelDragging()
auto pTrack = GetTracks()->Lock(mpClickedTrack);
if (pTrack)
ProcessUIHandleResult(
this, mRuler, pTrack.get(), NULL, refreshResult);
this, mRuler, pTrack.get(), NULL,
refreshResult | mMouseOverUpdateFlags );
mpClickedTrack.reset();
mUIHandle.reset();
mUIHandle.reset(), ClearTargets();
Uncapture();
}
}
@ -830,7 +831,7 @@ void TrackPanel::HandleCursorForLastMouseState()
// Come here on modifier key or mouse button transitions,
// or on starting or stopping of play or record,
// and change the cursor appropriately.
HandleCursor( &mLastMouseState );
HandleMotion( &mLastMouseState );
}
bool TrackPanel::IsAudioActive()
@ -840,10 +841,14 @@ bool TrackPanel::IsAudioActive()
}
/// TrackPanel::HandleCursor( ) sets the cursor drawn at the mouse location.
/// TrackPanel::HandleMotion( ) sets the cursor drawn at the mouse location,
/// and updates the status bar message.
/// We treat certain other changes of mouse button and key state as "motions"
/// too, and also starting and stopping of playback or recording, all of which
/// may cause the appropriate cursor and message to change.
/// As this procedure checks which region the mouse is over, it is
/// appropriate to establish the message in the status bar.
void TrackPanel::HandleCursor( wxMouseState *pState )
void TrackPanel::HandleMotion( wxMouseState *pState )
{
wxMouseState dummy;
if (!pState)
@ -857,39 +862,78 @@ void TrackPanel::HandleCursor( wxMouseState *pState )
auto &rect = foundCell.rect;
auto &pCell = foundCell.pCell;
const TrackPanelMouseState tpmState{ state, rect, pCell };
HandleCursor( tpmState );
HandleMotion( tpmState );
}
void TrackPanel::HandleCursor( const TrackPanelMouseState &tpmState )
void TrackPanel::HandleMotion( const TrackPanelMouseState &tpmState )
{
if ( mUIHandle ) {
// UIHANDLE PREVIEW
// Update status message and cursor during drag
HitTestPreview preview = mUIHandle->Preview( tpmState, GetProject() );
mListener->TP_DisplayStatusMessage( preview.message );
if ( preview.cursor )
SetCursor( *preview.cursor );
}
else {
wxCursor *pCursor = NULL;
HitTestResult result;
auto handle = mUIHandle;
wxString tip;
auto oldHandle = mLastHitTest.handle;
auto oldCell = mLastCell.lock();
auto newCell = tpmState.pCell;
auto pCell = tpmState.pCell;
auto track = static_cast<CommonTrackPanelCell*>( pCell.get() )->FindTrack();
if (pCell && pCursor == NULL && tip == wxString()) {
HitTestResult hitTest( pCell->HitTest(tpmState, GetProject()) );
tip = hitTest.preview.message;
ProcessUIHandleResult
(this, mRuler, track.get(), track.get(), hitTest.preview.refreshCode);
pCursor = hitTest.preview.cursor;
if (pCursor)
SetCursor(*pCursor);
std::shared_ptr<Track> newTrack;
if ( newCell )
newTrack = static_cast<CommonTrackPanelCell*>( newCell.get() )->FindTrack();
std::shared_ptr<Track> oldTrack;
if ( oldCell )
oldTrack = static_cast<CommonTrackPanelCell*>( oldCell.get() )->FindTrack();
wxString tip{};
wxCursor *pCursor{};
unsigned refreshCode = 0;
if ( !mUIHandle ) {
// Not yet dragging.
unsigned updateFlags = mMouseOverUpdateFlags;
// First check whether crossing cell to cell
if ( newCell == oldCell )
oldCell.reset();
else {
// Forget old targets
ClearTargets();
// Re-draw any highlighting
if (oldCell) {
ProcessUIHandleResult(
this, GetRuler(), oldTrack.get(), oldTrack.get(), updateFlags);
}
}
if (pCursor != NULL || tip != wxString())
mListener->TP_DisplayStatusMessage(tip);
// Now do the
// UIHANDLE HIT TEST !
result = newCell->HitTest(tpmState, GetProject());
handle = result.handle;
mLastCell = newCell;
mLastHitTestValid = true;
mLastHitTest = result;
if (!oldCell && oldHandle != handle)
// Did not move cell to cell, but did change the target
refreshCode = updateFlags;
}
// UIHANDLE PREVIEW
// Update status message and cursor, whether dragging or not
if (handle) {
auto preview = handle->Preview( tpmState, GetProject() );
tip = preview.message;
pCursor = preview.cursor;
auto code = handle->GetChangeHighlight();
handle->SetChangeHighlight(RefreshCode::RefreshNone);
refreshCode |= code;
mMouseOverUpdateFlags |= code;
}
mListener->TP_DisplayStatusMessage(tip);
if (pCursor)
SetCursor( *pCursor );
ProcessUIHandleResult(
this, GetRuler(), newTrack.get(), newTrack.get(), refreshCode);
}
void TrackPanel::UpdateSelectionDisplay()
@ -1238,7 +1282,8 @@ void TrackPanel::HandleWheelRotation( TrackPanelMouseEvent &tpmEvent )
unsigned result =
pCell->HandleWheelRotation( tpmEvent, GetProject() );
auto pTrack = static_cast<CommonTrackPanelCell*>(pCell.get())->FindTrack();
ProcessUIHandleResult(this, mRuler, pTrack.get(), pTrack.get(), result);
ProcessUIHandleResult(
this, mRuler, pTrack.get(), pTrack.get(), result);
}
/// Filter captured keys typed into LabelTracks.
@ -1361,6 +1406,8 @@ void TrackPanel::OnKeyUp(wxKeyEvent & event)
/// Should handle the case when the mouse capture is lost.
void TrackPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
{
ClearTargets();
wxMouseEvent e(wxEVT_LEFT_UP);
e.m_x = mMouseMostRecentX;
@ -1436,6 +1483,9 @@ try
if (event.Leaving())
{
if ( !mUIHandle )
ClearTargets();
auto buttons =
// Bug 1325: button state in Leaving events is unreliable on Mac.
// Poll the global state instead.
@ -1463,32 +1513,32 @@ try
mUIHandle->Drag( tpmEvent, GetProject() );
ProcessUIHandleResult
(this, mRuler, pClickedTrack.get(), pTrack.get(), refreshResult);
mMouseOverUpdateFlags |= refreshResult;
if (refreshResult & RefreshCode::Cancelled) {
// Drag decided to abort itself
mUIHandle.reset();
mUIHandle.reset(), ClearTargets();
mpClickedTrack.reset();
Uncapture( &event );
}
else {
TrackPanelMouseState tpmState{
tpmEvent.event,
tpmEvent.rect,
tpmEvent.pCell
};
HandleCursor( tpmState );
UpdateMouseState(event);
TrackPanelMouseState tpmState{ mLastMouseState, rect, pCell };
HandleMotion( tpmState );
}
}
else if (event.ButtonUp()) {
// UIHANDLE RELEASE
auto uiHandle = mUIHandle;
// Null this pointer out first before calling Release -- because on Windows, we can
// Null mUIHandle out first before calling Release -- because on Windows, we can
// come back recursively to this place during handling of the context menu,
// because of a capture lost event.
mUIHandle.reset();
unsigned moreFlags = mMouseOverUpdateFlags;
mUIHandle.reset(), ClearTargets();
UIHandle::Result refreshResult =
uiHandle->Release( tpmEvent, GetProject(), this );
ProcessUIHandleResult
(this, mRuler, pClickedTrack.get(), pTrack.get(), refreshResult);
(this, mRuler, pClickedTrack.get(), pTrack.get(),
refreshResult | moreFlags);
mpClickedTrack.reset();
// will also Uncapture() below
}
@ -1498,7 +1548,7 @@ try
// consider it not a drag, even if button is down during motion, if
// mUIHandle is null, as it becomes during interrupted drag
// (e.g. by hitting space to play while dragging an envelope point)
HandleCursor( &event );
HandleMotion( &event );
else if ( event.ButtonDown() || event.ButtonDClick() )
HandleClick( tpmEvent );
@ -1538,32 +1588,44 @@ void TrackPanel::HandleClick( const TrackPanelMouseEvent &tpmEvent )
const auto &rect = tpmEvent.rect;
auto pTrack = static_cast<CommonTrackPanelCell *>( pCell.get() )->FindTrack();
if ( !mUIHandle && pCell ) {
// Do hit test once more, in case the button really pressed was not the
// one "anticipated."
{
TrackPanelMouseState tpmState{
tpmEvent.event,
tpmEvent.rect,
tpmEvent.pCell
};
mUIHandle =
pCell->HitTest( tpmState, GetProject() ).handle;
HandleMotion( tpmState );
}
auto target = Target();
if (target)
mUIHandle = target->handle;
else
mUIHandle = {};
if (mUIHandle) {
// UIHANDLE CLICK
UIHandle::Result refreshResult =
mUIHandle->Click( tpmEvent, GetProject() );
if (refreshResult & RefreshCode::Cancelled)
mUIHandle.reset();
else
mUIHandle.reset(), ClearTargets();
else {
mpClickedTrack = pTrack;
ProcessUIHandleResult
(this, mRuler, pTrack.get(), pTrack.get(), refreshResult);
TrackPanelMouseState tpmState{
tpmEvent.event,
tpmEvent.rect,
tpmEvent.pCell
};
HandleCursor( tpmState );
// Perhaps the clicked handle wants to update cursor and state message
// after a click.
TrackPanelMouseState tpmState{
tpmEvent.event,
tpmEvent.rect,
tpmEvent.pCell
};
HandleMotion( tpmState );
}
ProcessUIHandleResult(
this, mRuler, pTrack.get(), pTrack.get(), refreshResult);
mMouseOverUpdateFlags |= refreshResult;
}
}
@ -1574,6 +1636,9 @@ double TrackPanel::GetMostRecentXPos()
void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
{
if (!trk)
return;
Track *link = trk->GetLink();
if (link && !trk->GetLinked()) {

View File

@ -18,6 +18,8 @@
#include "Experimental.h"
#include "HitTestResult.h"
#include "SelectedRegion.h"
#include "widgets/OverlayPanel.h"
@ -373,8 +375,8 @@ protected:
};
FoundCell FindCell(int mouseX, int mouseY);
void HandleCursor( wxMouseState *pState );
void HandleCursor( const TrackPanelMouseState &tpmState );
void HandleMotion( wxMouseState *pState );
void HandleMotion( const TrackPanelMouseState &tpmState );
// If label, rectangle includes track control panel only.
// If !label, rectangle includes all of that, and the vertical ruler, and
@ -528,6 +530,31 @@ protected:
wxSize vrulerSize;
protected:
std::weak_ptr<TrackPanelCell> mLastCell;
HitTestResult mLastHitTest{};
bool mLastHitTestValid{};
unsigned mMouseOverUpdateFlags{};
public:
HitTestResult *Target()
{
if ( mLastHitTestValid )
return &mLastHitTest;
else
return nullptr;
}
protected:
void ClearTargets()
{
// Forget the rotation of hit test candidates when the mouse moves from
// cell to cell or outside of the TrackPanel entirely.
mLastCell.reset();
mLastHitTestValid = false;
mLastHitTest = {};
mMouseOverUpdateFlags = 0;
}
std::weak_ptr<Track> mpClickedTrack;
UIHandlePtr mUIHandle;

View File

@ -60,7 +60,9 @@ public:
virtual Result Drag
(const TrackPanelMouseEvent &event, AudacityProject *pProject) = 0;
// Update the cursor and status message.
// Can be called when the handle has been hit but not yet clicked,
// or called after Drag().
// Specifies cursor and status bar message.
virtual HitTestPreview Preview
(const TrackPanelMouseState &state, const AudacityProject *pProject) = 0;
@ -99,11 +101,35 @@ public:
// to avoid dangling pointers to tracks. But maybe there will be a future
// use?
virtual void OnProjectChange(AudacityProject *pProject);
public:
Result GetChangeHighlight() const { return mChangeHighlight; }
void SetChangeHighlight(Result val) { mChangeHighlight = val; }
// If AssignUIHandlePtr is used, then this function is also called before any
// overwrite.
// Make overloads of this for other subclasses, to cause refresh
// of the cell during mouse motion within it.
static UIHandle::Result NeedChangeHighlight
(const UIHandle &/*oldState*/, const UIHandle &/*newState*/)
{
return 0;
}
protected:
// Derived classes can set this nonzero in a constructor, which is enough
// to cause repaint of the cell whenever the pointer hits the target,
// or leaves it without clicking, or releases or escapes from a drag.
Result mChangeHighlight { 0 };
};
using UIHandlePtr = std::shared_ptr<UIHandle>;
// A frequent convenience
// A frequent convenience for defining a hit test.
// Construct a NEW handle as if hit the first time; then either keep it, or
// use it to overwrite the state of a previously constructed handle that has not
// yet been released.
template<typename Subclass>
std::shared_ptr<Subclass> AssignUIHandlePtr
( std::weak_ptr<Subclass> &holder, const std::shared_ptr<Subclass> &pNew )
@ -118,7 +144,9 @@ std::shared_ptr<Subclass> AssignUIHandlePtr
return pNew;
}
else {
auto code = Subclass::NeedChangeHighlight( *ptr, *pNew );
*ptr = std::move(*pNew);
ptr->SetChangeHighlight( code );
return ptr;
}
}

View File

@ -24,23 +24,32 @@ Paul Licameli split from TrackPanel.cpp
#include <wx/translation.h>
LabelGlyphHandle::LabelGlyphHandle
(const std::shared_ptr<LabelTrack> &pLT, const wxRect &rect)
(const std::shared_ptr<LabelTrack> &pLT,
const wxRect &rect, const LabelTrackHit &hit)
: mpLT{ pLT }
, mRect{ rect }
{}
, mHit{ hit }
{
mChangeHighlight = RefreshCode::RefreshCell;
}
HitTestPreview LabelGlyphHandle::HitPreview
(bool hitCenter, unsigned refreshResult)
UIHandle::Result LabelGlyphHandle::NeedChangeHighlight
(const LabelGlyphHandle &oldState, const LabelGlyphHandle &newState)
{
if (oldState.mHit.mEdge != newState.mHit.mEdge)
// pointer moves between the circle and the chevron
return RefreshCode::RefreshCell;
return 0;
}
HitTestPreview LabelGlyphHandle::HitPreview(bool hitCenter)
{
static wxCursor arrowCursor{ wxCURSOR_ARROW };
return {
(hitCenter
? _("Drag one or more label boundaries.")
: _("Drag label boundary.")),
&arrowCursor,
// Unusually, can have a non-zero third member of HitTestPreview, so that
// mouse-over highlights it.
refreshResult
&arrowCursor
};
}
@ -49,39 +58,22 @@ HitTestResult LabelGlyphHandle::HitTest
const wxMouseState &state,
const std::shared_ptr<LabelTrack> &pLT, const wxRect &rect)
{
using namespace RefreshCode;
unsigned refreshResult = RefreshNone;
// Note: this has side effects on pLT!
int edge = pLT->OverGlyph(state.m_x, state.m_y);
//KLUDGE: We refresh the whole Label track when the icon hovered over
//changes colouration. Inefficient.
edge += pLT->mbHitCenter ? 4 : 0;
if (edge != pLT->mOldEdge)
{
pLT->mOldEdge = edge;
refreshResult |= RefreshCell;
}
LabelTrackHit hit{};
pLT->OverGlyph(hit, 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 (edge != 0)
if ( hit.mEdge & 3 )
{
auto result = std::make_shared<LabelGlyphHandle>( pLT, rect );
auto result = std::make_shared<LabelGlyphHandle>( pLT, rect, hit );
result = AssignUIHandlePtr(holder, result);
return {
HitPreview(pLT->mbHitCenter, refreshResult),
HitPreview( hit.mEdge & 4 ),
result
};
}
else {
// An empty result, except maybe, unusually, the refresh
return {
{ wxString{}, nullptr, refreshResult },
{}
};
}
return {};
}
LabelGlyphHandle::~LabelGlyphHandle()
@ -96,9 +88,10 @@ UIHandle::Result LabelGlyphHandle::Click
const wxMouseEvent &event = evt.event;
ViewInfo &viewInfo = pProject->GetViewInfo();
mpLT->HandleGlyphClick(event, mRect, viewInfo, &viewInfo.selectedRegion);
mpLT->HandleGlyphClick
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
if (! mpLT->IsAdjustingLabel() )
if (! mHit.mIsAdjustingLabel )
{
// The positive hit test should have ensured otherwise
//wxASSERT(false);
@ -125,7 +118,8 @@ UIHandle::Result LabelGlyphHandle::Drag
const wxMouseEvent &event = evt.event;
ViewInfo &viewInfo = pProject->GetViewInfo();
mpLT->HandleGlyphDragRelease(event, mRect, viewInfo, &viewInfo.selectedRegion);
mpLT->HandleGlyphDragRelease
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion);
// Refresh all so that the change of selection is redrawn in all tracks
return result | RefreshCode::RefreshAll | RefreshCode::DrawOverlays;
@ -134,7 +128,7 @@ UIHandle::Result LabelGlyphHandle::Drag
HitTestPreview LabelGlyphHandle::Preview
(const TrackPanelMouseState &, const AudacityProject *)
{
return HitPreview(mpLT->mbHitCenter, 0);
return HitPreview( mHit.mEdge & 4 );
}
UIHandle::Result LabelGlyphHandle::Release
@ -142,11 +136,11 @@ UIHandle::Result LabelGlyphHandle::Release
wxWindow *pParent)
{
auto result = LabelDefaultClickHandle::Release( evt, pProject, pParent );
mpLT->mOldEdge = 0;
const wxMouseEvent &event = evt.event;
ViewInfo &viewInfo = pProject->GetViewInfo();
if (mpLT->HandleGlyphDragRelease(event, mRect, viewInfo, &viewInfo.selectedRegion)) {
if (mpLT->HandleGlyphDragRelease
(mHit, event, mRect, viewInfo, &viewInfo.selectedRegion)) {
pProject->PushState(_("Modified Label"),
_("Label Edit"),
UndoPush::CONSOLIDATE);
@ -158,7 +152,6 @@ UIHandle::Result LabelGlyphHandle::Release
UIHandle::Result LabelGlyphHandle::Cancel(AudacityProject *pProject)
{
mpLT->mOldEdge = 0;
pProject->RollbackState();
auto result = LabelDefaultClickHandle::Cancel( pProject );
return result | RefreshCode::RefreshAll;

View File

@ -19,14 +19,33 @@ class wxMouseState;
struct HitTestResult;
class LabelTrack;
/// mEdge:
/// 0 if not over a glyph,
/// else a bitwise or of :
/// 1 if over the left-hand glyph,
/// 2 if over the right-hand glyph on a label,
/// 4 if over center.
///
/// mMouseLabelLeft - index of any left label hit
/// mMouseLabelRight - index of any right label hit
///
struct 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 {};
};
class LabelGlyphHandle final : public LabelDefaultClickHandle
{
LabelGlyphHandle(const LabelGlyphHandle&) = delete;
static HitTestPreview HitPreview(bool hitCenter, unsigned refreshResult);
static HitTestPreview HitPreview(bool hitCenter);
public:
explicit LabelGlyphHandle
(const std::shared_ptr<LabelTrack> &pLT, const wxRect &rect);
(const std::shared_ptr<LabelTrack> &pLT,
const wxRect &rect, const LabelTrackHit &hit);
LabelGlyphHandle &operator=(LabelGlyphHandle&&) = default;
@ -55,6 +74,11 @@ public:
bool StopsOnKeystroke() override { return true; }
LabelTrackHit mHit{};
static UIHandle::Result NeedChangeHighlight
(const LabelGlyphHandle &oldState, const LabelGlyphHandle &newState);
private:
std::shared_ptr<LabelTrack> mpLT {};
wxRect mRect {};

View File

@ -31,13 +31,11 @@ HitTestResult LabelTrack::DetailedHitTest
// Try label movement handles first
result = LabelGlyphHandle::HitTest(
mGlyphHandle, state, Pointer<LabelTrack>(this), st.rect);
auto refresh = result.preview.refreshCode; // kludge
if ( !result.handle ) {
// Missed glyph, try text box
result = LabelTextHandle::HitTest(
mTextHandle, state, Pointer<LabelTrack>(this));
result.preview.refreshCode |= refresh; // kludge
}
return result;

View File

@ -46,9 +46,6 @@ HitTestResult Track::HitTest
// If there is no detailed hit for the subclass, there are still some
// general cases.
// Label track kludge!
auto refresh = result.preview.refreshCode;
// Sliding applies in more than one track type.
if ( !result.handle && !isMultiTool && currentTool == slideTool )
result = TimeShiftHandle::HitAnywhere(
@ -65,7 +62,6 @@ HitTestResult Track::HitTest
result = SelectHandle::HitTest(
mSelectHandle, st, pProject, Pointer(this));
result.preview.refreshCode |= refresh;
return result;
}