mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-14 17:14:07 +01:00
TrackPanel no longer implements the selection tool or MIDI stretch...
This one's big! Also restores the effect of ctrl-click on label track. Also adds ESC key handling for the Stretch.
This commit is contained in:
committed by
Paul Licameli
parent
efdb9889b1
commit
770b3b52ef
@@ -15,8 +15,12 @@ Paul Licameli split from TrackPanel.cpp
|
||||
#include "LabelGlyphHandle.h"
|
||||
#include "LabelTextHandle.h"
|
||||
|
||||
#include "../../ui/SelectHandle.h"
|
||||
|
||||
#include "../../../HitTestResult.h"
|
||||
#include "../../../Project.h"
|
||||
#include "../../../TrackPanelMouseEvent.h"
|
||||
#include "../../../toolbars/ToolsToolBar.h"
|
||||
|
||||
HitTestResult LabelTrack::HitTest
|
||||
(const TrackPanelMouseEvent &evt,
|
||||
@@ -37,6 +41,12 @@ HitTestResult LabelTrack::HitTest
|
||||
// Missed glyph, try text box
|
||||
// This hit test does not define its own messages or cursor
|
||||
HitTestResult defaultResult = Track::HitTest(evt, pProject);
|
||||
if (!defaultResult.handle) {
|
||||
// In case of multi tool, default to selection.
|
||||
const ToolsToolBar *const pTtb = pProject->GetToolsToolBar();
|
||||
if (pTtb->IsDown(multiTool))
|
||||
defaultResult = SelectHandle::HitTest(evt, pProject, this);
|
||||
}
|
||||
result = LabelTextHandle::HitTest(event, this);
|
||||
if (result.handle)
|
||||
// Use any cursor or status message change from catchall,
|
||||
|
||||
@@ -17,12 +17,49 @@ Paul Licameli split from TrackPanel.cpp
|
||||
#include "NoteTrackVRulerControls.h"
|
||||
|
||||
#include "../../../../HitTestResult.h"
|
||||
#include "../../../../Project.h"
|
||||
#include "../../../../TrackPanelMouseEvent.h"
|
||||
#include "../../../ui/SelectHandle.h"
|
||||
#include "StretchHandle.h"
|
||||
#include "../../../../toolbars/ToolsToolBar.h"
|
||||
|
||||
HitTestResult NoteTrack::HitTest
|
||||
(const TrackPanelMouseEvent &event,
|
||||
const AudacityProject *pProject)
|
||||
{
|
||||
return Track::HitTest(event, pProject);
|
||||
const ToolsToolBar *const pTtb = pProject->GetToolsToolBar();
|
||||
|
||||
// If the cursor is in the hot zone for stretching, that takes precedence
|
||||
// over selection, but it didn't take precedence over other hits.
|
||||
// That was the old logic, and maybe I tried too hard to preserve it just so.
|
||||
// PRL.
|
||||
|
||||
// Eligible for stretch?
|
||||
HitTestResult result1;
|
||||
#ifdef USE_MIDI
|
||||
StretchHandle::StretchState state;
|
||||
result1 = StretchHandle::HitTest( event, pProject, this, state );
|
||||
#endif
|
||||
|
||||
// But some other non-select tool like zoom may take priority.
|
||||
HitTestResult result = Track::HitTest(event, pProject);
|
||||
if (result.preview.cursor &&
|
||||
!(result1.preview.cursor && pTtb->GetCurrentTool() == selectTool))
|
||||
return result;
|
||||
|
||||
if (pTtb->IsDown(multiTool)) {
|
||||
// Default to selection
|
||||
if (!result1.preview.cursor &&
|
||||
NULL != (result =
|
||||
SelectHandle::HitTest(event, pProject, this)).preview.cursor)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Do stretch!
|
||||
if (result1.preview.cursor)
|
||||
return result1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TrackControls *NoteTrack::GetControls()
|
||||
|
||||
339
src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp
Normal file
339
src/tracks/playabletrack/notetrack/ui/StretchHandle.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
StretchHandle.cpp
|
||||
|
||||
Paul Licameli split from TrackPanel.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "../../../../Audacity.h"
|
||||
|
||||
#ifdef USE_MIDI
|
||||
|
||||
#include "StretchHandle.h"
|
||||
|
||||
#include "../../../../HitTestResult.h"
|
||||
#include "../../../../NoteTrack.h"
|
||||
#include "../../../../Project.h"
|
||||
#include "../../../../RefreshCode.h"
|
||||
#include "../../../../TrackPanelMouseEvent.h"
|
||||
#include "../../../../UndoManager.h"
|
||||
#include "../../../../../images/Cursors.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
StretchHandle::StretchHandle()
|
||||
{
|
||||
}
|
||||
|
||||
StretchHandle &StretchHandle::Instance()
|
||||
{
|
||||
static StretchHandle instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
HitTestPreview StretchHandle::HitPreview( StretchEnum stretchMode, bool unsafe )
|
||||
{
|
||||
static auto disabledCursor =
|
||||
::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
|
||||
static auto stretchLeftCursor =
|
||||
::MakeCursor(wxCURSOR_BULLSEYE, StretchLeftCursorXpm, 16, 16);
|
||||
static auto stretchRightCursor =
|
||||
::MakeCursor(wxCURSOR_BULLSEYE, StretchRightCursorXpm, 16, 16);
|
||||
static auto stretchCursor =
|
||||
::MakeCursor(wxCURSOR_BULLSEYE, StretchCursorXpm, 16, 16);
|
||||
|
||||
if (unsafe) {
|
||||
return { _(""), &*disabledCursor };
|
||||
}
|
||||
else {
|
||||
wxCursor *pCursor = NULL;
|
||||
switch (stretchMode) {
|
||||
default:
|
||||
wxASSERT(false);
|
||||
case stretchLeft:
|
||||
pCursor = &*stretchLeftCursor; break;
|
||||
case stretchCenter:
|
||||
pCursor = &*stretchCursor; break;
|
||||
case stretchRight:
|
||||
pCursor = &*stretchRightCursor; break;
|
||||
}
|
||||
return {
|
||||
_("Click and drag to stretch selected region."),
|
||||
pCursor
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
HitTestResult StretchHandle::HitTest
|
||||
( const TrackPanelMouseEvent &evt, const AudacityProject *pProject,
|
||||
NoteTrack *pTrack, StretchState &stretchState)
|
||||
{
|
||||
const wxMouseEvent &event = evt.event;
|
||||
|
||||
// later, we may want a different policy, but for now, stretch is
|
||||
// selected when the cursor is near the center of the track and
|
||||
// within the selection
|
||||
|
||||
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||
const bool unsafe = pProject->IsAudioActive();
|
||||
if (!pTrack || !pTrack->GetSelected() || pTrack->GetKind() != Track::Note)
|
||||
return {};
|
||||
|
||||
const wxRect &rect = evt.rect;
|
||||
int center = rect.y + rect.height / 2;
|
||||
int distance = abs(event.m_y - center);
|
||||
const int yTolerance = 10;
|
||||
wxInt64 leftSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t0(), rect.x);
|
||||
wxInt64 rightSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t1(), rect.x);
|
||||
// Something is wrong if right edge comes before left edge
|
||||
wxASSERT(!(rightSel < leftSel));
|
||||
if (!(leftSel <= event.m_x && event.m_x <= rightSel &&
|
||||
distance < yTolerance))
|
||||
return {};
|
||||
|
||||
// find nearest beat to sel0, sel1
|
||||
static const double minPeriod = 0.05; // minimum beat period
|
||||
stretchState.mBeatCenter = { 0, 0 };
|
||||
|
||||
auto t0 = viewInfo.selectedRegion.t0();
|
||||
stretchState.mBeat0 = pTrack->NearestBeatTime( t0 );
|
||||
stretchState.mOrigSel0Quantized = stretchState.mBeat0.first;
|
||||
|
||||
auto t1 = viewInfo.selectedRegion.t1();
|
||||
stretchState.mBeat1 = pTrack->NearestBeatTime( t1 );
|
||||
stretchState.mOrigSel1Quantized = stretchState.mBeat1.first;
|
||||
|
||||
// If there is not (almost) a beat to stretch that is slower
|
||||
// than 20 beats per second, don't stretch
|
||||
if ( within( stretchState.mBeat0.second,
|
||||
stretchState.mBeat1.second, 0.9 ) ||
|
||||
( stretchState.mBeat1.first - stretchState.mBeat0.first ) /
|
||||
( stretchState.mBeat1.second - stretchState.mBeat0.second )
|
||||
< minPeriod )
|
||||
return {};
|
||||
|
||||
auto selStart = viewInfo.PositionToTime( event.m_x, rect.x );
|
||||
stretchState.mBeatCenter = pTrack->NearestBeatTime( selStart );
|
||||
bool startNewSelection = true;
|
||||
if ( within( stretchState.mBeat0.second,
|
||||
stretchState.mBeatCenter.second, 0.1 ) ) {
|
||||
stretchState.mMode = stretchLeft;
|
||||
stretchState.mLeftBeats = 0;
|
||||
stretchState.mRightBeats =
|
||||
stretchState.mBeat1.second - stretchState.mBeat0.second;
|
||||
}
|
||||
else if ( within( stretchState.mBeat1.second,
|
||||
stretchState.mBeatCenter.second, 0.1 ) ) {
|
||||
stretchState.mMode = stretchRight;
|
||||
stretchState.mLeftBeats =
|
||||
stretchState.mBeat1.second - stretchState.mBeat0.second;
|
||||
stretchState.mRightBeats = 0;
|
||||
}
|
||||
else {
|
||||
stretchState.mMode = stretchCenter;
|
||||
stretchState.mLeftBeats =
|
||||
stretchState.mBeat1.second - stretchState.mBeatCenter.second;
|
||||
stretchState.mRightBeats =
|
||||
stretchState.mBeatCenter.second - stretchState.mBeat0.second;
|
||||
}
|
||||
|
||||
return {
|
||||
HitPreview( stretchState.mMode, unsafe ),
|
||||
&Instance()
|
||||
};
|
||||
}
|
||||
|
||||
StretchHandle::~StretchHandle()
|
||||
{
|
||||
}
|
||||
|
||||
UIHandle::Result StretchHandle::Click
|
||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||
{
|
||||
using namespace RefreshCode;
|
||||
const bool unsafe = pProject->IsAudioActive();
|
||||
const wxMouseEvent &event = evt.event;
|
||||
|
||||
if (unsafe ||
|
||||
event.LeftDClick() ||
|
||||
!event.LeftDown() ||
|
||||
evt.pCell == NULL)
|
||||
return Cancelled;
|
||||
|
||||
|
||||
mLeftEdge = evt.rect.GetLeft();
|
||||
mpTrack = static_cast<NoteTrack*>(evt.pCell);
|
||||
ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||
|
||||
// We must have hit if we got here, but repeat some
|
||||
// calculations that set members
|
||||
HitTest( evt, pProject, mpTrack, mStretchState );
|
||||
|
||||
viewInfo.selectedRegion.setTimes
|
||||
( mStretchState.mBeat0.first, mStretchState.mBeat1.first );
|
||||
|
||||
// Full refresh since the label area may need to indicate
|
||||
// newly selected tracks. (I'm really not sure if the label area
|
||||
// needs to be refreshed or how to just refresh non-label areas.-RBD)
|
||||
|
||||
return RefreshAll | UpdateSelection;
|
||||
}
|
||||
|
||||
UIHandle::Result StretchHandle::Drag
|
||||
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
||||
{
|
||||
using namespace RefreshCode;
|
||||
const bool unsafe = pProject->IsAudioActive();
|
||||
if (unsafe) {
|
||||
this->Cancel(pProject);
|
||||
return RefreshAll | Cancelled;
|
||||
}
|
||||
|
||||
const wxMouseEvent &event = evt.event;
|
||||
const int x = event.m_x;
|
||||
|
||||
Track *clickedTrack =
|
||||
static_cast<CommonTrackPanelCell*>(evt.pCell)->FindTrack();
|
||||
|
||||
if (clickedTrack == NULL && mpTrack != NULL)
|
||||
clickedTrack = mpTrack;
|
||||
Stretch(pProject, x, mLeftEdge, clickedTrack);
|
||||
return RefreshAll;
|
||||
}
|
||||
|
||||
HitTestPreview StretchHandle::Preview
|
||||
(const TrackPanelMouseEvent &, const AudacityProject *pProject)
|
||||
{
|
||||
const bool unsafe = pProject->IsAudioActive();
|
||||
return HitPreview( mStretchState.mMode, unsafe );
|
||||
}
|
||||
|
||||
UIHandle::Result StretchHandle::Release
|
||||
(const TrackPanelMouseEvent &, AudacityProject *pProject,
|
||||
wxWindow *)
|
||||
{
|
||||
using namespace RefreshCode;
|
||||
|
||||
const bool unsafe = pProject->IsAudioActive();
|
||||
if (unsafe) {
|
||||
this->Cancel(pProject);
|
||||
return RefreshAll | Cancelled;
|
||||
}
|
||||
|
||||
bool left;
|
||||
ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||
if ( pProject->IsSyncLocked() &&
|
||||
( ( left = mStretchState.mMode == stretchLeft ) ||
|
||||
mStretchState.mMode == stretchRight ) ) {
|
||||
SyncLockedTracksIterator syncIter( pProject->GetTracks() );
|
||||
for ( auto track = syncIter.StartWith( mpTrack ); track != nullptr;
|
||||
track = syncIter.Next() ) {
|
||||
if ( track != mpTrack ) {
|
||||
if ( left ) {
|
||||
auto origT0 = mStretchState.mOrigSel0Quantized;
|
||||
auto diff = viewInfo.selectedRegion.t0() - origT0;
|
||||
if ( diff > 0)
|
||||
track->SyncLockAdjust( origT0 + diff, origT0 );
|
||||
else
|
||||
track->SyncLockAdjust( origT0, origT0 - diff );
|
||||
track->Offset( diff );
|
||||
}
|
||||
else {
|
||||
auto origT1 = mStretchState.mOrigSel1Quantized;
|
||||
auto diff = viewInfo.selectedRegion.t1() - origT1;
|
||||
track->SyncLockAdjust( origT1, origT1 + diff );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* i18n-hint: (noun) The track that is used for MIDI notes which can be
|
||||
dragged to change their duration.*/
|
||||
pProject->PushState(_("Stretch Note Track"),
|
||||
/* i18n-hint: In the history list, indicates a MIDI note has
|
||||
been dragged to change its duration (stretch it). Using either past
|
||||
or present tense is fine here. If unsure, go for whichever is
|
||||
shorter.*/
|
||||
_("Stretch"),
|
||||
UndoPush::CONSOLIDATE | UndoPush::AUTOSAVE);
|
||||
return RefreshAll;
|
||||
}
|
||||
|
||||
UIHandle::Result StretchHandle::Cancel(AudacityProject *pProject)
|
||||
{
|
||||
pProject->RollbackState();
|
||||
return RefreshCode::RefreshNone;
|
||||
}
|
||||
|
||||
void StretchHandle::Stretch(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge,
|
||||
Track *pTrack)
|
||||
{
|
||||
ViewInfo &viewInfo = pProject->GetViewInfo();
|
||||
TrackList *const trackList = pProject->GetTracks();
|
||||
|
||||
if (pTrack == NULL && mpTrack != NULL)
|
||||
pTrack = mpTrack;
|
||||
|
||||
if (!pTrack || pTrack->GetKind() != Track::Note) {
|
||||
return;
|
||||
}
|
||||
|
||||
NoteTrack *pNt = static_cast<NoteTrack *>(pTrack);
|
||||
double moveto =
|
||||
std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
|
||||
|
||||
auto t1 = viewInfo.selectedRegion.t1();
|
||||
auto t0 = viewInfo.selectedRegion.t0();
|
||||
double dur, left_dur, right_dur;
|
||||
|
||||
// check to make sure tempo is not higher than 20 beats per second
|
||||
// (In principle, tempo can be higher, but not infinity.)
|
||||
double minPeriod = 0.05; // minimum beat period
|
||||
|
||||
// make sure target duration is not too short
|
||||
// Take quick exit if so, without changing the selection.
|
||||
switch ( mStretchState.mMode ) {
|
||||
case stretchLeft: {
|
||||
dur = t1 - moveto;
|
||||
if (dur < mStretchState.mRightBeats * minPeriod)
|
||||
return;
|
||||
pNt->StretchRegion
|
||||
( mStretchState.mBeat0, mStretchState.mBeat1, dur );
|
||||
pNt->Offset( moveto - t0 );
|
||||
mStretchState.mBeat0.first = moveto;
|
||||
viewInfo.selectedRegion.setT0(moveto);
|
||||
break;
|
||||
}
|
||||
case stretchRight: {
|
||||
dur = moveto - t0;
|
||||
if (dur < mStretchState.mLeftBeats * minPeriod)
|
||||
return;
|
||||
pNt->StretchRegion
|
||||
( mStretchState.mBeat0, mStretchState.mBeat1, dur );
|
||||
viewInfo.selectedRegion.setT1(moveto);
|
||||
mStretchState.mBeat1.first = moveto;
|
||||
viewInfo.selectedRegion.setT0(moveto);
|
||||
break;
|
||||
}
|
||||
case stretchCenter: {
|
||||
left_dur = moveto - t0;
|
||||
right_dur = t1 - moveto;
|
||||
if ( left_dur < mStretchState.mLeftBeats * minPeriod ||
|
||||
right_dur < mStretchState.mRightBeats * minPeriod )
|
||||
return;
|
||||
pNt->StretchRegion
|
||||
( mStretchState.mBeatCenter, mStretchState.mBeat1, right_dur );
|
||||
pNt->StretchRegion
|
||||
( mStretchState.mBeat0, mStretchState.mBeatCenter, left_dur );
|
||||
mStretchState.mBeatCenter.first = moveto;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
wxASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
98
src/tracks/playabletrack/notetrack/ui/StretchHandle.h
Normal file
98
src/tracks/playabletrack/notetrack/ui/StretchHandle.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
StretchHandle.h
|
||||
|
||||
Paul Licameli split from TrackPanel.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_STRETCH_HANDLE__
|
||||
#define __AUDACITY_STRETCH_HANDLE__
|
||||
|
||||
#include "../../../../UIHandle.h"
|
||||
|
||||
#include "../../../../MemoryX.h"
|
||||
|
||||
class Alg_seq;
|
||||
struct HitTestResult;
|
||||
class NoteTrack;
|
||||
class Track;
|
||||
class ViewInfo;
|
||||
class wxCursor;
|
||||
|
||||
class StretchHandle : public UIHandle
|
||||
{
|
||||
public:
|
||||
enum StretchEnum {
|
||||
stretchNone = 0, // false value!
|
||||
stretchLeft,
|
||||
stretchCenter,
|
||||
stretchRight
|
||||
};
|
||||
|
||||
// Stretching applies to a selected region after quantizing the
|
||||
// region to beat boundaries (subbeat stretching is not supported,
|
||||
// but maybe it should be enabled with shift or ctrl or something)
|
||||
// Stretching can drag the left boundary (the right stays fixed),
|
||||
// the right boundary (the left stays fixed), or the center (splits
|
||||
// the selection into two parts: when left part grows, the right
|
||||
// part shrinks, keeping the leftmost and rightmost boundaries
|
||||
// fixed.
|
||||
struct StretchState {
|
||||
StretchEnum mMode { stretchCenter }; // remembers what to drag
|
||||
|
||||
using QuantizedTimeAndBeat = std::pair< double, double >;
|
||||
|
||||
QuantizedTimeAndBeat mBeatCenter { 0, 0 };
|
||||
QuantizedTimeAndBeat mBeat0 { 0, 0 };
|
||||
QuantizedTimeAndBeat mBeat1 { 0, 0 };
|
||||
double mLeftBeats {}; // how many beats from left to cursor
|
||||
double mRightBeats {}; // how many beats from cursor to right
|
||||
|
||||
double mOrigSel0Quantized { -1 }, mOrigSel1Quantized { -1 };
|
||||
};
|
||||
|
||||
private:
|
||||
StretchHandle();
|
||||
StretchHandle(const StretchHandle&);
|
||||
StretchHandle &operator=(const StretchHandle&);
|
||||
static StretchHandle& Instance();
|
||||
static HitTestPreview HitPreview(StretchEnum stretchMode, bool unsafe);
|
||||
|
||||
public:
|
||||
static HitTestResult HitTest
|
||||
( const TrackPanelMouseEvent &event, const AudacityProject *pProject,
|
||||
NoteTrack *pTrack, StretchState &state );
|
||||
|
||||
virtual ~StretchHandle();
|
||||
|
||||
virtual Result Click
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
||||
|
||||
virtual Result Drag
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
||||
|
||||
virtual HitTestPreview Preview
|
||||
(const TrackPanelMouseEvent &event, const AudacityProject *pProject);
|
||||
|
||||
virtual Result Release
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject,
|
||||
wxWindow *pParent);
|
||||
|
||||
virtual Result Cancel(AudacityProject *pProject);
|
||||
|
||||
bool StopsOnKeystroke() override { return true; }
|
||||
|
||||
private:
|
||||
void Stretch
|
||||
(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge, Track *pTrack);
|
||||
|
||||
NoteTrack *mpTrack{};
|
||||
int mLeftEdge{ -1 };
|
||||
|
||||
StretchState mStretchState{};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,7 @@ Paul Licameli split from TrackPanel.cpp
|
||||
#include "../../../../toolbars/ToolsToolBar.h"
|
||||
|
||||
#include "CutlineHandle.h"
|
||||
#include "../../../ui/SelectHandle.h"
|
||||
#include "../../../ui/EnvelopeHandle.h"
|
||||
#include "SampleHandle.h"
|
||||
#include "../../../ui/TimeShiftHandle.h"
|
||||
@@ -26,6 +27,11 @@ HitTestResult WaveTrack::HitTest
|
||||
(const TrackPanelMouseEvent &event,
|
||||
const AudacityProject *pProject)
|
||||
{
|
||||
// FIXME: Should similar logic apply to NoteTrack (#if defined(USE_MIDI)) ?
|
||||
// From here on the order in which we hit test determines
|
||||
// which tool takes priority in the rare cases where it
|
||||
// could be more than one.
|
||||
|
||||
// This hit was always tested first no matter which tool:
|
||||
HitTestResult result = CutlineHandle::HitTest(event.event, event.rect, pProject, this);
|
||||
if (result.preview.cursor)
|
||||
@@ -51,6 +57,10 @@ HitTestResult WaveTrack::HitTest
|
||||
else if (NULL != (result =
|
||||
SampleHandle::HitTest(event.event, event.rect, pProject, this)).preview.cursor)
|
||||
;
|
||||
else if (NULL != (result =
|
||||
SelectHandle::HitTest(event, pProject, this)).preview.cursor)
|
||||
// default of all other hit tests
|
||||
;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
1437
src/tracks/ui/SelectHandle.cpp
Normal file
1437
src/tracks/ui/SelectHandle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
153
src/tracks/ui/SelectHandle.h
Normal file
153
src/tracks/ui/SelectHandle.h
Normal file
@@ -0,0 +1,153 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
SelectHandle.h
|
||||
|
||||
Paul Licameli split from TrackPanel.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_SELECT_HANDLE__
|
||||
#define __AUDACITY_SELECT_HANDLE__
|
||||
|
||||
#include "../../UIHandle.h"
|
||||
#include "../../SelectedRegion.h"
|
||||
|
||||
#include "../../MemoryX.h"
|
||||
#include <vector>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/gdicmn.h>
|
||||
|
||||
struct HitTestResult;
|
||||
class SelectionStateChanger;
|
||||
class SnapManager;
|
||||
class SpectrumAnalyst;
|
||||
class Track;
|
||||
class ViewInfo;
|
||||
class WaveTrack;
|
||||
|
||||
class SelectHandle : public wxEvtHandler, public UIHandle
|
||||
{
|
||||
SelectHandle();
|
||||
SelectHandle(const SelectHandle&);
|
||||
SelectHandle &operator=(const SelectHandle&);
|
||||
|
||||
public:
|
||||
static SelectHandle& Instance();
|
||||
|
||||
// This always hits, but details of the hit vary with mouse position and
|
||||
// key state.
|
||||
static HitTestResult HitTest
|
||||
(const TrackPanelMouseEvent &event, const AudacityProject *pProject, const Track *pTrack);
|
||||
|
||||
virtual ~SelectHandle();
|
||||
|
||||
virtual Result Click
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
||||
|
||||
virtual Result Drag
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject);
|
||||
|
||||
virtual HitTestPreview Preview
|
||||
(const TrackPanelMouseEvent &event, const AudacityProject *pProject);
|
||||
|
||||
virtual Result Release
|
||||
(const TrackPanelMouseEvent &event, AudacityProject *pProject,
|
||||
wxWindow *pParent);
|
||||
|
||||
virtual Result Cancel(AudacityProject*);
|
||||
|
||||
virtual void DrawExtras
|
||||
(DrawingPass pass,
|
||||
wxDC * dc, const wxRegion &updateRegion, const wxRect &panelRect);
|
||||
|
||||
void OnProjectChange(AudacityProject *pProject) override;
|
||||
|
||||
// Receives timer event notifications, to implement auto-scroll
|
||||
void OnTimer(wxCommandEvent &event);
|
||||
|
||||
private:
|
||||
void Connect(AudacityProject *pProject);
|
||||
void Disconnect();
|
||||
|
||||
void StartSelection
|
||||
(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge);
|
||||
void AdjustSelection
|
||||
(ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
|
||||
Track *pTrack);
|
||||
|
||||
void StartFreqSelection
|
||||
(ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge,
|
||||
int trackHeight, Track *pTrack);
|
||||
void AdjustFreqSelection
|
||||
(ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge,
|
||||
int trackHeight);
|
||||
|
||||
void HandleCenterFrequencyClick
|
||||
(const ViewInfo &viewInfo, bool shiftDown,
|
||||
const WaveTrack *pTrack, double value);
|
||||
void StartSnappingFreqSelection
|
||||
(const ViewInfo &viewInfo, const WaveTrack *pTrack);
|
||||
void MoveSnappingFreqSelection
|
||||
(AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
|
||||
int trackTopEdge,
|
||||
int trackHeight, Track *pTrack);
|
||||
public:
|
||||
// This is needed to implement a command assignable to keystrokes
|
||||
void SnapCenterOnce
|
||||
(ViewInfo &viewInfo, const WaveTrack *pTrack, bool up);
|
||||
private:
|
||||
//void ResetFreqSelectionPin
|
||||
// (const ViewInfo &viewInfo, double hintFrequency, bool logF);
|
||||
|
||||
|
||||
Track *mpTrack;
|
||||
wxRect mRect;
|
||||
SelectedRegion mInitialSelection;
|
||||
|
||||
// Handles snapping the selection boundaries or track boundaries to
|
||||
// line up with existing tracks or labels. mSnapLeft and mSnapRight
|
||||
// are the horizontal index of pixels to display user feedback
|
||||
// guidelines so the user knows when such snapping is taking place.
|
||||
std::unique_ptr<SnapManager> mSnapManager;
|
||||
wxInt64 mSnapLeft;
|
||||
wxInt64 mSnapRight;
|
||||
|
||||
bool mSelStartValid;
|
||||
double mSelStart;
|
||||
|
||||
int mSelectionBoundary;
|
||||
|
||||
enum eFreqSelMode {
|
||||
FREQ_SEL_INVALID,
|
||||
|
||||
FREQ_SEL_SNAPPING_CENTER,
|
||||
FREQ_SEL_PINNED_CENTER,
|
||||
FREQ_SEL_DRAG_CENTER,
|
||||
|
||||
FREQ_SEL_FREE,
|
||||
FREQ_SEL_TOP_FREE,
|
||||
FREQ_SEL_BOTTOM_FREE,
|
||||
} mFreqSelMode;
|
||||
const WaveTrack *mFreqSelTrack {};
|
||||
// Following holds:
|
||||
// the center for FREQ_SEL_PINNED_CENTER,
|
||||
// the ratio of top to center (== center to bottom) for FREQ_SEL_DRAG_CENTER,
|
||||
// a frequency boundary for FREQ_SEL_FREE, FREQ_SEL_TOP_FREE, or
|
||||
// FREQ_SEL_BOTTOM_FREE,
|
||||
// and is ignored otherwise.
|
||||
double mFreqSelPin;
|
||||
std::unique_ptr<SpectrumAnalyst> mFrequencySnapper;
|
||||
|
||||
int mMostRecentX, mMostRecentY;
|
||||
|
||||
bool mAutoScrolling;
|
||||
|
||||
AudacityProject *mConnectedProject;
|
||||
|
||||
std::unique_ptr<SelectionStateChanger> mSelectionStateChanger;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@ Paul Licameli split from TrackPanel.cpp
|
||||
#include "../../Project.h"
|
||||
#include "../../toolbars/ToolsToolBar.h"
|
||||
|
||||
#include "../ui/SelectHandle.h"
|
||||
#include "EnvelopeHandle.h"
|
||||
#include "../playabletrack/wavetrack/ui/SampleHandle.h"
|
||||
#include "ZoomHandle.h"
|
||||
@@ -40,10 +41,10 @@ HitTestResult Track::HitTest
|
||||
return ZoomHandle::HitAnywhere(event.event, pProject);
|
||||
case slideTool:
|
||||
return TimeShiftHandle::HitAnywhere(pProject);
|
||||
|
||||
case selectTool:
|
||||
return SelectHandle::HitTest(event, pProject, this);
|
||||
|
||||
default:
|
||||
// cases not yet implemented
|
||||
// fallthru
|
||||
;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user