<|start_filename|>src/SelectUtilities.cpp<|end_filename|>
/**********************************************************************
 
 Audacity: A Digital Audio Editor
 
 SelectUtilities.cpp
 
 <NAME> split from SelectMenus.cpp
 
 **********************************************************************/

#include "SelectUtilities.h"

#include <wx/frame.h>

#include "widgets/AudacityMessageBox.h"
#include "AudioIO.h"
#include "CommonCommandFlags.h"
#include "Menus.h"
#include "Project.h"
#include "ProjectAudioIO.h"
#include "ProjectHistory.h"
#include "ProjectWindows.h"
#include "ProjectRate.h"
#include "ProjectSettings.h"
#include "SelectionState.h"
#include "TimeDialog.h"
#include "TrackPanelAx.h"
#include "TrackPanel.h"
#include "ViewInfo.h"
#include "WaveTrack.h"

namespace {

// Temporal selection (not TimeTrack selection)
// potentially for all wave tracks.
void DoSelectTimeAndAudioTracks
(AudacityProject &project, bool bAllTime, bool bAllTracks)
{
   auto &tracks = TrackList::Get( project );
   auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;

   if( bAllTime )
      selectedRegion.setTimes(
         tracks.GetMinOffset(), tracks.GetEndTime());

   if( bAllTracks ) {
      // Unselect all tracks before selecting audio.
      for (auto t : tracks.Any())
         t->SetSelected(false);
      for (auto t : tracks.Any<WaveTrack>())
         t->SetSelected(true);

      ProjectHistory::Get( project ).ModifyState(false);
   }
}

}
namespace SelectUtilities {

void DoSelectTimeAndTracks
(AudacityProject &project, bool bAllTime, bool bAllTracks)
{
   auto &tracks = TrackList::Get( project );
   auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;

   if( bAllTime )
      selectedRegion.setTimes(
         tracks.GetMinOffset(), tracks.GetEndTime());

   if( bAllTracks ) {
      for (auto t : tracks.Any())
         t->SetSelected(true);

      ProjectHistory::Get( project ).ModifyState(false);
   }
}

void SelectNone( AudacityProject &project )
{
   auto &tracks = TrackList::Get( project );
   for (auto t : tracks.Any())
      t->SetSelected(false);

   auto &trackPanel = TrackPanel::Get( project );
   trackPanel.Refresh(false);
}

// Select the full time range, if no
// time range is selected.
void SelectAllIfNone( AudacityProject &project )
{
   auto &viewInfo = ViewInfo::Get( project );
   auto flags = MenuManager::Get( project ).GetUpdateFlags();
   if((flags & EditableTracksSelectedFlag()).none() ||
      viewInfo.selectedRegion.isPoint())
      DoSelectAllAudio( project );
}

// Select the full time range, if no time range is selected and
// selecting is allowed. Returns "false" selecting not allowed.
bool SelectAllIfNoneAndAllowed( AudacityProject &project )
{
   auto allowed = gPrefs->ReadBool(wxT("/GUI/SelectAllOnNone"), false);
   auto &viewInfo = ViewInfo::Get( project );
   auto flags = MenuManager::Get( project ).GetUpdateFlags();

   if((flags & EditableTracksSelectedFlag()).none() ||
      viewInfo.selectedRegion.isPoint()) {
      if (!allowed) {
         return false;
      }
      DoSelectAllAudio( project );
   }
   return true;
}

void DoListSelection
(AudacityProject &project, Track *t, bool shift, bool ctrl, bool modifyState)
{
   auto &tracks = TrackList::Get( project );
   auto &selectionState = SelectionState::Get( project );
   const auto &settings = ProjectSettings::Get( project );
   auto &viewInfo = ViewInfo::Get( project );
   auto &window = GetProjectFrame( project );

   auto isSyncLocked = settings.IsSyncLocked();

   selectionState.HandleListSelection(
      tracks, viewInfo, *t,
      shift, ctrl, isSyncLocked );

   if (! ctrl )
      TrackFocus::Get( project ).Set( t );
   window.Refresh(false);
   if (modifyState)
      ProjectHistory::Get( project ).ModifyState(true);
}

void DoSelectAll(AudacityProject &project)
{
   DoSelectTimeAndTracks( project, true, true );
}

void DoSelectAllAudio(AudacityProject &project)
{
   DoSelectTimeAndAudioTracks( project, true, true );
}

// This function selects all tracks if no tracks selected,
// and all time if no time selected.
// There is an argument for making it just count wave tracks,
// However you could then not select a label and cut it,
// without this function selecting all tracks.
void DoSelectSomething(AudacityProject &project)
{
   auto &tracks = TrackList::Get( project );
   auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;

   bool bTime = selectedRegion.isPoint();
   bool bTracks = tracks.Selected().empty();

   if( bTime || bTracks )
      DoSelectTimeAndTracks( project, bTime, bTracks );
}

void ActivatePlayRegion(AudacityProject &project)
{
   auto &tracks = TrackList::Get( project );

   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   if (playRegion.GetStart() >= tracks.GetEndTime()) {
      AudacityMessageBox(
         XO("Cannot lock region beyond\nend of project."),
         XO("Error"));
   }
   else {
      playRegion.SetActive( true );
      if (playRegion.Empty()) {
         auto &selectedRegion = viewInfo.selectedRegion;
         if (!selectedRegion.isPoint())
            playRegion.SetTimes(selectedRegion.t0(), selectedRegion.t1());
         else
            // Arbitrary first four seconds
            playRegion.SetTimes(0.0, 4.0);
      }
   }
}

void InactivatePlayRegion(AudacityProject &project)
{
   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   auto &selectedRegion = viewInfo.selectedRegion;
   // Set only the times that are fetched by the playback engine, but not
   // the last-active times that are used for display.
   playRegion.SetActive( false );
   playRegion.SetTimes( selectedRegion.t0(), selectedRegion.t1() );
}

void TogglePlayRegion(AudacityProject &project)
{
   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   if (playRegion.Active())
      InactivatePlayRegion(project);
   else
      ActivatePlayRegion(project);
}

void ClearPlayRegion(AudacityProject &project)
{
   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   playRegion.SetAllTimes(-1, -1);
}

void SetPlayRegionToSelection(AudacityProject &project)
{
   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   auto &selectedRegion = viewInfo.selectedRegion;
   playRegion.SetAllTimes( selectedRegion.t0(), selectedRegion.t1() );
}

void OnSetRegion(AudacityProject &project,
   bool left, bool selection, const TranslatableString &dialogTitle)
{
   auto token = ProjectAudioIO::Get( project ).GetAudioIOToken();
   auto &viewInfo = ViewInfo::Get( project );
   auto &playRegion = viewInfo.playRegion;
   auto &selectedRegion = viewInfo.selectedRegion;
   const auto &settings = ProjectSettings::Get( project );
   auto &window = GetProjectFrame( project );

   const auto getValue = [&]() -> double {
      if (selection) {
         if (left)
            return selectedRegion.t0();
         else
            return selectedRegion.t1();
      }
      else {
         if (left)
            return playRegion.GetStart();
         else
            return playRegion.GetEnd();
      }
   };

   const auto setValue = [&](double value){
      if (selection) {
         if (left)
            selectedRegion.setT0(value, false);
         else
            selectedRegion.setT1(value, false);
      }
      else {
         if (left)
            playRegion.SetStart(value);
         else
            playRegion.SetEnd(value);
      }
   };

   bool bSelChanged = false;
   auto gAudioIO = AudioIO::Get();
   if ((token > 0) && gAudioIO->IsStreamActive(token))
   {
      double indicator = gAudioIO->GetStreamTime();
      setValue(indicator);
      bSelChanged = true;
   }
   else
   {
      auto fmt = settings.GetSelectionFormat();
      auto rate = ProjectRate::Get(project).GetRate();

      TimeDialog dlg(&window, dialogTitle,
         fmt, rate, getValue(), XO("Position"));

      if (wxID_OK == dlg.ShowModal())
      {
         //Get the value from the dialog
         setValue( std::max(0.0, dlg.GetTimeValue()) );
         bSelChanged = true;
      }
   }

   if (bSelChanged)
      ProjectHistory::Get( project ).ModifyState(false);
}
}


