1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 NoteTrackVZoomHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 
12 #ifdef USE_MIDI
13 #include "NoteTrackVZoomHandle.h"
14 
15 #include "../../../ui/TrackVRulerControls.h"
16 
17 #include "../../../../HitTestResult.h"
18 #include "../../../../NoteTrack.h"
19 #include "Project.h"
20 #include "../../../../ProjectHistory.h"
21 #include "../../../../RefreshCode.h"
22 #include "../../../../TrackArtist.h"
23 #include "../../../../TrackPanelMouseEvent.h"
24 #include "../../../../widgets/PopupMenuTable.h"
25 #include "../../../../../images/Cursors.h"
26 #include "Prefs.h"
27 
28 #include <wx/event.h>
29 
30 namespace
31 {
32 
33    struct InitMenuData
34    {
35    public:
36       AudacityProject &project;
37       NoteTrack *pTrack;
38       wxRect rect;
39       unsigned result;
40       int yy;
41    };
42 
IsDragZooming(int zoomStart,int zoomEnd)43    bool IsDragZooming(int zoomStart, int zoomEnd)
44    {
45       const int DragThreshold = 3;// Anything over 3 pixels is a drag, else a click.
46       bool bVZoom;
47       gPrefs->Read(wxT("/GUI/VerticalZooming"), &bVZoom, false);
48       return bVZoom && (abs(zoomEnd - zoomStart) > DragThreshold);
49    }
50 
51 }
52 
53 ///////////////////////////////////////////////////////////////////////////////
54 
NoteTrackVZoomHandle(const std::shared_ptr<NoteTrack> & pTrack,const wxRect & rect,int y)55 NoteTrackVZoomHandle::NoteTrackVZoomHandle
56 (const std::shared_ptr<NoteTrack> &pTrack, const wxRect &rect, int y)
57    : mpTrack{ pTrack } , mZoomStart(y), mZoomEnd(y), mRect(rect)
58 {
59 }
60 
Enter(bool,AudacityProject *)61 void NoteTrackVZoomHandle::Enter(bool, AudacityProject *)
62 {
63 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
64    mChangeHighlight = RefreshCode::RefreshCell;
65 #endif
66 }
67 
HitPreview(const wxMouseState & state)68 HitTestPreview NoteTrackVZoomHandle::HitPreview(const wxMouseState &state)
69 {
70    static auto zoomInCursor =
71       ::MakeCursor(wxCURSOR_MAGNIFIER, ZoomInCursorXpm, 19, 15);
72    static auto zoomOutCursor =
73       ::MakeCursor(wxCURSOR_MAGNIFIER, ZoomOutCursorXpm, 19, 15);
74    static  wxCursor arrowCursor{ wxCURSOR_ARROW };
75 
76    bool bVZoom;
77    gPrefs->Read(wxT("/GUI/VerticalZooming"), &bVZoom, false);
78    bVZoom &= !state.RightIsDown();
79    const auto message = bVZoom ?
80       XO("Click to vertically zoom in. Shift-click to zoom out. Drag to specify a zoom region.") :
81       XO("Right-click for menu.");
82 
83    return {
84       message,
85       bVZoom ? (state.ShiftDown() ? &*zoomOutCursor : &*zoomInCursor) : &arrowCursor
86       // , message
87    };
88 }
89 
HitTest(std::weak_ptr<NoteTrackVZoomHandle> & holder,const wxMouseState & state,const std::shared_ptr<NoteTrack> & pTrack,const wxRect & rect)90 UIHandlePtr NoteTrackVZoomHandle::HitTest
91 (std::weak_ptr<NoteTrackVZoomHandle> &holder,
92  const wxMouseState &state,
93  const std::shared_ptr<NoteTrack> &pTrack, const wxRect &rect)
94 {
95    if (pTrack) {
96       auto result = std::make_shared<NoteTrackVZoomHandle>(
97          pTrack, rect, state.m_y);
98       result = AssignUIHandlePtr(holder, result);
99       return result;
100    }
101    return {};
102 }
103 
~NoteTrackVZoomHandle()104 NoteTrackVZoomHandle::~NoteTrackVZoomHandle()
105 {
106 }
107 
HandlesRightClick()108 bool NoteTrackVZoomHandle::HandlesRightClick()
109 {
110    return true;
111 }
112 
Click(const TrackPanelMouseEvent &,AudacityProject *)113 UIHandle::Result NoteTrackVZoomHandle::Click
114 (const TrackPanelMouseEvent &, AudacityProject *)
115 {
116    // change note track to zoom like audio track
117    //          mpTrack->StartVScroll();
118 
119    return RefreshCode::RefreshNone;
120 }
121 
Drag(const TrackPanelMouseEvent & evt,AudacityProject * pProject)122 UIHandle::Result NoteTrackVZoomHandle::Drag
123 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
124 {
125    using namespace RefreshCode;
126    auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
127    if (!pTrack)
128       return Cancelled;
129 
130    const wxMouseEvent &event = evt.event;
131    mZoomEnd = event.m_y;
132    if (IsDragZooming(mZoomStart, mZoomEnd)) {
133       // changed Note track to work like audio track
134       //         pTrack->VScroll(mZoomStart, mZoomEnd);
135       return RefreshAll;
136    }
137    return RefreshNone;
138 }
139 
Preview(const TrackPanelMouseState & st,AudacityProject *)140 HitTestPreview NoteTrackVZoomHandle::Preview
141 (const TrackPanelMouseState &st, AudacityProject *)
142 {
143    return HitPreview(st.state);
144 }
145 
146 enum {
147    OnZoomFitVerticalID = 20000,
148    OnZoomResetID,
149    OnZoomDiv2ID,
150    OnZoomTimes2ID,
151    OnZoomHalfWaveID,
152    OnZoomInVerticalID,
153    OnZoomOutVerticalID,
154 
155    OnZoomMaxID,
156 
157    OnUpOctaveID,
158    OnDownOctaveID,
159 };
160 ///////////////////////////////////////////////////////////////////////////////
161 // Table class
162 
163 class NoteTrackVRulerMenuTable
164   : public PopupMenuTable
165   , private PrefsListener
166 {
NoteTrackVRulerMenuTable()167    NoteTrackVRulerMenuTable()
168       : PopupMenuTable{ "NoteTrackVRuler" }
169    {};
~NoteTrackVRulerMenuTable()170    virtual ~NoteTrackVRulerMenuTable() {}
171    DECLARE_POPUP_MENU(NoteTrackVRulerMenuTable);
172 
173 public:
174    static NoteTrackVRulerMenuTable &Instance();
175 
176 protected:
177    enum {
178 // Note that these can be with or without spectrum view which
179 // adds a constant.
180 //const int kZoom1to1 = 1;
181 //const int kZoomTimes2 = 2;
182 //const int kZoomDiv2 = 3;
183 //const int kZoomHalfWave = 4;
184 //const int kZoomInByDrag = 5;
185       kZoomIn = 6,
186       kZoomOut = 7,
187       kZoomReset = 8,
188       kZoomMax = 9,
189       kUpOctave = 10,
190       kDownOctave = 11,
191    };
192 
193    InitMenuData *mpData {};
194    void OnZoom( int iZoomCode );
195 // void OnZoomFitVertical(wxCommandEvent&){ OnZoom( kZoom1to1 );};
OnZoomReset(wxCommandEvent &)196    void OnZoomReset(wxCommandEvent&){ OnZoom( kZoomReset );};
197 // void OnZoomDiv2Vertical(wxCommandEvent&){ OnZoom( kZoomDiv2 );};
198 // void OnZoomTimes2Vertical(wxCommandEvent&){ OnZoom( kZoomTimes2 );};
199 // void OnZoomHalfWave(wxCommandEvent&){ OnZoom( kZoomHalfWave );};
OnZoomInVertical(wxCommandEvent &)200    void OnZoomInVertical(wxCommandEvent&){ OnZoom( kZoomIn );};
OnZoomOutVertical(wxCommandEvent &)201    void OnZoomOutVertical(wxCommandEvent&){ OnZoom( kZoomOut );};
OnZoomMax(wxCommandEvent &)202    void OnZoomMax(wxCommandEvent&){ OnZoom( kZoomMax );};
OnUpOctave(wxCommandEvent &)203    void OnUpOctave(wxCommandEvent&){ OnZoom( kUpOctave );};
OnDownOctave(wxCommandEvent &)204    void OnDownOctave(wxCommandEvent&){ OnZoom( kDownOctave );};
205 
206 private:
207    void InitUserData(void *pUserData) override;
208 
UpdatePrefs()209    void UpdatePrefs() override
210    {
211       // Because labels depend on advanced vertical zoom setting
212       PopupMenuTable::Clear();
213    }
214 };
215 
Instance()216 NoteTrackVRulerMenuTable &NoteTrackVRulerMenuTable::Instance()
217 {
218    static NoteTrackVRulerMenuTable instance;
219    return instance;
220 }
221 
InitUserData(void * pUserData)222 void NoteTrackVRulerMenuTable::InitUserData(void *pUserData)
223 {
224    mpData = static_cast<InitMenuData*>(pUserData);
225 }
226 
OnZoom(int iZoomCode)227 void NoteTrackVRulerMenuTable::OnZoom( int iZoomCode ){
228    switch( iZoomCode ){
229    case kZoomReset:
230       mpData->pTrack->ZoomAllNotes();
231       break;
232    case kZoomIn:
233       mpData->pTrack->ZoomIn(mpData->rect, mpData->yy);
234       break;
235    case kZoomOut:
236       mpData->pTrack->ZoomOut(mpData->rect, mpData->yy);
237       break;
238    case kZoomMax:
239       mpData->pTrack->ZoomMaxExtent();
240       break;
241    case kUpOctave:
242       mpData->pTrack->ShiftNoteRange(12);
243       break;
244    case kDownOctave:
245       mpData->pTrack->ShiftNoteRange(-12);
246       break;
247    }
248    AudacityProject *const project = &mpData->project;
249    ProjectHistory::Get( *project ).ModifyState(false);
250    using namespace RefreshCode;
251    mpData->result = UpdateVRuler | RefreshAll;
252 }
253 
254 
255 BEGIN_POPUP_MENU(NoteTrackVRulerMenuTable)
256 
257    // Accelerators only if zooming enabled.
258    bool bVZoom;
259    gPrefs->Read(wxT("/GUI/VerticalZooming"), &bVZoom, false);
260 
261    BeginSection( "Zoom" );
262       BeginSection( "Basic" );
263          AppendItem( "Reset", OnZoomResetID,
264             MakeLabel( XXO("Zoom Reset"), bVZoom, XXO("Shift-Right-Click")),
265             POPUP_MENU_FN( OnZoomReset ) );
266          AppendItem( "Max", OnZoomMaxID,        XXO("Max Zoom"), POPUP_MENU_FN( OnZoomMax ) );
267       EndSection();
268 
269       BeginSection( "InOut" );
270          AppendItem( "In", OnZoomInVerticalID,
271             MakeLabel( XXO("Zoom In"), bVZoom, XXO("Left-Click/Left-Drag") ),
272             POPUP_MENU_FN( OnZoomInVertical ) );
273          AppendItem( "Out", OnZoomOutVerticalID,
274             MakeLabel( XXO("Zoom Out"), bVZoom, XXO("Shift-Left-Click") ),
275             POPUP_MENU_FN( OnZoomOutVertical ) );
276       EndSection();
277    EndSection();
278 
279    BeginSection( "Pan" );
280       BeginSection( "Octaves" );
281          AppendItem( "Up", OnUpOctaveID,   XXO("Up &Octave"),   POPUP_MENU_FN( OnUpOctave) );
282          AppendItem( "Down", OnDownOctaveID, XXO("Down Octa&ve"), POPUP_MENU_FN( OnDownOctave ) );
283       EndSection();
284    EndSection();
285 
END_POPUP_MENU()286 END_POPUP_MENU()
287 
288 
289 
290 UIHandle::Result NoteTrackVZoomHandle::Release
291 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
292  wxWindow *pParent)
293 {
294    using namespace RefreshCode;
295    auto pTrack = TrackList::Get( *pProject ).Lock(mpTrack);
296    if (!pTrack)
297       return RefreshNone;
298 
299    const wxMouseEvent &event = evt.event;
300    //const bool shiftDown = event.ShiftDown();
301    const bool rightUp = event.RightUp();
302 
303 
304    // Popup menu...
305    if (
306        rightUp &&
307        !(event.ShiftDown() || event.CmdDown()))
308    {
309       InitMenuData data {
310          *pProject, pTrack.get(), mRect, RefreshNone, event.m_y
311       };
312 
313       PopupMenuTable *const pTable =
314           (PopupMenuTable *) &NoteTrackVRulerMenuTable::Instance();
315       auto pMenu = PopupMenuTable::BuildMenu(pTable, &data);
316 
317       pMenu->Popup( *pParent, { event.m_x, event.m_y } );
318 
319       return data.result;
320    }
321 
322    bool bVZoom;
323    gPrefs->Read(wxT("/GUI/VerticalZooming"), &bVZoom, false);
324    bVZoom &= event.GetId() != kCaptureLostEventId;
325    if( !bVZoom )
326       return RefreshAll;
327 
328    if (IsDragZooming(mZoomStart, mZoomEnd)) {
329       pTrack->ZoomTo(evt.rect, mZoomStart, mZoomEnd);
330    }
331    else if (event.ShiftDown() || event.RightUp()) {
332       if (event.ShiftDown() && event.RightUp()) {
333          auto oldBotNote = pTrack->GetBottomNote();
334          auto oldTopNote = pTrack->GetTopNote();
335          // Zoom out to show all notes
336          pTrack->ZoomAllNotes();
337          if (pTrack->GetBottomNote() == oldBotNote &&
338                pTrack->GetTopNote() == oldTopNote) {
339             // However if we are already showing all notes, zoom out further
340             pTrack->ZoomMaxExtent();
341          }
342       } else {
343          // Zoom out
344          pTrack->ZoomOut(evt.rect, mZoomEnd);
345       }
346    }
347    else {
348       pTrack->ZoomIn(evt.rect, mZoomEnd);
349    }
350 
351    mZoomEnd = mZoomStart = 0;
352    ProjectHistory::Get( *pProject ).ModifyState(false);
353 
354    return RefreshAll;
355 }
356 
Cancel(AudacityProject * WXUNUSED (pProject))357 UIHandle::Result NoteTrackVZoomHandle::Cancel(AudacityProject *WXUNUSED(pProject))
358 {
359    // Cancel is implemented!  And there is no initial state to restore,
360    // so just return a code.
361    return RefreshCode::RefreshAll;
362 }
363 
Draw(TrackPanelDrawingContext & context,const wxRect & rect,unsigned iPass)364 void NoteTrackVZoomHandle::Draw(
365    TrackPanelDrawingContext &context,
366    const wxRect &rect, unsigned iPass )
367 {
368    if ( iPass == TrackArtist::PassZooming ) {
369       if (!mpTrack.lock()) //? TrackList::Lock()
370          return;
371 
372       if ( IsDragZooming( mZoomStart, mZoomEnd ) )
373          TrackVRulerControls::DrawZooming
374             ( context, rect, mZoomStart, mZoomEnd );
375    }
376 }
377 
DrawingArea(TrackPanelDrawingContext &,const wxRect & rect,const wxRect & panelRect,unsigned iPass)378 wxRect NoteTrackVZoomHandle::DrawingArea(
379    TrackPanelDrawingContext &,
380    const wxRect &rect, const wxRect &panelRect, unsigned iPass )
381 {
382    if ( iPass == TrackArtist::PassZooming )
383       return TrackVRulerControls::ZoomingArea( rect, panelRect );
384    else
385       return rect;
386 }
387 
388 #endif
389