audacia/src/tracks/playabletrack/wavetrack/ui/CutlineHandle.cpp

255 lines
7.2 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
CutlineHandle.cpp
Paul Licameli split from TrackPanel.cpp
**********************************************************************/
#include "../../../../Audacity.h"
#include "CutlineHandle.h"
#include "../../../../Experimental.h"
#include "../../../../HitTestResult.h"
#include "../../../../Project.h"
#include "../../../../RefreshCode.h"
#include "../../../../Snap.h" // for kPixelTolerance
#include "../../../../TrackPanelMouseEvent.h"
#include "../../../../UndoManager.h"
#include "../../../../ViewInfo.h"
#include "../../../../WaveTrack.h"
#include "../../../../../images/Cursors.h"
CutlineHandle::CutlineHandle
( const std::shared_ptr<WaveTrack> &pTrack, WaveTrackLocation location )
: mpTrack{ pTrack }
, mLocation{ location }
{
}
void CutlineHandle::Enter(bool)
{
#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
mChangeHighlight = RefreshCode::RefreshCell;
#endif
}
HitTestPreview CutlineHandle::HitPreview(bool cutline, bool unsafe)
{
static auto disabledCursor =
::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
static wxCursor arrowCursor{ wxCURSOR_ARROW };
return {
(cutline
? _("Left-Click to expand, Right-Click to remove")
: _("Left-Click to merge clips")),
(unsafe
? &*disabledCursor
: &arrowCursor)
};
}
namespace
{
int FindMergeLine(WaveTrack *track, double time)
{
const double tolerance = 0.5 / track->GetRate();
int ii = 0;
for (const auto loc: track->GetCachedLocations()) {
if (loc.typ == WaveTrackLocation::locationMergePoint &&
fabs(time - loc.pos) < tolerance)
return ii;
++ii;
}
return -1;
}
bool IsOverCutline
(const ViewInfo &viewInfo, WaveTrack * track,
const wxRect &rect, const wxMouseState &state,
WaveTrackLocation *pmLocation)
{
for (auto loc: track->GetCachedLocations())
{
const double x = viewInfo.TimeToPosition(loc.pos);
if (x >= 0 && x < rect.width)
{
wxRect locRect;
locRect.width = 2 * kPixelTolerance - 1;
locRect.x = (int)(rect.x + x) - locRect.width / 2;
locRect.y = rect.y;
locRect.height = rect.height;
if (locRect.Contains(state.m_x, state.m_y))
{
if (pmLocation)
*pmLocation = loc;
return true;
}
}
}
return false;
}
}
UIHandlePtr CutlineHandle::HitTest
(std::weak_ptr<CutlineHandle> &holder,
const wxMouseState &state, const wxRect &rect,
const AudacityProject *pProject,
const std::shared_ptr<WaveTrack> &pTrack)
{
auto &viewInfo = ViewInfo::Get( *pProject );
/// method that tells us if the mouse event landed on an
/// editable Cutline
WaveTrackLocation location;
if (!IsOverCutline(viewInfo, pTrack.get(), rect, state, &location))
return {};
auto result = std::make_shared<CutlineHandle>( pTrack, location );
result = AssignUIHandlePtr( holder, result );
return result;
}
CutlineHandle::~CutlineHandle()
{
}
UIHandle::Result CutlineHandle::Click
(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
{
using namespace RefreshCode;
const bool unsafe = pProject->IsAudioActive();
if ( unsafe )
return Cancelled;
const wxMouseEvent &event = evt.event;
auto &viewInfo = ViewInfo::Get( *pProject );
// Can affect the track by merging clips, expanding a cutline, or
// deleting a cutline.
// All the change is done at button-down. Button-up just commits the undo item.
/// Someone has just clicked the mouse. What do we do?
// FIXME: Disable this and return true when CutLines aren't showing?
// (Don't use gPrefs-> for the fix as registry access is slow).
// Cutline data changed on either branch, so refresh the track display.
UIHandle::Result result = RefreshCell;
if (event.LeftDown())
{
if (mLocation.typ == WaveTrackLocation::locationCutLine)
{
mOperation = Expand;
mStartTime = viewInfo.selectedRegion.t0();
mEndTime = viewInfo.selectedRegion.t1();
// When user presses left button on cut line, expand the line again
double cutlineStart = 0, cutlineEnd = 0;
double *pCutlineStart = &cutlineStart, *pCutlineEnd = &cutlineEnd;
for (auto channel :
TrackList::Channels(mpTrack.get())) {
channel->ExpandCutLine(
mLocation.pos, pCutlineStart, pCutlineEnd);
if ( channel == mpTrack.get() )
pCutlineStart = pCutlineEnd = nullptr;
}
viewInfo.selectedRegion.setTimes(cutlineStart, cutlineEnd);
result |= UpdateSelection;
}
else if (mLocation.typ == WaveTrackLocation::locationMergePoint) {
const double pos = mLocation.pos;
for (auto channel :
TrackList::Channels(mpTrack.get())) {
// Don't assume correspondence of merge points across channels!
int idx = FindMergeLine(channel, pos);
if (idx >= 0) {
WaveTrack::Location location =
channel->GetCachedLocations()[idx];
channel->MergeClips(
location.clipidx1, location.clipidx2);
}
}
mOperation = Merge;
}
}
else if (event.RightDown())
{
bool removed = false;
for (auto channel :
TrackList::Channels(mpTrack.get()))
removed = channel->RemoveCutLine(mLocation.pos) || removed;
if (!removed)
// Nothing happened, make no Undo item
return Cancelled;
mOperation = Remove;
}
else
result = RefreshNone;
return result;
}
UIHandle::Result CutlineHandle::Drag
(const TrackPanelMouseEvent &, AudacityProject *)
{
return RefreshCode::RefreshNone;
}
HitTestPreview CutlineHandle::Preview
(const TrackPanelMouseState &, const AudacityProject *pProject)
{
const bool unsafe = pProject->IsAudioActive();
auto bCutline = (mLocation.typ == WaveTrackLocation::locationCutLine);
return HitPreview( bCutline, unsafe );
}
UIHandle::Result CutlineHandle::Release
(const TrackPanelMouseEvent &, AudacityProject *pProject, wxWindow *)
{
UIHandle::Result result = RefreshCode::RefreshNone;
// Only now commit the result to the undo stack
AudacityProject *const project = pProject;
switch (mOperation) {
default:
wxASSERT(false);
case Merge:
project->PushState(_("Merged Clips"), _("Merge"), UndoPush::CONSOLIDATE);
break;
case Expand:
project->PushState(_("Expanded Cut Line"), _("Expand"));
result |= RefreshCode::UpdateSelection;
break;
case Remove:
project->PushState(_("Removed Cut Line"), _("Remove"));
break;
}
// Nothing to do for the display
return result;
}
UIHandle::Result CutlineHandle::Cancel(AudacityProject *pProject)
{
using namespace RefreshCode;
UIHandle::Result result = RefreshCell;
pProject->RollbackState();
if (mOperation == Expand) {
AudacityProject &project = *pProject;
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
selectedRegion.setTimes( mStartTime, mEndTime );
result |= UpdateSelection;
}
return result;
}