<|start_filename|>src/tracks/playabletrack/wavetrack/ui/WaveClipTrimHandle.cpp<|end_filename|>
/*!********************************************************************
*
 Audacity: A Digital Audio Editor

 WaveClipTrimHandle.cpp

 <NAME>

 **********************************************************************/

#include "WaveClipTrimHandle.h"
#include "ProjectAudioIO.h"
#include "RefreshCode.h"


#include "../../../images/Cursors.h"
#include "WaveClip.h"
#include "WaveTrack.h"
#include "WaveTrackView.h"
#include "HitTestResult.h"
#include "TrackPanelMouseEvent.h"
#include "ViewInfo.h"
#include "ProjectHistory.h"
#include "UndoManager.h"

namespace {

    std::vector<std::shared_ptr<WaveClip>> FindClipsInChannels(double start, double end, WaveTrack* track) {
        std::vector<std::shared_ptr<WaveClip>> result;
        for (auto channel : TrackList::Channels(track))
        {
            for (auto& clip : channel->GetClips())
            {
                if (clip->GetPlayStartTime() == start && clip->GetPlayEndTime() == end)
                    result.push_back(clip);
            }
        }
        return result;
    }
}


HitTestPreview WaveClipTrimHandle::HitPreview(const AudacityProject*, bool unsafe)
{
    static auto disabledCursor =
        ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
    static auto slideCursor =
        MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
    auto message = XO("Click and drag to move clip boundary in time");

    return {
       message,
       (unsafe
        ? &*disabledCursor
        : &*slideCursor)
    };
}

