2015-09-09 01:15:35 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
SelectHandle.cpp
|
|
|
|
|
|
|
|
Paul Licameli split from TrackPanel.cpp
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
#include "../../Audacity.h"
|
2015-09-09 01:15:35 +00:00
|
|
|
#include "SelectHandle.h"
|
|
|
|
|
|
|
|
#include "Scrubbing.h"
|
|
|
|
#include "TrackControls.h"
|
|
|
|
|
|
|
|
#include "../../AColor.h"
|
|
|
|
#include "../../FreqWindow.h"
|
|
|
|
#include "../../HitTestResult.h"
|
|
|
|
#include "../../MixerBoard.h"
|
|
|
|
#include "../../NumberScale.h"
|
|
|
|
#include "../../Project.h"
|
|
|
|
#include "../../RefreshCode.h"
|
|
|
|
#include "../../Snap.h"
|
|
|
|
#include "../../TrackPanel.h"
|
|
|
|
#include "../../TrackPanelMouseEvent.h"
|
|
|
|
#include "../../ViewInfo.h"
|
|
|
|
#include "../../WaveTrack.h"
|
|
|
|
#include "../../commands/Keyboard.h"
|
|
|
|
#include "../../ondemand/ODManager.h"
|
|
|
|
#include "../../prefs/SpectrogramSettings.h"
|
|
|
|
#include "../../toolbars/ToolsToolBar.h"
|
|
|
|
#include "../../../images/Cursors.h"
|
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
#include <wx/event.h>
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
// Only for definition of SonifyBeginModifyState:
|
|
|
|
//#include "../../NoteTrack.h"
|
|
|
|
|
|
|
|
#include "../../Experimental.h"
|
|
|
|
|
|
|
|
enum {
|
|
|
|
//This constant determines the size of the horizontal region (in pixels) around
|
|
|
|
//the right and left selection bounds that can be used for horizontal selection adjusting
|
|
|
|
//(or, vertical distance around top and bottom bounds in spectrograms,
|
|
|
|
// for vertical selection adjusting)
|
|
|
|
SELECTION_RESIZE_REGION = 3,
|
|
|
|
|
|
|
|
// Seems 4 is too small to work at the top. Why?
|
|
|
|
FREQ_SNAP_DISTANCE = 10,
|
|
|
|
};
|
|
|
|
|
|
|
|
// #define SPECTRAL_EDITING_ESC_KEY
|
|
|
|
|
2017-06-18 04:32:30 +00:00
|
|
|
bool SelectHandle::IsClicked() const
|
|
|
|
{
|
2017-11-07 09:14:00 +00:00
|
|
|
return mSelectionStateChanger.get() != NULL;
|
2017-06-18 04:32:30 +00:00
|
|
|
}
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
// If we're in OnDemand mode, we may change the tip.
|
|
|
|
void MaySetOnDemandTip(const Track * t, wxString &tip)
|
|
|
|
{
|
|
|
|
wxASSERT(t);
|
|
|
|
//For OD regions, we need to override and display the percent complete for this task.
|
|
|
|
//first, make sure it's a wavetrack.
|
|
|
|
if (t->GetKind() != Track::Wave)
|
|
|
|
return;
|
|
|
|
//see if the wavetrack exists in the ODManager (if the ODManager exists)
|
|
|
|
if (!ODManager::IsInstanceCreated())
|
|
|
|
return;
|
|
|
|
//ask the wavetrack for the corresponding tip - it may not change tip, but that's fine.
|
|
|
|
ODManager::Instance()->FillTipForWaveTrack(static_cast<const WaveTrack*>(t), tip);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a frequency to screen y position.
|
|
|
|
wxInt64 FrequencyToPosition(const WaveTrack *wt,
|
|
|
|
double frequency,
|
|
|
|
wxInt64 trackTopEdge,
|
|
|
|
int trackHeight)
|
|
|
|
{
|
|
|
|
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
|
|
|
|
float minFreq, maxFreq;
|
|
|
|
wt->GetSpectrumBounds(&minFreq, &maxFreq);
|
|
|
|
const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
|
|
|
|
const float p = numberScale.ValueToPosition(frequency);
|
|
|
|
return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Converts a position (mouse Y coordinate) to
|
|
|
|
/// frequency, in Hz.
|
|
|
|
double PositionToFrequency(const WaveTrack *wt,
|
|
|
|
bool maySnap,
|
|
|
|
wxInt64 mouseYCoordinate,
|
|
|
|
wxInt64 trackTopEdge,
|
|
|
|
int trackHeight)
|
|
|
|
{
|
|
|
|
const double rate = wt->GetRate();
|
|
|
|
|
|
|
|
// Handle snapping
|
|
|
|
if (maySnap &&
|
|
|
|
mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
|
|
|
|
return rate;
|
|
|
|
if (maySnap &&
|
|
|
|
trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
|
|
|
|
float minFreq, maxFreq;
|
|
|
|
wt->GetSpectrumBounds(&minFreq, &maxFreq);
|
|
|
|
const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
|
|
|
|
const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
|
|
|
|
return numberScale.PositionToValue(1.0 - p);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
inline void SetIfNotNull(T * pValue, const T Value)
|
|
|
|
{
|
|
|
|
if (pValue == NULL)
|
|
|
|
return;
|
|
|
|
*pValue = Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This returns true if we're a spectral editing track.
|
|
|
|
inline bool isSpectralSelectionTrack(const Track *pTrack) {
|
|
|
|
if (pTrack &&
|
|
|
|
pTrack->GetKind() == Track::Wave) {
|
|
|
|
const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
const SpectrogramSettings &settings = wt->GetSpectrogramSettings();
|
|
|
|
const int display = wt->GetDisplay();
|
|
|
|
return (display == WaveTrack::Spectrum) && settings.SpectralSelectionEnabled();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SelectionBoundary {
|
|
|
|
SBNone,
|
|
|
|
SBLeft, SBRight,
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
SBBottom, SBTop, SBCenter, SBWidth,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
SelectionBoundary ChooseTimeBoundary
|
2017-06-30 12:10:56 +00:00
|
|
|
(
|
|
|
|
const double t0, const double t1,
|
|
|
|
const ViewInfo &viewInfo,
|
2015-09-09 01:15:35 +00:00
|
|
|
double selend, bool onlyWithinSnapDistance,
|
|
|
|
wxInt64 *pPixelDist, double *pPinValue)
|
|
|
|
{
|
|
|
|
const wxInt64 posS = viewInfo.TimeToPosition(selend);
|
|
|
|
const wxInt64 pos0 = viewInfo.TimeToPosition(t0);
|
|
|
|
wxInt64 pixelDist = std::abs(posS - pos0);
|
|
|
|
bool chooseLeft = true;
|
|
|
|
|
2017-06-30 12:10:56 +00:00
|
|
|
if (t1<=t0)
|
2015-09-09 01:15:35 +00:00
|
|
|
// Special case when selection is a point, and thus left
|
|
|
|
// and right distances are the same
|
|
|
|
chooseLeft = (selend < t0);
|
|
|
|
else {
|
|
|
|
const wxInt64 pos1 = viewInfo.TimeToPosition(t1);
|
|
|
|
const wxInt64 rightDist = std::abs(posS - pos1);
|
|
|
|
if (rightDist < pixelDist)
|
|
|
|
chooseLeft = false, pixelDist = rightDist;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetIfNotNull(pPixelDist, pixelDist);
|
|
|
|
|
|
|
|
if (onlyWithinSnapDistance &&
|
|
|
|
pixelDist >= SELECTION_RESIZE_REGION) {
|
|
|
|
SetIfNotNull(pPinValue, -1.0);
|
|
|
|
return SBNone;
|
|
|
|
}
|
|
|
|
else if (chooseLeft) {
|
|
|
|
SetIfNotNull(pPinValue, t1);
|
|
|
|
return SBLeft;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SetIfNotNull(pPinValue, t0);
|
|
|
|
return SBRight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SelectionBoundary ChooseBoundary
|
|
|
|
(const ViewInfo &viewInfo,
|
2017-07-13 12:21:07 +00:00
|
|
|
wxCoord xx, wxCoord yy, const Track *pTrack, const wxRect &rect,
|
2015-09-09 01:15:35 +00:00
|
|
|
bool mayDragWidth, bool onlyWithinSnapDistance,
|
|
|
|
double *pPinValue = NULL)
|
|
|
|
{
|
|
|
|
// Choose one of four boundaries to adjust, or the center frequency.
|
|
|
|
// May choose frequencies only if in a spectrogram view and
|
|
|
|
// within the time boundaries.
|
|
|
|
// May choose no boundary if onlyWithinSnapDistance is true.
|
|
|
|
// Otherwise choose the eligible boundary nearest the mouse click.
|
2017-07-13 12:21:07 +00:00
|
|
|
const double selend = viewInfo.PositionToTime(xx, rect.x);
|
2015-09-09 01:15:35 +00:00
|
|
|
wxInt64 pixelDist = 0;
|
2017-06-30 12:10:56 +00:00
|
|
|
const double t0 = viewInfo.selectedRegion.t0();
|
|
|
|
const double t1 = viewInfo.selectedRegion.t1();
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
SelectionBoundary boundary =
|
2017-06-30 12:10:56 +00:00
|
|
|
ChooseTimeBoundary(t0,t1,viewInfo, selend, onlyWithinSnapDistance,
|
2015-09-09 01:15:35 +00:00
|
|
|
&pixelDist, pPinValue);
|
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2017-06-30 12:10:56 +00:00
|
|
|
//const double t0 = viewInfo.selectedRegion.t0();
|
|
|
|
//const double t1 = viewInfo.selectedRegion.t1();
|
2015-09-09 01:15:35 +00:00
|
|
|
const double f0 = viewInfo.selectedRegion.f0();
|
|
|
|
const double f1 = viewInfo.selectedRegion.f1();
|
|
|
|
const double fc = viewInfo.selectedRegion.fc();
|
|
|
|
double ratio = 0;
|
|
|
|
|
|
|
|
bool chooseTime = true;
|
|
|
|
bool chooseBottom = true;
|
|
|
|
bool chooseCenter = false;
|
|
|
|
// Consider adjustment of frequencies only if mouse is
|
|
|
|
// within the time boundaries
|
|
|
|
if (!viewInfo.selectedRegion.isPoint() &&
|
|
|
|
t0 <= selend && selend < t1 &&
|
|
|
|
isSpectralSelectionTrack(pTrack)) {
|
|
|
|
// Spectral selection track is always wave
|
|
|
|
const WaveTrack *const wt = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
const wxInt64 bottomSel = (f0 >= 0)
|
|
|
|
? FrequencyToPosition(wt, f0, rect.y, rect.height)
|
|
|
|
: rect.y + rect.height;
|
|
|
|
const wxInt64 topSel = (f1 >= 0)
|
|
|
|
? FrequencyToPosition(wt, f1, rect.y, rect.height)
|
|
|
|
: rect.y;
|
2017-07-13 12:21:07 +00:00
|
|
|
wxInt64 signedBottomDist = (int)(yy - bottomSel);
|
2015-09-09 01:15:35 +00:00
|
|
|
wxInt64 verticalDist = std::abs(signedBottomDist);
|
|
|
|
if (bottomSel == topSel)
|
|
|
|
// Top and bottom are too close to resolve on screen
|
|
|
|
chooseBottom = (signedBottomDist >= 0);
|
|
|
|
else {
|
2017-07-13 12:21:07 +00:00
|
|
|
const wxInt64 topDist = std::abs((int)(yy - topSel));
|
2015-09-09 01:15:35 +00:00
|
|
|
if (topDist < verticalDist)
|
|
|
|
chooseBottom = false, verticalDist = topDist;
|
|
|
|
}
|
|
|
|
if (fc > 0
|
|
|
|
#ifdef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
&& mayDragWidth
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
const wxInt64 centerSel =
|
|
|
|
FrequencyToPosition(wt, fc, rect.y, rect.height);
|
2017-07-13 12:21:07 +00:00
|
|
|
const wxInt64 centerDist = abs((int)(yy - centerSel));
|
2015-09-09 01:15:35 +00:00
|
|
|
if (centerDist < verticalDist)
|
|
|
|
chooseCenter = true, verticalDist = centerDist,
|
|
|
|
ratio = f1 / fc;
|
|
|
|
}
|
|
|
|
if (verticalDist >= 0 &&
|
|
|
|
verticalDist < pixelDist) {
|
|
|
|
pixelDist = verticalDist;
|
|
|
|
chooseTime = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chooseTime) {
|
|
|
|
// PRL: Seems I need a larger tolerance to make snapping work
|
|
|
|
// at top of track, not sure why
|
|
|
|
if (onlyWithinSnapDistance &&
|
|
|
|
pixelDist >= FREQ_SNAP_DISTANCE) {
|
|
|
|
SetIfNotNull(pPinValue, -1.0);
|
|
|
|
return SBNone;
|
|
|
|
}
|
|
|
|
else if (chooseCenter) {
|
|
|
|
SetIfNotNull(pPinValue, ratio);
|
|
|
|
return SBCenter;
|
|
|
|
}
|
|
|
|
else if (mayDragWidth && fc > 0) {
|
|
|
|
SetIfNotNull(pPinValue, fc);
|
|
|
|
return SBWidth;
|
|
|
|
}
|
|
|
|
else if (chooseBottom) {
|
|
|
|
SetIfNotNull(pPinValue, f1);
|
|
|
|
return SBBottom;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SetIfNotNull(pPinValue, f0);
|
|
|
|
return SBTop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
return boundary;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wxCursor *SelectCursor()
|
|
|
|
{
|
|
|
|
static auto selectCursor =
|
|
|
|
::MakeCursor(wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
|
|
|
|
return &*selectCursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxCursor *EnvelopeCursor()
|
|
|
|
{
|
|
|
|
// This one doubles as the center frequency cursor for spectral selection:
|
|
|
|
static auto envelopeCursor =
|
|
|
|
::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
|
|
|
|
return &*envelopeCursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetTipAndCursorForBoundary
|
|
|
|
(SelectionBoundary boundary, bool frequencySnapping,
|
|
|
|
wxString &tip, wxCursor *&pCursor)
|
|
|
|
{
|
|
|
|
static wxCursor adjustLeftSelectionCursor{ wxCURSOR_POINT_LEFT };
|
|
|
|
static wxCursor adjustRightSelectionCursor{ wxCURSOR_POINT_RIGHT };
|
|
|
|
|
|
|
|
static auto bottomFrequencyCursor =
|
|
|
|
::MakeCursor(wxCURSOR_ARROW, BottomFrequencyCursorXpm, 16, 16);
|
|
|
|
static auto topFrequencyCursor =
|
|
|
|
::MakeCursor(wxCURSOR_ARROW, TopFrequencyCursorXpm, 16, 16);
|
|
|
|
static auto bandWidthCursor =
|
|
|
|
::MakeCursor(wxCURSOR_ARROW, BandWidthCursorXpm, 16, 16);
|
|
|
|
|
|
|
|
switch (boundary) {
|
|
|
|
case SBNone:
|
|
|
|
pCursor = SelectCursor();
|
|
|
|
break;
|
|
|
|
case SBLeft:
|
|
|
|
tip = _("Click and drag to move left selection boundary.");
|
|
|
|
pCursor = &adjustLeftSelectionCursor;
|
|
|
|
break;
|
|
|
|
case SBRight:
|
|
|
|
tip = _("Click and drag to move right selection boundary.");
|
|
|
|
pCursor = &adjustRightSelectionCursor;
|
|
|
|
break;
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
case SBBottom:
|
|
|
|
tip = _("Click and drag to move bottom selection frequency.");
|
|
|
|
pCursor = &*bottomFrequencyCursor;
|
|
|
|
break;
|
|
|
|
case SBTop:
|
|
|
|
tip = _("Click and drag to move top selection frequency.");
|
|
|
|
pCursor = &*topFrequencyCursor;
|
|
|
|
break;
|
|
|
|
case SBCenter:
|
|
|
|
{
|
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
tip =
|
|
|
|
frequencySnapping ?
|
|
|
|
_("Click and drag to move center selection frequency to a spectral peak.") :
|
|
|
|
_("Click and drag to move center selection frequency.");
|
|
|
|
|
|
|
|
#else
|
|
|
|
shiftDown;
|
|
|
|
|
|
|
|
tip =
|
|
|
|
_("Click and drag to move center selection frequency.");
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
pCursor = EnvelopeCursor();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SBWidth:
|
|
|
|
tip = _("Click and drag to adjust frequency bandwidth.");
|
|
|
|
pCursor = &*bandWidthCursor;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
} // switch
|
|
|
|
// Falls through the switch if there was no boundary found.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-04 04:48:09 +00:00
|
|
|
UIHandlePtr SelectHandle::HitTest
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
(std::weak_ptr<SelectHandle> &holder,
|
|
|
|
const TrackPanelMouseState &st, const AudacityProject *pProject,
|
2017-06-27 17:40:36 +00:00
|
|
|
const std::shared_ptr<Track> &pTrack)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
2017-07-12 16:54:11 +00:00
|
|
|
// This handle is a little special because there may be some state to
|
|
|
|
// preserve during movement before the click.
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
auto old = holder.lock();
|
2017-07-12 16:09:35 +00:00
|
|
|
bool oldUseSnap = true;
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
if (old) {
|
|
|
|
// It should not have started listening to timer events
|
2017-07-18 18:10:29 +00:00
|
|
|
if( old->mTimerHandler ) {
|
|
|
|
wxASSERT(false);
|
|
|
|
// Handle this eventuality anyway, don't leave a dangling back-pointer
|
|
|
|
// in the attached event handler.
|
|
|
|
old->mTimerHandler.reset();
|
|
|
|
}
|
2017-07-12 16:09:35 +00:00
|
|
|
oldUseSnap = old->mUseSnap;
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
}
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
2017-07-12 21:47:44 +00:00
|
|
|
auto result = std::make_shared<SelectHandle>(
|
2017-07-12 16:09:35 +00:00
|
|
|
pTrack, oldUseSnap, *pProject->GetTracks(), st, viewInfo );
|
|
|
|
|
2017-07-12 21:47:44 +00:00
|
|
|
result = AssignUIHandlePtr(holder, result);
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
//Make sure we are within the selected track
|
|
|
|
// Adjusting the selection edges can be turned off in
|
|
|
|
// the preferences...
|
|
|
|
if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
|
|
|
|
{
|
2017-07-04 04:48:09 +00:00
|
|
|
return result;
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2017-07-12 21:47:44 +00:00
|
|
|
const wxRect &rect = st.rect;
|
2015-09-09 01:15:35 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2017-07-04 04:48:09 +00:00
|
|
|
return result;
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 16:09:35 +00:00
|
|
|
UIHandle::Result SelectHandle::NeedChangeHighlight
|
|
|
|
(const SelectHandle &oldState, const SelectHandle &newState)
|
|
|
|
{
|
|
|
|
auto useSnap = oldState.mUseSnap;
|
2017-07-23 03:54:56 +00:00
|
|
|
// This is guaranteed when constructing the NEW handle:
|
2017-07-12 16:09:35 +00:00
|
|
|
wxASSERT( useSnap == newState.mUseSnap );
|
|
|
|
if (!useSnap)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
auto &oldSnapState = oldState.mSnapStart;
|
|
|
|
auto &newSnapState = newState.mSnapStart;
|
|
|
|
if ( oldSnapState.Snapped() == newSnapState.Snapped() &&
|
|
|
|
(!oldSnapState.Snapped() ||
|
|
|
|
oldSnapState.outCoord == newSnapState.outCoord) )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return RefreshCode::RefreshAll;
|
|
|
|
}
|
|
|
|
|
2017-07-12 21:47:44 +00:00
|
|
|
SelectHandle::SelectHandle
|
2017-07-12 16:09:35 +00:00
|
|
|
( const std::shared_ptr<Track> &pTrack, bool useSnap,
|
|
|
|
const TrackList &trackList,
|
2017-07-12 21:47:44 +00:00
|
|
|
const TrackPanelMouseState &st, const ViewInfo &viewInfo )
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
: mpTrack{ pTrack }
|
2017-07-12 21:47:44 +00:00
|
|
|
, mSnapManager{ std::make_shared<SnapManager>(&trackList, &viewInfo) }
|
|
|
|
{
|
|
|
|
const wxMouseState &state = st.state;
|
2017-07-18 15:10:45 +00:00
|
|
|
mRect = st.rect;
|
2017-07-12 21:47:44 +00:00
|
|
|
|
2017-07-18 15:10:45 +00:00
|
|
|
auto time = std::max(0.0, viewInfo.PositionToTime(state.m_x, mRect.x));
|
2017-07-12 21:47:44 +00:00
|
|
|
mSnapStart = mSnapManager->Snap(pTrack.get(), time, false);
|
|
|
|
if (mSnapStart.snappedPoint)
|
2017-07-18 15:10:45 +00:00
|
|
|
mSnapStart.outCoord += mRect.x;
|
2017-07-12 21:47:44 +00:00
|
|
|
else
|
|
|
|
mSnapStart.outCoord = -1;
|
2017-07-12 16:09:35 +00:00
|
|
|
|
|
|
|
mUseSnap = useSnap;
|
2017-07-12 21:47:44 +00:00
|
|
|
}
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
SelectHandle::~SelectHandle()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
// Is the distance between A and B less than D?
|
|
|
|
template < class A, class B, class DIST > bool within(A a, B b, DIST d)
|
|
|
|
{
|
|
|
|
return (a > b - d) && (a < b + d);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline double findMaxRatio(double center, double rate)
|
|
|
|
{
|
|
|
|
const double minFrequency = 1.0;
|
|
|
|
const double maxFrequency = (rate / 2.0);
|
|
|
|
const double frequency =
|
|
|
|
std::min(maxFrequency,
|
|
|
|
std::max(minFrequency, center));
|
|
|
|
return
|
|
|
|
std::min(frequency / minFrequency, maxFrequency / frequency);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-16 00:13:18 +00:00
|
|
|
void SelectHandle::Enter(bool)
|
2017-07-12 16:09:35 +00:00
|
|
|
{
|
2017-07-16 00:13:18 +00:00
|
|
|
SetUseSnap(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::SetUseSnap(bool use)
|
|
|
|
{
|
|
|
|
mUseSnap = use;
|
2017-07-12 16:09:35 +00:00
|
|
|
|
2017-07-16 00:13:18 +00:00
|
|
|
bool hasSnap = HasSnap();
|
2017-07-12 16:09:35 +00:00
|
|
|
if (hasSnap)
|
|
|
|
// Repaint to turn the snap lines on or off
|
|
|
|
mChangeHighlight = RefreshCode::RefreshAll;
|
|
|
|
|
2017-07-21 02:10:40 +00:00
|
|
|
if (IsClicked()) {
|
2017-07-12 16:09:35 +00:00
|
|
|
// Readjust the moving selection end
|
|
|
|
AssignSelection(
|
|
|
|
::GetActiveProject()->GetViewInfo(),
|
|
|
|
mUseSnap ? mSnapEnd.outTime : mSnapEnd.timeSnappedTime,
|
|
|
|
nullptr);
|
2017-07-21 02:10:40 +00:00
|
|
|
mChangeHighlight |= RefreshCode::UpdateSelection;
|
|
|
|
}
|
2017-07-12 16:09:35 +00:00
|
|
|
}
|
|
|
|
|
2017-07-16 00:13:18 +00:00
|
|
|
bool SelectHandle::HasSnap() const
|
2017-07-12 16:09:35 +00:00
|
|
|
{
|
|
|
|
return
|
2017-07-16 00:13:18 +00:00
|
|
|
(IsClicked() ? mSnapEnd : mSnapStart).snappedPoint;
|
2017-07-12 16:09:35 +00:00
|
|
|
}
|
|
|
|
|
2017-07-16 00:13:18 +00:00
|
|
|
bool SelectHandle::HasEscape() const
|
2017-07-12 16:09:35 +00:00
|
|
|
{
|
2017-07-16 00:13:18 +00:00
|
|
|
return HasSnap() && mUseSnap;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SelectHandle::Escape()
|
|
|
|
{
|
|
|
|
if (SelectHandle::HasEscape()) {
|
|
|
|
SetUseSnap(false);
|
|
|
|
return true;
|
2017-07-12 16:09:35 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
UIHandle::Result SelectHandle::Click
|
|
|
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
|
|
|
{
|
|
|
|
/// This method gets called when we're handling selection
|
|
|
|
/// and the mouse was just clicked.
|
|
|
|
|
|
|
|
using namespace RefreshCode;
|
|
|
|
|
|
|
|
wxMouseEvent &event = evt.event;
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
const auto sTrack = pProject->GetTracks()->Lock(mpTrack);
|
|
|
|
const auto pTrack = sTrack.get();
|
2015-09-09 01:15:35 +00:00
|
|
|
ViewInfo &viewInfo = pProject->GetViewInfo();
|
|
|
|
|
|
|
|
mMostRecentX = event.m_x;
|
|
|
|
mMostRecentY = event.m_y;
|
|
|
|
|
|
|
|
TrackPanel *const trackPanel = pProject->GetTrackPanel();
|
|
|
|
|
|
|
|
if( pTrack->GetKind() == Track::Label &&
|
|
|
|
event.LeftDown() &&
|
|
|
|
event.ControlDown() ){
|
|
|
|
// We should reach this, only in default of other hits on glyphs or
|
|
|
|
// text boxes.
|
|
|
|
bool bShift = event.ShiftDown();
|
|
|
|
bool unsafe = pProject->IsAudioActive();
|
|
|
|
pProject->HandleListSelection(pTrack, bShift, true, !unsafe);
|
|
|
|
// Do not start a drag
|
|
|
|
return RefreshAll | Cancelled;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto &selectionState = pProject->GetSelectionState();
|
|
|
|
if (event.LeftDClick() && !event.ShiftDown()) {
|
|
|
|
TrackList *const trackList = pProject->GetTracks();
|
|
|
|
|
|
|
|
// Deselect all other tracks and select this one.
|
|
|
|
selectionState.SelectNone( *trackList, pProject->GetMixerBoard() );
|
|
|
|
|
|
|
|
selectionState.SelectTrack
|
|
|
|
( *trackList, *pTrack, true, true, pProject->GetMixerBoard() );
|
|
|
|
|
|
|
|
// Default behavior: select whole track
|
|
|
|
SelectionState::SelectTrackLength
|
|
|
|
( *trackList, viewInfo, *pTrack, pProject->IsSyncLocked() );
|
|
|
|
|
|
|
|
// Special case: if we're over a clip in a WaveTrack,
|
|
|
|
// select just that clip
|
|
|
|
if (pTrack->GetKind() == Track::Wave) {
|
|
|
|
WaveTrack *const wt = static_cast<WaveTrack *>(pTrack);
|
|
|
|
WaveClip *const selectedClip = wt->GetClipAtX(event.m_x);
|
|
|
|
if (selectedClip) {
|
|
|
|
viewInfo.selectedRegion.setTimes(
|
|
|
|
selectedClip->GetOffset(), selectedClip->GetEndTime());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pProject->ModifyState(false);
|
|
|
|
|
|
|
|
// Do not start a drag
|
|
|
|
return RefreshAll | UpdateSelection | Cancelled;
|
|
|
|
}
|
|
|
|
else if (!event.LeftDown())
|
|
|
|
return Cancelled;
|
|
|
|
|
|
|
|
mInitialSelection = viewInfo.selectedRegion;
|
|
|
|
|
|
|
|
TrackList *const trackList = pProject->GetTracks();
|
2017-07-12 16:54:11 +00:00
|
|
|
mSelectionStateChanger = std::make_shared< SelectionStateChanger >
|
2015-09-09 01:15:35 +00:00
|
|
|
( selectionState, *trackList );
|
|
|
|
|
|
|
|
mSelectionBoundary = 0;
|
|
|
|
|
|
|
|
bool bShiftDown = event.ShiftDown();
|
|
|
|
bool bCtrlDown = event.ControlDown();
|
|
|
|
|
|
|
|
auto pMixerBoard = pProject->GetMixerBoard();
|
|
|
|
|
2017-07-12 16:09:35 +00:00
|
|
|
mSelStart = mUseSnap ? mSnapStart.outTime : mSnapStart.timeSnappedTime;
|
2017-07-18 20:06:25 +00:00
|
|
|
auto xx = viewInfo.TimeToPosition(mSelStart, mRect.x);
|
2017-07-12 21:47:44 +00:00
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
// I. Shift-click adjusts an existing selection
|
|
|
|
if (bShiftDown || bCtrlDown) {
|
|
|
|
if (bShiftDown)
|
|
|
|
selectionState.ChangeSelectionOnShiftClick
|
|
|
|
( *trackList, *pTrack, pMixerBoard );
|
|
|
|
if( bCtrlDown ){
|
|
|
|
//Commented out bIsSelected toggles, as in Track Control Panel.
|
|
|
|
//bool bIsSelected = pTrack->GetSelected();
|
|
|
|
//Actual bIsSelected will always add.
|
|
|
|
bool bIsSelected = false;
|
|
|
|
// Don't toggle away the last selected track.
|
|
|
|
if( !bIsSelected || trackPanel->GetSelectedTrackCount() > 1 )
|
|
|
|
selectionState.SelectTrack
|
|
|
|
( *trackList, *pTrack, !bIsSelected, true, pMixerBoard );
|
|
|
|
}
|
|
|
|
|
|
|
|
double value;
|
|
|
|
// Shift-click, choose closest boundary
|
|
|
|
SelectionBoundary boundary =
|
2017-07-18 20:06:25 +00:00
|
|
|
ChooseBoundary(viewInfo, xx, event.m_y, pTrack, mRect, false, false, &value);
|
2015-09-09 01:15:35 +00:00
|
|
|
mSelectionBoundary = boundary;
|
|
|
|
switch (boundary) {
|
|
|
|
case SBLeft:
|
|
|
|
case SBRight:
|
|
|
|
{
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
// If drag starts, change time selection only
|
|
|
|
// (also exit frequency snapping)
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
#endif
|
|
|
|
mSelStartValid = true;
|
2017-07-21 02:10:40 +00:00
|
|
|
mSelStart = value;
|
2017-07-21 14:54:43 +00:00
|
|
|
mSnapStart = SnapResults{};
|
2017-06-28 04:31:18 +00:00
|
|
|
AdjustSelection(pProject, viewInfo, event.m_x, mRect.x, pTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
case SBBottom:
|
|
|
|
case SBTop:
|
|
|
|
{
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelPin = value;
|
|
|
|
mFreqSelMode =
|
|
|
|
(boundary == SBBottom)
|
|
|
|
? FREQ_SEL_BOTTOM_FREE : FREQ_SEL_TOP_FREE;
|
|
|
|
|
|
|
|
// Drag frequency only, not time:
|
|
|
|
mSelStartValid = false;
|
2017-06-24 21:04:07 +00:00
|
|
|
AdjustFreqSelection(
|
|
|
|
static_cast<WaveTrack*>(pTrack),
|
|
|
|
viewInfo, event.m_y, mRect.y, mRect.height);
|
2015-09-09 01:15:35 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SBCenter:
|
|
|
|
{
|
|
|
|
const auto wt = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
HandleCenterFrequencyClick(viewInfo, true, wt, value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
// For persistence of the selection change:
|
|
|
|
pProject->ModifyState(false);
|
|
|
|
|
|
|
|
// Get timer events so we can auto-scroll
|
|
|
|
Connect(pProject);
|
|
|
|
|
|
|
|
// Full refresh since the label area may need to indicate
|
|
|
|
// newly selected tracks.
|
|
|
|
return RefreshAll | UpdateSelection;
|
|
|
|
}
|
|
|
|
|
2017-07-23 03:54:56 +00:00
|
|
|
// II. Unmodified click starts a NEW selection
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
//Make sure you are within the selected track
|
|
|
|
bool startNewSelection = true;
|
2017-06-24 21:04:07 +00:00
|
|
|
if (pTrack && pTrack->GetSelected()) {
|
2015-09-09 01:15:35 +00:00
|
|
|
// Adjusting selection edges can be turned off in the
|
|
|
|
// preferences now
|
|
|
|
if (viewInfo.bAdjustSelectionEdges) {
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
|
2017-06-24 21:04:07 +00:00
|
|
|
isSpectralSelectionTrack(pTrack)) {
|
2015-09-09 01:15:35 +00:00
|
|
|
// This code is no longer reachable, but it had a place in the
|
|
|
|
// spectral selection prototype. It used to be that you could be
|
|
|
|
// in a center-frequency-snapping mode that was not a mouse drag
|
|
|
|
// but responded to mouse movements. Click exited that and dragged
|
|
|
|
// width instead. PRL.
|
|
|
|
|
|
|
|
// Ignore whether we are inside the time selection.
|
|
|
|
// Exit center-snapping, start dragging the width.
|
|
|
|
mFreqSelMode = FREQ_SEL_PINNED_CENTER;
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelPin = viewInfo.selectedRegion.fc();
|
|
|
|
// Do not adjust time boundaries
|
|
|
|
mSelStartValid = false;
|
2017-06-24 21:04:07 +00:00
|
|
|
AdjustFreqSelection(
|
|
|
|
static_cast<WaveTrack*>(pTrack),
|
|
|
|
viewInfo, event.m_y, mRect.y, mRect.height);
|
2015-09-09 01:15:35 +00:00
|
|
|
// For persistence of the selection change:
|
|
|
|
pProject->ModifyState(false);
|
|
|
|
mSelectionBoundary = SBWidth;
|
|
|
|
return UpdateSelection;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
// Not shift-down, choose boundary only within snapping
|
|
|
|
double value;
|
|
|
|
SelectionBoundary boundary =
|
2017-07-18 20:06:25 +00:00
|
|
|
ChooseBoundary(viewInfo, xx, event.m_y, pTrack, mRect, true, true, &value);
|
2015-09-09 01:15:35 +00:00
|
|
|
mSelectionBoundary = boundary;
|
|
|
|
switch (boundary) {
|
|
|
|
case SBNone:
|
|
|
|
// startNewSelection remains true
|
|
|
|
break;
|
|
|
|
case SBLeft:
|
|
|
|
case SBRight:
|
|
|
|
startNewSelection = false;
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
// Disable frequency selection
|
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
#endif
|
|
|
|
mSelStartValid = true;
|
2017-07-21 02:10:40 +00:00
|
|
|
mSelStart = value;
|
2017-07-21 14:54:43 +00:00
|
|
|
mSnapStart = SnapResults{};
|
2015-09-09 01:15:35 +00:00
|
|
|
break;
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
case SBBottom:
|
|
|
|
case SBTop:
|
|
|
|
case SBWidth:
|
|
|
|
startNewSelection = false;
|
|
|
|
// Disable time selection
|
|
|
|
mSelStartValid = false;
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelPin = value;
|
|
|
|
mFreqSelMode =
|
|
|
|
(boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
|
|
|
|
(boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
|
|
|
|
FREQ_SEL_TOP_FREE;
|
|
|
|
break;
|
|
|
|
case SBCenter:
|
|
|
|
{
|
|
|
|
const auto wt = static_cast<const WaveTrack*>(pTrack);
|
|
|
|
HandleCenterFrequencyClick(viewInfo, false, wt, value);
|
|
|
|
startNewSelection = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // bAdjustSelectionEdges
|
|
|
|
}
|
|
|
|
|
2017-07-23 03:54:56 +00:00
|
|
|
// III. Common case for starting a NEW selection
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
if (startNewSelection) {
|
|
|
|
// If we didn't move a selection boundary, start a NEW selection
|
|
|
|
selectionState.SelectNone( *trackList, pMixerBoard );
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
2017-06-24 21:04:07 +00:00
|
|
|
StartFreqSelection (viewInfo, event.m_y, mRect.y, mRect.height, pTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
#endif
|
2017-07-12 21:47:44 +00:00
|
|
|
StartSelection(pProject);
|
2015-09-09 01:15:35 +00:00
|
|
|
selectionState.SelectTrack
|
2017-06-24 21:04:07 +00:00
|
|
|
( *trackList, *pTrack, true, true, pMixerBoard );
|
|
|
|
trackPanel->SetFocusedTrack(pTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
//On-Demand: check to see if there is an OD thing associated with this track.
|
2017-06-24 21:04:07 +00:00
|
|
|
if (pTrack->GetKind() == Track::Wave) {
|
2015-09-09 01:15:35 +00:00
|
|
|
if(ODManager::IsInstanceCreated())
|
|
|
|
ODManager::Instance()->DemandTrackUpdate
|
2017-06-24 21:04:07 +00:00
|
|
|
(static_cast<WaveTrack*>(pTrack),mSelStart);
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Connect(pProject);
|
|
|
|
return RefreshAll | UpdateSelection;
|
|
|
|
}
|
2017-11-05 15:39:25 +00:00
|
|
|
else {
|
|
|
|
Connect(pProject);
|
|
|
|
return RefreshAll;
|
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
UIHandle::Result SelectHandle::Drag
|
|
|
|
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
|
|
|
|
{
|
|
|
|
using namespace RefreshCode;
|
|
|
|
|
|
|
|
ViewInfo &viewInfo = pProject->GetViewInfo();
|
|
|
|
const wxMouseEvent &event = evt.event;
|
|
|
|
|
|
|
|
int x = mAutoScrolling ? mMostRecentX : event.m_x;
|
|
|
|
int y = mAutoScrolling ? mMostRecentY : event.m_y;
|
|
|
|
mMostRecentX = x;
|
|
|
|
mMostRecentY = y;
|
|
|
|
|
|
|
|
/// AS: If we're dragging to adjust a selection (or actually,
|
|
|
|
/// if the screen is scrolling while you're selecting), we
|
|
|
|
/// handle it here.
|
|
|
|
|
|
|
|
// Fuhggeddaboudit if we're not dragging and not autoscrolling.
|
|
|
|
if (!event.Dragging() && !mAutoScrolling)
|
|
|
|
return RefreshNone;
|
|
|
|
|
|
|
|
if (event.CmdDown()) {
|
|
|
|
// Ctrl-drag has no meaning, fuhggeddaboudit
|
|
|
|
// JKC YES it has meaning.
|
|
|
|
//return RefreshNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also fuhggeddaboudit if not in a track.
|
2017-06-28 04:31:18 +00:00
|
|
|
auto pTrack = pProject->GetTracks()->Lock(mpTrack);
|
2017-06-24 21:04:07 +00:00
|
|
|
if (!pTrack)
|
2015-09-09 01:15:35 +00:00
|
|
|
return RefreshNone;
|
|
|
|
|
|
|
|
// JKC: Logic to prevent a selection smaller than 5 pixels to
|
|
|
|
// prevent accidental dragging when selecting.
|
|
|
|
// (if user really wants a tiny selection, they should zoom in).
|
|
|
|
// Can someone make this value of '5' configurable in
|
|
|
|
// preferences?
|
|
|
|
enum { minimumSizedSelection = 5 }; //measured in pixels
|
|
|
|
|
|
|
|
// Might be dragging frequency bounds only, test
|
|
|
|
if (mSelStartValid) {
|
|
|
|
wxInt64 SelStart = viewInfo.TimeToPosition(mSelStart, mRect.x); //cvt time to pixels.
|
|
|
|
// Abandon this drag if selecting < 5 pixels.
|
|
|
|
if (wxLongLong(SelStart - x).Abs() < minimumSizedSelection)
|
|
|
|
return RefreshNone;
|
|
|
|
}
|
|
|
|
|
2017-06-23 22:08:15 +00:00
|
|
|
if ( auto clickedTrack =
|
2017-06-27 17:40:36 +00:00
|
|
|
static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack() ) {
|
2017-06-23 22:08:15 +00:00
|
|
|
// Handle which tracks are selected
|
|
|
|
Track *sTrack = pTrack.get();
|
2017-06-27 18:12:23 +00:00
|
|
|
Track *eTrack = clickedTrack.get();
|
2017-06-23 22:08:15 +00:00
|
|
|
auto trackList = pProject->GetTracks();
|
|
|
|
auto pMixerBoard = pProject->GetMixerBoard();
|
|
|
|
if ( sTrack && eTrack && !event.ControlDown() ) {
|
|
|
|
auto &selectionState = pProject->GetSelectionState();
|
|
|
|
selectionState.SelectRangeOfTracks
|
2015-09-09 01:15:35 +00:00
|
|
|
( *trackList, *sTrack, *eTrack, pMixerBoard );
|
2017-06-23 22:08:15 +00:00
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
2017-06-23 22:08:15 +00:00
|
|
|
if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
|
|
|
|
!viewInfo.selectedRegion.isPoint())
|
|
|
|
MoveSnappingFreqSelection
|
2017-06-24 21:04:07 +00:00
|
|
|
(pProject, viewInfo, y, mRect.y, mRect.height, pTrack.get());
|
2017-06-23 22:08:15 +00:00
|
|
|
else
|
2015-09-09 01:15:35 +00:00
|
|
|
#endif
|
2017-06-28 04:31:18 +00:00
|
|
|
if (pProject->GetTracks()->Lock(mFreqSelTrack) == pTrack)
|
2017-06-23 22:08:15 +00:00
|
|
|
AdjustFreqSelection(
|
|
|
|
static_cast<WaveTrack*>(pTrack.get()),
|
|
|
|
viewInfo, y, mRect.y, mRect.height);
|
2015-09-09 01:15:35 +00:00
|
|
|
#endif
|
2017-06-23 22:08:15 +00:00
|
|
|
|
2017-06-28 04:31:18 +00:00
|
|
|
AdjustSelection(pProject, viewInfo, x, mRect.x, clickedTrack.get());
|
2017-06-23 22:08:15 +00:00
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
return RefreshNone
|
|
|
|
|
|
|
|
// If scrubbing does not use the helper poller thread, then
|
|
|
|
// don't refresh at every mouse event, because it slows down seek-scrub.
|
|
|
|
// Instead, let OnTimer do it, which is often enough.
|
|
|
|
// And even if scrubbing does use the thread, then skipping refresh does not
|
|
|
|
// bring that advantage, but it is probably still a good idea anyway.
|
|
|
|
|
|
|
|
// | UpdateSelection
|
|
|
|
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
HitTestPreview SelectHandle::Preview
|
2017-06-18 04:32:30 +00:00
|
|
|
(const TrackPanelMouseState &st, const AudacityProject *pProject)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
2017-07-16 00:13:18 +00:00
|
|
|
if (!HasSnap() && !mUseSnap)
|
|
|
|
// Moved out of snapping; revert to un-escaped state
|
2017-07-12 16:09:35 +00:00
|
|
|
mUseSnap = true;
|
|
|
|
|
2017-06-18 04:32:30 +00:00
|
|
|
auto pTrack = mpTrack.lock();
|
|
|
|
if (!pTrack)
|
|
|
|
return {};
|
|
|
|
|
2015-09-09 01:15:35 +00:00
|
|
|
wxString tip;
|
2017-06-18 04:32:30 +00:00
|
|
|
wxCursor *pCursor = SelectCursor();
|
|
|
|
if ( IsClicked() )
|
|
|
|
// Use same cursor as at the clck
|
|
|
|
SetTipAndCursorForBoundary
|
|
|
|
(SelectionBoundary(mSelectionBoundary),
|
|
|
|
(mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
|
|
|
|
tip, pCursor);
|
|
|
|
else {
|
|
|
|
// Choose one of many cursors for mouse-over
|
|
|
|
|
|
|
|
const ViewInfo &viewInfo = pProject->GetViewInfo();
|
|
|
|
|
2017-07-12 16:09:35 +00:00
|
|
|
auto &state = st.state;
|
|
|
|
auto time = mUseSnap ? mSnapStart.outTime : mSnapStart.timeSnappedTime;
|
|
|
|
auto xx = viewInfo.TimeToPosition(time, mRect.x);
|
|
|
|
|
2017-06-18 04:32:30 +00:00
|
|
|
const bool bMultiToolMode =
|
|
|
|
pProject->GetToolsToolBar()->IsDown(multiTool);
|
|
|
|
|
|
|
|
//In Multi-tool mode, give multitool prompt if no-special-hit.
|
|
|
|
if (bMultiToolMode) {
|
|
|
|
// Look up the current key binding for Preferences.
|
|
|
|
// (Don't assume it's the default!)
|
|
|
|
wxString keyStr
|
|
|
|
(pProject->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
|
|
|
|
if (keyStr.IsEmpty())
|
|
|
|
// No keyboard preference defined for opening Preferences dialog
|
|
|
|
/* i18n-hint: These are the names of a menu and a command in that menu */
|
|
|
|
keyStr = _("Edit, Preferences...");
|
|
|
|
else
|
|
|
|
keyStr = KeyStringDisplay(keyStr);
|
|
|
|
/* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
|
|
|
|
tip = wxString::Format(
|
|
|
|
_("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
|
2017-10-09 05:03:14 +00:00
|
|
|
keyStr);
|
2017-06-18 04:32:30 +00:00
|
|
|
// Later in this function we may point to some other string instead.
|
|
|
|
if (!pTrack->GetSelected() ||
|
|
|
|
!viewInfo.bAdjustSelectionEdges)
|
|
|
|
;
|
|
|
|
else {
|
|
|
|
const wxRect &rect = st.rect;
|
|
|
|
const bool bShiftDown = state.ShiftDown();
|
|
|
|
const bool bCtrlDown = state.ControlDown();
|
|
|
|
const bool bModifierDown = bShiftDown || bCtrlDown;
|
|
|
|
|
|
|
|
// If not shift-down and not snapping center, then
|
|
|
|
// choose boundaries only in snapping tolerance,
|
|
|
|
// and may choose center.
|
|
|
|
SelectionBoundary boundary =
|
2017-07-12 16:09:35 +00:00
|
|
|
ChooseBoundary(viewInfo, xx, state.m_y, pTrack.get(), rect, !bModifierDown, !bModifierDown);
|
2017-06-18 04:32:30 +00:00
|
|
|
|
|
|
|
SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// This is a vestige of an idea in the prototype version.
|
|
|
|
// Center would snap without mouse button down, click would pin the center
|
|
|
|
// and drag width.
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
|
|
if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
|
|
|
|
isSpectralSelectionTrack(pTrack)) {
|
|
|
|
// Not shift-down, but center frequency snapping toggle is on
|
|
|
|
tip = _("Click and drag to set frequency bandwidth.");
|
|
|
|
pCursor = &*envelopeCursor;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
|
|
|
|
;
|
|
|
|
else {
|
|
|
|
const wxRect &rect = st.rect;
|
|
|
|
const bool bShiftDown = state.ShiftDown();
|
|
|
|
const bool bCtrlDown = state.ControlDown();
|
|
|
|
const bool bModifierDown = bShiftDown || bCtrlDown;
|
|
|
|
SelectionBoundary boundary = ChooseBoundary(
|
2017-07-12 16:09:35 +00:00
|
|
|
viewInfo, xx, state.m_y, pTrack.get(), rect, !bModifierDown, !bModifierDown);
|
2017-06-18 04:32:30 +00:00
|
|
|
SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
MaySetOnDemandTip(pTrack.get(), tip);
|
|
|
|
}
|
2017-06-28 02:35:19 +00:00
|
|
|
if (tip == "") {
|
2017-07-17 15:39:32 +00:00
|
|
|
tip = _("Click and drag to select audio");
|
2017-06-28 02:35:19 +00:00
|
|
|
}
|
2017-07-16 00:13:18 +00:00
|
|
|
if (HasEscape() && mUseSnap) {
|
|
|
|
tip += wxT(" ") +
|
2017-07-14 02:28:55 +00:00
|
|
|
/* i18n-hint: "Snapping" means automatic alignment of selection edges to any nearby label or clip boundaries */
|
2017-07-16 00:13:18 +00:00
|
|
|
_("(snapping)");
|
2017-07-14 02:28:55 +00:00
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
return { tip, pCursor };
|
|
|
|
}
|
|
|
|
|
|
|
|
UIHandle::Result SelectHandle::Release
|
|
|
|
(const TrackPanelMouseEvent &, AudacityProject *pProject,
|
|
|
|
wxWindow *)
|
|
|
|
{
|
|
|
|
using namespace RefreshCode;
|
|
|
|
pProject->ModifyState(false);
|
|
|
|
mFrequencySnapper.reset();
|
|
|
|
mSnapManager.reset();
|
|
|
|
if (mSelectionStateChanger) {
|
|
|
|
mSelectionStateChanger->Commit();
|
|
|
|
mSelectionStateChanger.reset();
|
|
|
|
}
|
|
|
|
|
2017-07-12 16:09:35 +00:00
|
|
|
if (mUseSnap && (mSnapStart.outCoord != -1 || mSnapEnd.outCoord != -1))
|
2015-09-09 01:15:35 +00:00
|
|
|
return RefreshAll;
|
|
|
|
else
|
|
|
|
return RefreshNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
UIHandle::Result SelectHandle::Cancel(AudacityProject *pProject)
|
|
|
|
{
|
|
|
|
mSelectionStateChanger.reset();
|
|
|
|
pProject->GetViewInfo().selectedRegion = mInitialSelection;
|
|
|
|
|
|
|
|
// Refresh mixer board for change of set of selected tracks
|
|
|
|
if (MixerBoard* pMixerBoard = pProject->GetMixerBoard())
|
|
|
|
pMixerBoard->Refresh();
|
|
|
|
|
|
|
|
return RefreshCode::RefreshAll;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::DrawExtras
|
|
|
|
(DrawingPass pass, wxDC * dc, const wxRegion &, const wxRect &)
|
|
|
|
{
|
|
|
|
if (pass == Panel) {
|
|
|
|
// Draw snap guidelines if we have any
|
2017-07-16 00:13:18 +00:00
|
|
|
if ( mSnapManager ) {
|
|
|
|
auto coord1 = (mUseSnap || IsClicked()) ? mSnapStart.outCoord : -1;
|
|
|
|
auto coord2 = (!mUseSnap || !IsClicked()) ? -1 : mSnapEnd.outCoord;
|
|
|
|
mSnapManager->Draw( dc, coord1, coord2 );
|
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::Connect(AudacityProject *pProject)
|
|
|
|
{
|
2017-07-12 16:54:11 +00:00
|
|
|
mTimerHandler = std::make_shared<TimerHandler>( this, pProject );
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
class SelectHandle::TimerHandler : public wxEvtHandler
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
2017-07-12 16:54:11 +00:00
|
|
|
public:
|
|
|
|
TimerHandler( SelectHandle *pParent, AudacityProject *pProject )
|
|
|
|
: mParent{ pParent }
|
|
|
|
, mConnectedProject{ pProject }
|
|
|
|
{
|
|
|
|
if (mConnectedProject)
|
|
|
|
mConnectedProject->Connect(EVT_TRACK_PANEL_TIMER,
|
|
|
|
wxCommandEventHandler(SelectHandle::TimerHandler::OnTimer),
|
|
|
|
NULL,
|
|
|
|
this);
|
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
~TimerHandler()
|
|
|
|
{
|
|
|
|
if (mConnectedProject)
|
|
|
|
mConnectedProject->Disconnect(EVT_TRACK_PANEL_TIMER,
|
|
|
|
wxCommandEventHandler(SelectHandle::TimerHandler::OnTimer),
|
|
|
|
NULL,
|
|
|
|
this);
|
|
|
|
}
|
2015-09-09 01:15:35 +00:00
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
// Receives timer event notifications, to implement auto-scroll
|
|
|
|
void OnTimer(wxCommandEvent &event);
|
|
|
|
|
|
|
|
private:
|
|
|
|
SelectHandle *mParent;
|
|
|
|
AudacityProject *mConnectedProject;
|
|
|
|
};
|
2015-09-09 01:15:35 +00:00
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
void SelectHandle::TimerHandler::OnTimer(wxCommandEvent &event)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
|
|
|
event.Skip();
|
|
|
|
|
|
|
|
// AS: If the user is dragging the mouse and there is a track that
|
|
|
|
// has captured the mouse, then scroll the screen, as necessary.
|
|
|
|
|
|
|
|
/// We check on each timer tick to see if we need to scroll.
|
|
|
|
|
|
|
|
// DM: If we're "autoscrolling" (which means that we're scrolling
|
|
|
|
// because the user dragged from inside to outside the window,
|
|
|
|
// not because the user clicked in the scroll bar), then
|
|
|
|
// the selection code needs to be handled slightly differently.
|
|
|
|
// We set this flag ("mAutoScrolling") to tell the selecting
|
|
|
|
// code that we didn't get here as a result of a mouse event,
|
|
|
|
// and therefore it should ignore the event,
|
|
|
|
// and instead use the last known mouse position. Setting
|
|
|
|
// this flag also causes the Mac to redraw immediately rather
|
|
|
|
// than waiting for the next update event; this makes scrolling
|
|
|
|
// smoother on MacOS 9.
|
|
|
|
|
|
|
|
const auto project = mConnectedProject;
|
|
|
|
const auto trackPanel = project->GetTrackPanel();
|
2017-07-12 16:54:11 +00:00
|
|
|
if (mParent->mMostRecentX >= mParent->mRect.x + mParent->mRect.width) {
|
|
|
|
mParent->mAutoScrolling = true;
|
2015-09-09 01:15:35 +00:00
|
|
|
project->TP_ScrollRight();
|
|
|
|
}
|
2017-07-12 16:54:11 +00:00
|
|
|
else if (mParent->mMostRecentX < mParent->mRect.x) {
|
|
|
|
mParent->mAutoScrolling = true;
|
2015-09-09 01:15:35 +00:00
|
|
|
project->TP_ScrollLeft();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Bug1387: enable autoscroll during drag, if the pointer is at either
|
|
|
|
// extreme x coordinate of the screen, even if that is still within the
|
|
|
|
// track area.
|
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
int xx = mParent->mMostRecentX, yy = 0;
|
2015-09-09 01:15:35 +00:00
|
|
|
trackPanel->ClientToScreen(&xx, &yy);
|
|
|
|
if (xx == 0) {
|
2017-07-12 16:54:11 +00:00
|
|
|
mParent->mAutoScrolling = true;
|
2015-09-09 01:15:35 +00:00
|
|
|
project->TP_ScrollLeft();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
int width, height;
|
|
|
|
::wxDisplaySize(&width, &height);
|
|
|
|
if (xx == width - 1) {
|
2017-07-12 16:54:11 +00:00
|
|
|
mParent->mAutoScrolling = true;
|
2015-09-09 01:15:35 +00:00
|
|
|
project->TP_ScrollRight();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-12 16:54:11 +00:00
|
|
|
auto pTrack = mParent->mpTrack.lock(); // TrackList::Lock() ?
|
|
|
|
if (mParent->mAutoScrolling && pTrack) {
|
2015-09-09 01:15:35 +00:00
|
|
|
// AS: To keep the selection working properly as we scroll,
|
|
|
|
// we fake a mouse event (remember, this method is called
|
|
|
|
// from a timer tick).
|
|
|
|
|
|
|
|
// AS: For some reason, GCC won't let us pass this directly.
|
|
|
|
wxMouseEvent evt(wxEVT_MOTION);
|
|
|
|
const auto size = trackPanel->GetSize();
|
2017-07-12 16:54:11 +00:00
|
|
|
mParent->Drag(TrackPanelMouseEvent{ evt, mParent->mRect, size, pTrack }, project);
|
|
|
|
mParent->mAutoScrolling = false;
|
2015-09-09 01:15:35 +00:00
|
|
|
mConnectedProject->GetTrackPanel()->Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reset our selection markers.
|
2017-07-12 21:47:44 +00:00
|
|
|
void SelectHandle::StartSelection( AudacityProject *pProject )
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
|
|
|
ViewInfo &viewInfo = pProject->GetViewInfo();
|
|
|
|
mSelStartValid = true;
|
|
|
|
|
2017-07-12 20:58:21 +00:00
|
|
|
viewInfo.selectedRegion.setTimes(mSelStart, mSelStart);
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
// PRL: commented out the Sonify stuff with the TrackPanel refactor.
|
|
|
|
// It was no-op anyway.
|
|
|
|
//SonifyBeginModifyState();
|
|
|
|
pProject->ModifyState(false);
|
|
|
|
//SonifyEndModifyState();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Extend or contract the existing selection
|
|
|
|
void SelectHandle::AdjustSelection
|
2017-06-28 04:31:18 +00:00
|
|
|
(AudacityProject *pProject,
|
|
|
|
ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
|
2017-06-24 21:04:07 +00:00
|
|
|
Track *track)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
|
|
|
if (!mSelStartValid)
|
|
|
|
// Must be dragging frequency bounds only.
|
|
|
|
return;
|
|
|
|
|
2017-07-12 20:58:21 +00:00
|
|
|
double selend =
|
2015-09-09 01:15:35 +00:00
|
|
|
std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
|
2017-07-12 20:58:21 +00:00
|
|
|
double origSelend = selend;
|
2015-09-09 01:15:35 +00:00
|
|
|
|
2017-06-24 21:04:07 +00:00
|
|
|
auto pTrack = Track::Pointer( track );
|
|
|
|
if (!pTrack)
|
2017-06-28 04:31:18 +00:00
|
|
|
pTrack = pProject->GetTracks()->Lock(mpTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
|
2017-07-12 20:58:21 +00:00
|
|
|
if (pTrack && mSnapManager.get()) {
|
|
|
|
bool rightEdge = (selend > mSelStart);
|
|
|
|
mSnapEnd = mSnapManager->Snap(pTrack.get(), selend, rightEdge);
|
|
|
|
if (mSnapEnd.Snapped()) {
|
2017-07-12 16:09:35 +00:00
|
|
|
if (mUseSnap)
|
|
|
|
selend = mSnapEnd.outTime;
|
2017-07-12 20:58:21 +00:00
|
|
|
if (mSnapEnd.snappedPoint)
|
|
|
|
mSnapEnd.outCoord += trackLeftEdge;
|
|
|
|
}
|
|
|
|
if (!mSnapEnd.snappedPoint)
|
|
|
|
mSnapEnd.outCoord = -1;
|
|
|
|
|
|
|
|
// Check if selection endpoints are too close together to snap (unless
|
|
|
|
// using snap-to-time -- then we always accept the snap results)
|
|
|
|
if (mSnapStart.outCoord >= 0 &&
|
|
|
|
mSnapEnd.outCoord >= 0 &&
|
2017-07-21 02:10:40 +00:00
|
|
|
std::abs(mSnapStart.outCoord - mSnapEnd.outCoord) < 3) {
|
|
|
|
if(!mSnapEnd.snappedTime)
|
|
|
|
selend = origSelend;
|
2017-07-12 20:58:21 +00:00
|
|
|
mSnapEnd.outCoord = -1;
|
|
|
|
}
|
|
|
|
}
|
2017-07-12 16:09:35 +00:00
|
|
|
AssignSelection(viewInfo, selend, pTrack.get());
|
|
|
|
}
|
2017-07-12 20:58:21 +00:00
|
|
|
|
2017-07-12 16:09:35 +00:00
|
|
|
void SelectHandle::AssignSelection
|
|
|
|
(ViewInfo &viewInfo, double selend, Track *pTrack)
|
|
|
|
{
|
2017-07-12 20:58:21 +00:00
|
|
|
double sel0, sel1;
|
2015-09-09 01:15:35 +00:00
|
|
|
if (mSelStart < selend) {
|
|
|
|
sel0 = mSelStart;
|
|
|
|
sel1 = selend;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sel1 = mSelStart;
|
|
|
|
sel0 = selend;
|
|
|
|
}
|
|
|
|
|
|
|
|
viewInfo.selectedRegion.setTimes(sel0, sel1);
|
|
|
|
|
|
|
|
//On-Demand: check to see if there is an OD thing associated with this track. If so we want to update the focal point for the task.
|
|
|
|
if (pTrack && (pTrack->GetKind() == Track::Wave) && ODManager::IsInstanceCreated())
|
|
|
|
ODManager::Instance()->DemandTrackUpdate
|
2017-07-12 16:09:35 +00:00
|
|
|
(static_cast<WaveTrack*>(pTrack),sel0); //sel0 is sometimes less than mSelStart
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::StartFreqSelection(ViewInfo &viewInfo,
|
|
|
|
int mouseYCoordinate, int trackTopEdge,
|
|
|
|
int trackHeight, Track *pTrack)
|
|
|
|
{
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack.reset();
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelMode = FREQ_SEL_INVALID;
|
|
|
|
mFreqSelPin = SelectedRegion::UndefinedFrequency;
|
|
|
|
|
|
|
|
if (isSpectralSelectionTrack(pTrack)) {
|
|
|
|
// Spectral selection track is always wave
|
2017-06-24 21:04:07 +00:00
|
|
|
auto shTrack = Track::Pointer<const WaveTrack>( pTrack );
|
|
|
|
mFreqSelTrack = shTrack;
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelMode = FREQ_SEL_FREE;
|
|
|
|
mFreqSelPin =
|
2017-06-24 21:04:07 +00:00
|
|
|
PositionToFrequency(shTrack.get(), false, mouseYCoordinate,
|
2015-09-09 01:15:35 +00:00
|
|
|
trackTopEdge, trackHeight);
|
|
|
|
viewInfo.selectedRegion.setFrequencies(mFreqSelPin, mFreqSelPin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-24 21:04:07 +00:00
|
|
|
void SelectHandle::AdjustFreqSelection(
|
|
|
|
const WaveTrack *wt, ViewInfo &viewInfo,
|
2015-09-09 01:15:35 +00:00
|
|
|
int mouseYCoordinate, int trackTopEdge,
|
|
|
|
int trackHeight)
|
|
|
|
{
|
|
|
|
if (mFreqSelMode == FREQ_SEL_INVALID ||
|
|
|
|
mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Extension happens only when dragging in the same track in which we
|
|
|
|
// started, and that is of a spectrogram display type.
|
|
|
|
|
|
|
|
const double rate = wt->GetRate();
|
|
|
|
const double frequency =
|
|
|
|
PositionToFrequency(wt, true, mouseYCoordinate,
|
|
|
|
trackTopEdge, trackHeight);
|
|
|
|
|
|
|
|
// Dragging center?
|
|
|
|
if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
|
|
|
|
if (frequency == rate || frequency < 1.0)
|
|
|
|
// snapped to top or bottom
|
|
|
|
viewInfo.selectedRegion.setFrequencies(
|
|
|
|
SelectedRegion::UndefinedFrequency,
|
|
|
|
SelectedRegion::UndefinedFrequency);
|
|
|
|
else {
|
|
|
|
// mFreqSelPin holds the ratio of top to center
|
|
|
|
const double maxRatio = findMaxRatio(frequency, rate);
|
|
|
|
const double ratio = std::min(maxRatio, mFreqSelPin);
|
|
|
|
viewInfo.selectedRegion.setFrequencies(
|
|
|
|
frequency / ratio, frequency * ratio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mFreqSelMode == FREQ_SEL_PINNED_CENTER) {
|
|
|
|
if (mFreqSelPin >= 0) {
|
|
|
|
// Change both upper and lower edges leaving centre where it is.
|
|
|
|
if (frequency == rate || frequency < 1.0)
|
|
|
|
// snapped to top or bottom
|
|
|
|
viewInfo.selectedRegion.setFrequencies(
|
|
|
|
SelectedRegion::UndefinedFrequency,
|
|
|
|
SelectedRegion::UndefinedFrequency);
|
|
|
|
else {
|
|
|
|
// Given center and mouse position, find ratio of the larger to the
|
|
|
|
// smaller, limit that to the frequency scale bounds, and adjust
|
|
|
|
// top and bottom accordingly.
|
|
|
|
const double maxRatio = findMaxRatio(mFreqSelPin, rate);
|
|
|
|
double ratio = frequency / mFreqSelPin;
|
|
|
|
if (ratio < 1.0)
|
|
|
|
ratio = 1.0 / ratio;
|
|
|
|
ratio = std::min(maxRatio, ratio);
|
|
|
|
viewInfo.selectedRegion.setFrequencies(
|
|
|
|
mFreqSelPin / ratio, mFreqSelPin * ratio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Dragging of upper or lower.
|
|
|
|
const bool bottomDefined =
|
|
|
|
!(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelPin < 0);
|
|
|
|
const bool topDefined =
|
|
|
|
!(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelPin < 0);
|
|
|
|
if (!bottomDefined || (topDefined && mFreqSelPin < frequency)) {
|
|
|
|
// Adjust top
|
|
|
|
if (frequency == rate)
|
|
|
|
// snapped high; upper frequency is undefined
|
|
|
|
viewInfo.selectedRegion.setF1(SelectedRegion::UndefinedFrequency);
|
|
|
|
else
|
|
|
|
viewInfo.selectedRegion.setF1(std::max(1.0, frequency));
|
|
|
|
|
|
|
|
viewInfo.selectedRegion.setF0(mFreqSelPin);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Adjust bottom
|
|
|
|
if (frequency < 1.0)
|
|
|
|
// snapped low; lower frequency is undefined
|
|
|
|
viewInfo.selectedRegion.setF0(SelectedRegion::UndefinedFrequency);
|
|
|
|
else
|
|
|
|
viewInfo.selectedRegion.setF0(std::min(rate / 2.0, frequency));
|
|
|
|
|
|
|
|
viewInfo.selectedRegion.setF1(mFreqSelPin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::HandleCenterFrequencyClick
|
|
|
|
(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
|
|
|
|
{
|
|
|
|
if (shiftDown) {
|
|
|
|
// Disable time selection
|
|
|
|
mSelStartValid = false;
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
|
2015-09-09 01:15:35 +00:00
|
|
|
mFreqSelPin = value;
|
|
|
|
mFreqSelMode = FREQ_SEL_DRAG_CENTER;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
#ifndef SPECTRAL_EDITING_ESC_KEY
|
|
|
|
// Start center snapping
|
|
|
|
// Turn center snapping on (the only way to do this)
|
|
|
|
mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
|
|
|
|
// Disable time selection
|
|
|
|
mSelStartValid = false;
|
2017-07-12 16:54:11 +00:00
|
|
|
mFrequencySnapper = std::make_shared<SpectrumAnalyst>();
|
2017-06-28 19:16:20 +00:00
|
|
|
StartSnappingFreqSelection(*mFrequencySnapper, viewInfo, pTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::StartSnappingFreqSelection
|
2017-06-28 19:16:20 +00:00
|
|
|
(SpectrumAnalyst &analyst,
|
|
|
|
const ViewInfo &viewInfo, const WaveTrack *pTrack)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
|
|
|
static const size_t minLength = 8;
|
|
|
|
|
|
|
|
const double rate = pTrack->GetRate();
|
|
|
|
|
|
|
|
// Grab samples, just for this track, at these times
|
|
|
|
std::vector<float> frequencySnappingData;
|
|
|
|
const auto start =
|
|
|
|
pTrack->TimeToLongSamples(viewInfo.selectedRegion.t0());
|
|
|
|
const auto end =
|
|
|
|
pTrack->TimeToLongSamples(viewInfo.selectedRegion.t1());
|
|
|
|
const auto length =
|
|
|
|
std::min(frequencySnappingData.max_size(),
|
|
|
|
limitSampleBufferSize(10485760, // as in FreqWindow.cpp
|
|
|
|
end - start));
|
|
|
|
const auto effectiveLength = std::max(minLength, length);
|
|
|
|
frequencySnappingData.resize(effectiveLength, 0.0f);
|
|
|
|
pTrack->Get(
|
|
|
|
reinterpret_cast<samplePtr>(&frequencySnappingData[0]),
|
|
|
|
floatSample, start, length, fillZero,
|
|
|
|
// Don't try to cope with exceptions, just read zeroes instead.
|
|
|
|
false);
|
|
|
|
|
|
|
|
// Use same settings as are now used for spectrogram display,
|
|
|
|
// except, shrink the window as needed so we get some answers
|
|
|
|
|
|
|
|
const SpectrogramSettings &settings = pTrack->GetSpectrogramSettings();
|
|
|
|
auto windowSize = settings.GetFFTLength();
|
|
|
|
|
|
|
|
while(windowSize > effectiveLength)
|
|
|
|
windowSize >>= 1;
|
|
|
|
const int windowType = settings.windowType;
|
|
|
|
|
2017-06-28 19:16:20 +00:00
|
|
|
analyst.Calculate(
|
2015-09-09 01:15:35 +00:00
|
|
|
SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
|
|
|
|
&frequencySnappingData[0], length);
|
|
|
|
|
|
|
|
// We can now throw away the sample data but we keep the spectrum.
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::MoveSnappingFreqSelection
|
|
|
|
(AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
|
|
|
|
int trackTopEdge,
|
|
|
|
int trackHeight, Track *pTrack)
|
|
|
|
{
|
|
|
|
if (pTrack &&
|
|
|
|
pTrack->GetSelected() &&
|
|
|
|
isSpectralSelectionTrack(pTrack)) {
|
|
|
|
// Spectral selection track is always wave
|
|
|
|
WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
|
|
|
|
// PRL:
|
|
|
|
// What would happen if center snapping selection began in one spectrogram track,
|
|
|
|
// then continues inside another? We do not then recalculate
|
|
|
|
// the spectrum (as was done in StartSnappingFreqSelection)
|
|
|
|
// but snap according to the peaks in the old track.
|
|
|
|
|
|
|
|
// But if we always supply the original clicked track here that doesn't matter.
|
|
|
|
const double rate = wt->GetRate();
|
|
|
|
const double frequency =
|
|
|
|
PositionToFrequency(wt, false, mouseYCoordinate,
|
|
|
|
trackTopEdge, trackHeight);
|
|
|
|
const double snappedFrequency =
|
|
|
|
mFrequencySnapper->FindPeak(frequency, NULL);
|
|
|
|
const double maxRatio = findMaxRatio(snappedFrequency, rate);
|
|
|
|
double ratio = 2.0; // An arbitrary octave on each side, at most
|
|
|
|
{
|
|
|
|
const double f0 = viewInfo.selectedRegion.f0();
|
|
|
|
const double f1 = viewInfo.selectedRegion.f1();
|
|
|
|
if (f1 >= f0 && f0 >= 0)
|
|
|
|
// Preserve already chosen ratio instead
|
|
|
|
ratio = sqrt(f1 / f0);
|
|
|
|
}
|
|
|
|
ratio = std::min(ratio, maxRatio);
|
|
|
|
|
|
|
|
mFreqSelPin = snappedFrequency;
|
|
|
|
viewInfo.selectedRegion.setFrequencies(
|
|
|
|
snappedFrequency / ratio, snappedFrequency * ratio);
|
|
|
|
|
|
|
|
// A change here would affect what AdjustFreqSelection() does
|
|
|
|
// in the prototype version where you switch from moving center to
|
|
|
|
// dragging width with a click. No effect now.
|
2017-06-24 21:04:07 +00:00
|
|
|
mFreqSelTrack = Track::Pointer<const WaveTrack>( wt );
|
2015-09-09 01:15:35 +00:00
|
|
|
|
|
|
|
// SelectNone();
|
|
|
|
// SelectTrack(pTrack, true);
|
|
|
|
pProject->GetTrackPanel()->SetFocusedTrack(pTrack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SelectHandle::SnapCenterOnce
|
2017-06-28 19:16:20 +00:00
|
|
|
(SpectrumAnalyst &analyst,
|
|
|
|
ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
|
2015-09-09 01:15:35 +00:00
|
|
|
{
|
|
|
|
const SpectrogramSettings &settings = pTrack->GetSpectrogramSettings();
|
|
|
|
const auto windowSize = settings.GetFFTLength();
|
|
|
|
const double rate = pTrack->GetRate();
|
|
|
|
const double nyq = rate / 2.0;
|
|
|
|
const double binFrequency = rate / windowSize;
|
|
|
|
|
|
|
|
double f1 = viewInfo.selectedRegion.f1();
|
|
|
|
double centerFrequency = viewInfo.selectedRegion.fc();
|
|
|
|
if (centerFrequency <= 0) {
|
|
|
|
centerFrequency = up ? binFrequency : nyq;
|
|
|
|
f1 = centerFrequency * sqrt(2.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
double ratio = f1 / centerFrequency;
|
|
|
|
const int originalBin = floor(0.5 + centerFrequency / binFrequency);
|
|
|
|
const int limitingBin = up ? floor(0.5 + nyq / binFrequency) : 1;
|
|
|
|
|
|
|
|
// This is crude and wasteful, doing the FFT each time the command is called.
|
|
|
|
// It would be better to cache the data, but then invalidation of the cache would
|
|
|
|
// need doing in all places that change the time selection.
|
2017-06-28 19:16:20 +00:00
|
|
|
StartSnappingFreqSelection(analyst, viewInfo, pTrack);
|
2015-09-09 01:15:35 +00:00
|
|
|
double snappedFrequency = centerFrequency;
|
|
|
|
int bin = originalBin;
|
|
|
|
if (up) {
|
|
|
|
while (snappedFrequency <= centerFrequency &&
|
|
|
|
bin < limitingBin)
|
2017-06-28 19:16:20 +00:00
|
|
|
snappedFrequency = analyst.FindPeak(++bin * binFrequency, NULL);
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
while (snappedFrequency >= centerFrequency &&
|
|
|
|
bin > limitingBin)
|
2017-06-28 19:16:20 +00:00
|
|
|
snappedFrequency = analyst.FindPeak(--bin * binFrequency, NULL);
|
2015-09-09 01:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PRL: added these two lines with the big TrackPanel refactor
|
|
|
|
const double maxRatio = findMaxRatio(snappedFrequency, rate);
|
|
|
|
ratio = std::min(ratio, maxRatio);
|
|
|
|
|
|
|
|
viewInfo.selectedRegion.setFrequencies
|
|
|
|
(snappedFrequency / ratio, snappedFrequency * ratio);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// unused
|
|
|
|
void SelectHandle::ResetFreqSelectionPin
|
|
|
|
(const ViewInfo &viewInfo, double hintFrequency, bool logF)
|
|
|
|
{
|
|
|
|
switch (mFreqSelMode) {
|
|
|
|
case FREQ_SEL_INVALID:
|
|
|
|
case FREQ_SEL_SNAPPING_CENTER:
|
|
|
|
mFreqSelPin = -1.0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_PINNED_CENTER:
|
|
|
|
mFreqSelPin = viewInfo.selectedRegion.fc();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_DRAG_CENTER:
|
|
|
|
{
|
|
|
|
// Re-pin the width
|
|
|
|
const double f0 = viewInfo.selectedRegion.f0();
|
|
|
|
const double f1 = viewInfo.selectedRegion.f1();
|
|
|
|
if (f0 >= 0 && f1 >= 0)
|
|
|
|
mFreqSelPin = sqrt(f1 / f0);
|
|
|
|
else
|
|
|
|
mFreqSelPin = -1.0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_FREE:
|
|
|
|
// Pin which? Farther from the hint which is the presumed
|
|
|
|
// mouse position.
|
|
|
|
{
|
|
|
|
// If this function finds use again, the following should be
|
|
|
|
// generalized using NumberScale
|
|
|
|
|
|
|
|
const double f0 = viewInfo.selectedRegion.f0();
|
|
|
|
const double f1 = viewInfo.selectedRegion.f1();
|
|
|
|
if (logF) {
|
|
|
|
if (f1 < 0)
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else {
|
|
|
|
const double logf1 = log(std::max(1.0, f1));
|
|
|
|
const double logf0 = log(std::max(1.0, f0));
|
|
|
|
const double logHint = log(std::max(1.0, hintFrequency));
|
|
|
|
if (std::abs(logHint - logf1) < std::abs(logHint - logf0))
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else
|
|
|
|
mFreqSelPin = f1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (f1 < 0 ||
|
|
|
|
std::abs(hintFrequency - f1) < std::abs(hintFrequency - f0))
|
|
|
|
mFreqSelPin = f0;
|
|
|
|
else
|
|
|
|
mFreqSelPin = f1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_TOP_FREE:
|
|
|
|
mFreqSelPin = viewInfo.selectedRegion.f0();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FREQ_SEL_BOTTOM_FREE:
|
|
|
|
mFreqSelPin = viewInfo.selectedRegion.f1();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
wxASSERT(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|