WaveClipTrimHandle::WaveClipTrimHandle(const std::pair<double, double>& range, const std::vector<std::shared_ptr<WaveClip>>& clips, Border targetBorder)
    : mRange(range), mClips(clips), mTargetBorder(targetBorder)
{
}

UIHandlePtr WaveClipTrimHandle::HitAnywhere(std::weak_ptr<WaveClipTrimHandle>& holder, WaveTrack* waveTrack, const AudacityProject* pProject, const TrackPanelMouseState& state)
{
    const auto rect = state.rect;

    auto px = state.state.m_x;

    auto makeHandle = [&](const std::shared_ptr<WaveClip>& clip, const std::pair<double, double>& trimRange, Border targetBorder) {
        const auto isAligned = waveTrack->IsAlignedWithLeader() || waveTrack->GetLinkType() == Track::LinkType::Aligned;
        auto clips = isAligned ?
            //find clips in other channels which are also should be trimmed
            FindClipsInChannels(clip->GetPlayStartTime(), clip->GetPlayEndTime(), waveTrack) :
            std::vector<std::shared_ptr<WaveClip>>{ clip };
        return AssignUIHandlePtr(holder,
            std::make_shared<WaveClipTrimHandle>(trimRange, clips, targetBorder)
        );
    };

    auto& zoomInfo = ViewInfo::Get(*pProject);
    //Test left and right boundaries of each clip
    //and determine the maximum offsets allowed for trimming,
    //which are constrained either by other clips, own length,
    //or another own edge
    for (const auto& clip : waveTrack->GetClips())
    {
        auto clipRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
        if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold)
        {
            auto left = -std::numeric_limits<double>::infinity();
            for (auto& other : waveTrack->GetClips())
                if (other->GetPlayStartTime() < clip->GetPlayStartTime() && other->GetPlayEndTime() > left)
                {
                    auto maxOffset = clip->SamplesToTime(clip->TimeToSamples(clip->GetPlayStartTime() - other->GetPlayEndTime()));
                    left = clip->GetPlayStartTime() - maxOffset;
                }
            //not less than 1 sample length
            auto range = std::make_pair(left, clip->GetPlayEndTime() - 1.0 / clip->GetRate());

            return makeHandle(clip, range, Border::Left);
        }
        else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold)
        {
            auto right = std::numeric_limits<double>::infinity();
            for (auto& other : waveTrack->GetClips())
                if (other->GetPlayStartTime() > clip->GetPlayStartTime() && other->GetPlayStartTime() < right)
                {
                    auto maxOffset = clip->SamplesToTime(clip->TimeToSamples(other->GetPlayStartTime() - clip->GetPlayEndTime()));
                    right = clip->GetPlayEndTime() + maxOffset;
                }
            //not less than 1 sample length
            auto range = std::make_pair(clip->GetPlayStartTime() + 1.0 / clip->GetRate(), right);

            return makeHandle(clip, range, Border::Right);
        }
    }

    return {};
}

UIHandlePtr WaveClipTrimHandle::HitTest(std::weak_ptr<WaveClipTrimHandle>& holder,
    WaveTrackView& view, const AudacityProject* pProject,
    const TrackPanelMouseState& state)
{
    auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(view.FindTrack()->SubstitutePendingChangedTrack());
    //For multichannel tracks, show trim handle only for the leader track
    if (!waveTrack->IsLeader() && waveTrack->IsAlignedWithLeader())
        return {};

    std::vector<UIHandlePtr> results;

    const auto rect = state.rect;

    auto px = state.state.m_x;
    auto py = state.state.m_y;

    if (py >= rect.GetTop() && 
        py <= (rect.GetTop() + static_cast<int>(rect.GetHeight() * 0.3)))
    {
        return HitAnywhere(holder, waveTrack.get(), pProject, state);
    }
    return {};
}



HitTestPreview WaveClipTrimHandle::Preview(const TrackPanelMouseState& mouseState, AudacityProject* pProject)
{
    const bool unsafe = ProjectAudioIO::Get(*pProject).IsAudioActive();
    return HitPreview(pProject, unsafe);
}

UIHandle::Result WaveClipTrimHandle::Click
(const TrackPanelMouseEvent& event, AudacityProject* pProject)
{
    if (event.event.LeftDown())
    {
        mDragStartX = event.event.GetX();
        mInitialBorderPosition = mTargetBorder == Border::Left ? mClips[0]->GetPlayStartTime()
            : mClips[0]->GetPlayEndTime();
        return RefreshCode::RefreshNone;
    }
    return RefreshCode::Cancelled;
}

UIHandle::Result WaveClipTrimHandle::Drag
(const TrackPanelMouseEvent& event, AudacityProject* project)
{
    const auto newX = event.event.GetX();
    const auto dx = newX - mDragStartX;

    auto& viewInfo = ViewInfo::Get(*project);

    
    auto eventT = viewInfo.PositionToTime(viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx, event.rect.x);
    auto offset = sampleCount(floor((eventT - mInitialBorderPosition) * mClips[0]->GetRate())).as_double() / mClips[0]->GetRate();
    auto t = std::clamp(mInitialBorderPosition + offset, mRange.first, mRange.second);

    switch (mTargetBorder)
    {
    case Border::Left: {
        for (auto& clip : mClips)
            clip->TrimLeftTo(t);
    } break;
    case Border::Right: {
        for (auto& clip : mClips)
            clip->TrimRightTo(t);
    } break;
    default: break;
    };
    return RefreshCode::RefreshCell;
}

UIHandle::Result WaveClipTrimHandle::Release
(const TrackPanelMouseEvent& event, AudacityProject* project,
    wxWindow* pParent)
{
    if (mTargetBorder == Border::Left && mClips[0]->GetPlayStartTime() != mInitialBorderPosition)
    {
        auto dt = std::abs(mClips[0]->GetPlayStartTime() - mInitialBorderPosition);
        ProjectHistory::Get(*project).PushState(XO("Clip-Trim-Left"),
            XO("Moved by %.02f").Format(dt), UndoPush::CONSOLIDATE);
    } 
    else if (mTargetBorder == Border::Right && mClips[0]->GetPlayEndTime() != mInitialBorderPosition)
    {
        auto dt = std::abs(mClips[0]->GetPlayEndTime() - mInitialBorderPosition);
        ProjectHistory::Get(*project).PushState(XO("Clip-Trim-Right"),
            XO("Moved by %.02f").Format(dt), UndoPush::CONSOLIDATE);
    }
    return RefreshCode::RefreshNone;
}

UIHandle::Result WaveClipTrimHandle::Cancel(AudacityProject* pProject)
{
    switch (mTargetBorder)
    {
    case Border::Left:
        for (auto& clip : mClips) clip->TrimLeftTo(mInitialBorderPosition);
        break;
    case Border::Right:
        for (auto& clip : mClips) clip->TrimRightTo(mInitialBorderPosition);
        break;
    }
    return RefreshCode::RefreshCell;
}


<|start_filename|>src/tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h<|end_filename|>
/*!********************************************************************
*
 Audacity: A Digital Audio Editor

 WaveTrackAffordanceControls.h

 <NAME>

 **********************************************************************/

#pragma once

#include <wx/font.h>
#include <wx/event.h>

#include "../../../ui/CommonTrackPanelCell.h"
#include "../../../ui/TextEditHelper.h"


struct TrackListEvent;

class AffordanceHandle;
class SelectHandle;
class WaveClip;
class TrackPanelResizeHandle;
class WaveClipTitleEditHandle;
class WaveTrackAffordanceHandle;
class WaveClipTrimHandle;
class TrackList;

//Handles clip movement, selection, navigation and
//allow name change
class AUDACITY_DLL_API WaveTrackAffordanceControls : 
    public CommonTrackCell,
    public TextEditDelegate,
    public wxEvtHandler,
    public std::enable_shared_from_this<WaveTrackAffordanceControls>
{
    std::weak_ptr<WaveClip> mFocusClip;
    std::weak_ptr<WaveTrackAffordanceHandle> mAffordanceHandle;
    std::weak_ptr<TrackPanelResizeHandle> mResizeHandle;
    std::weak_ptr<WaveClipTitleEditHandle> mTitleEditHandle;
    std::weak_ptr<SelectHandle> mSelectHandle;
    std::weak_ptr<WaveClipTrimHandle> mClipTrimHandle;

    std::weak_ptr<WaveClip> mEditedClip;
    std::shared_ptr<TextEditHelper> mTextEditHelper;

    wxFont mClipNameFont;

public:
    WaveTrackAffordanceControls(const std::shared_ptr<Track>& pTrack);

    std::vector<UIHandlePtr> HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject) override;

    void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override;

    //Invokes name editing for a clip that currently is
    //in focus(as a result of hit testing), returns true on success
    //false if there is no focus
    bool StartEditClipName(AudacityProject* project);

    std::weak_ptr<WaveClip> GetSelectedClip() const;

    unsigned CaptureKey
    (wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
        AudacityProject* project) override;
    
    unsigned KeyDown (wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
        AudacityProject* project) override;

    unsigned Char
    (wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
        AudacityProject* project) override;

    unsigned LoseFocus(AudacityProject *project) override;

    void OnTextEditFinished(AudacityProject* project, const wxString& text) override;
    void OnTextEditCancelled(AudacityProject* project) override;
    void OnTextModified(AudacityProject* project, const wxString& text) override;
    void OnTextContextMenu(AudacityProject* project, const wxPoint& position) override;

    bool StartEditNameOfMatchingClip( AudacityProject &project,
        std::function<bool(WaveClip&)> test /*!<
            Edit the first clip in the track's list satisfying the test */
    );

    unsigned OnAffordanceClick(const TrackPanelMouseEvent& event, AudacityProject* project);

private:
    void ResetClipNameEdit();

    void OnTrackChanged(TrackListEvent& evt);

    unsigned ExitTextEditing();

    bool SelectNextClip(ViewInfo& viewInfo, AudacityProject* project, bool forward);

    std::shared_ptr<TextEditHelper> MakeTextEditHelper(const wxString& text);
};
