Audacity 3.2.0
NoteTrackControls.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5NoteTrackControls.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#ifdef USE_MIDI
13#include "NoteTrackControls.h"
15#include "AColor.h"
16#include "AllThemeResources.h"
17#include "../../ui/PlayableTrackButtonHandles.h"
20#include "../../../../TrackArtist.h"
21#include "../../../../TrackPanel.h"
22#include "../../../ui/CommonTrackInfo.h"
23#include "../../../../TrackPanelMouseEvent.h"
24#include "NoteTrack.h"
25#include "../../../../widgets/PopupMenuTable.h"
26#include "ProjectHistory.h"
27#include "../../../../ProjectWindows.h"
28#include "../../../../RefreshCode.h"
29#include "Theme.h"
30#include <wx/frame.h>
31
34{
35}
36
37std::vector<UIHandlePtr> NoteTrackControls::HitTest
38(const TrackPanelMouseState & st,
39 const AudacityProject *pProject)
40{
41 // Hits are mutually exclusive, results single
42 std::vector<UIHandlePtr> results;
43 const wxMouseState &state = st.state;
44 const wxRect &rect = st.rect;
45 if (state.ButtonIsDown(wxMOUSE_BTN_ANY)) {
46 auto track = std::static_pointer_cast<NoteTrack>(FindTrack());
47 auto result = [&]{
48 UIHandlePtr result;
49 if (NULL != (result = MuteButtonHandle::HitTest(
50 mMuteHandle, state, rect, pProject, track)))
51 return result;
52
53 if (NULL != (result = SoloButtonHandle::HitTest(
54 mSoloHandle, state, rect, pProject, track)))
55 return result;
56#ifdef EXPERIMENTAL_MIDI_OUT
57 if (NULL != (result = VelocitySliderHandle::HitTest(
58 mVelocityHandle, state, rect, track)))
59 return result;
60 if (NULL != (result = NoteTrackButtonHandle::HitTest(
61 mClickHandle, state, rect, track)))
62 return result;
63#endif
64 return result;
65 }();
66 if (result) {
67 results.push_back(result);
68 return results;
69 }
70 }
71
72 return NoteTrackControlsBase::HitTest(st, pProject);
73}
74
76{
78 : PopupMenuTable{ "NoteTrack" }
79 {}
81
82public:
84
85private:
86 void InitUserData(void *pUserData) override
87 {
88 mpData = static_cast<NoteTrackControlsBase::InitMenuData*>(pUserData);
89 }
90
92
93 void OnChangeOctave(wxCommandEvent &);
94};
95
97{
98 static NoteTrackMenuTable instance;
99 return instance;
100}
101
102enum {
105};
106
108void NoteTrackMenuTable::OnChangeOctave(wxCommandEvent &event)
109{
110 NoteTrack *const pTrack = static_cast<NoteTrack*>(mpData->pTrack);
111
112 wxASSERT(event.GetId() == OnUpOctaveID
113 || event.GetId() == OnDownOctaveID);
114
115 const bool bDown = (OnDownOctaveID == event.GetId());
116 NoteTrackRange::Get(*pTrack).ShiftNoteRange((bDown) ? -12 : 12);
117
120 .ModifyState(false);
122}
123
125 BeginSection( "Basic" );
126 AppendItem( "Up", OnUpOctaveID, XXO("Up &Octave"), POPUP_MENU_FN( OnChangeOctave ) );
127 AppendItem( "Down", OnDownOctaveID, XXO("Down Octa&ve"), POPUP_MENU_FN( OnChangeOctave ) );
130
132{
133#if defined(USE_MIDI)
135#else
136 return NULL;
137#endif
138}
139
140// drawing related
141#include "../../../../widgets/ASlider.h"
142#include "../../../../TrackPanelDrawingContext.h"
143#include "ViewInfo.h"
144
146
147#ifdef USE_MIDI
148enum : int {
149 // PRL: was it correct to include the margin?
153#endif
154
157
158namespace {
159void GetMidiControlsHorizontalBounds( const wxRect &rect, wxRect &dest )
160{
161 dest.x = rect.x + 1; // To center slightly
162 // PRL: TODO: kMidiCellWidth is defined in terms of the other constant
163 // kTrackInfoWidth but I am trying to avoid use of that constant.
164 // Can cell width be computed from dest.width instead?
165 dest.width = kMidiCellWidth * 4;
166}
167
169( LWSlider *(*Selector)
170 (const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow*),
171 wxDC *dc, const wxRect &rect, const Track *pTrack,
172 wxWindow *pParent,
173 bool captured, bool highlight )
174{
175 wxRect sliderRect = rect;
176 CommonTrackInfo::GetSliderHorizontalBounds( rect.GetTopLeft(), sliderRect );
177 auto nt = static_cast<const NoteTrack*>( pTrack );
178 Selector( sliderRect, nt, captured, pParent )->OnPaint(*dc, highlight);
179}
180
181#ifdef EXPERIMENTAL_MIDI_OUT
182void VelocitySliderDrawFunction
183( TrackPanelDrawingContext &context,
184 const wxRect &rect, const Track *pTrack )
185{
186 auto dc = &context.dc;
187 auto target = dynamic_cast<VelocitySliderHandle*>( context.target.get() );
188 bool hit = target && target->GetTrack().get() == pTrack;
189 bool captured = hit && target->IsDragging();
190
191 const auto artist = TrackArtist::Get( context );
192 auto pParent = FindProjectFrame( artist->parent->GetProject() );
193
195 &NoteTrackControls::VelocitySlider, dc, rect, pTrack,
196 pParent, captured, hit);
197}
198#endif
199
200// Draws the midi channel toggle buttons within the given rect.
201// The rect should be evenly divisible by 4 on both axes.
203( const NoteTrack *pTrack, wxDC & dc, const wxRect &rect, int highlightedChannel )
204{
205 dc.SetTextForeground(theTheme.Colour(clrLabelTrackText));
206 wxASSERT_MSG(rect.width % 4 == 0, "Midi channel control rect width must be divisible by 4");
207 wxASSERT_MSG(rect.height % 4 == 0, "Midi channel control rect height must be divisible by 4");
208
209 auto cellWidth = rect.width / 4;
210 auto cellHeight = rect.height / 4;
211
212 wxRect box;
213 for (int row = 0; row < 4; row++) {
214 for (int col = 0; col < 4; col++) {
215 // chanName is the "external" channel number (1-16)
216 // used by AColor and button labels
217 int chanName = row * 4 + col + 1;
218
219 box.x = rect.x + col * cellWidth;
220 box.y = rect.y + row * cellHeight;
221 box.width = cellWidth;
222 box.height = cellHeight;
223
224 bool visible = pTrack ? pTrack->IsVisibleChan(chanName - 1) : true;
225 if (visible) {
226 // highlightedChannel counts 0 based
227 if ( chanName == highlightedChannel + 1 )
228 AColor::LightMIDIChannel(&dc, chanName);
229 else
230 AColor::MIDIChannel(&dc, chanName);
231 dc.DrawRectangle(box);
232// two choices: channel is enabled (to see and play) when button is in
233// "up" position (original Audacity style) or in "down" position
234//
235#define CHANNEL_ON_IS_DOWN 1
236#if CHANNEL_ON_IS_DOWN
237 AColor::DarkMIDIChannel(&dc, chanName);
238#else
239 AColor::LightMIDIChannel(&dc, chanName);
240#endif
241 AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
242 AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
243
244#if CHANNEL_ON_IS_DOWN
245 AColor::LightMIDIChannel(&dc, chanName);
246#else
247 AColor::DarkMIDIChannel(&dc, chanName);
248#endif
249 AColor::Line(dc,
250 box.x + box.width - 1, box.y,
251 box.x + box.width - 1, box.y + box.height - 1);
252 AColor::Line(dc,
253 box.x, box.y + box.height - 1,
254 box.x + box.width - 1, box.y + box.height - 1);
255 } else {
256 if ( chanName == highlightedChannel + 1 )
257 AColor::LightMIDIChannel(&dc, chanName);
258 else
259 AColor::MIDIChannel(&dc, 0);
260 dc.DrawRectangle(box);
261#if CHANNEL_ON_IS_DOWN
263#else
265#endif
266 AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y);
267 AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1);
268
269#if CHANNEL_ON_IS_DOWN
271#else
273#endif
274 AColor::Line(dc,
275 box.x + box.width - 1, box.y,
276 box.x + box.width - 1, box.y + box.height - 1);
277 AColor::Line(dc,
278 box.x, box.y + box.height - 1,
279 box.x + box.width - 1, box.y + box.height - 1);
280
281 }
282
283 wxString text;
284 wxCoord w;
285 wxCoord h;
286
287 text.Printf(wxT("%d"), chanName);
288 dc.GetTextExtent(text, &w, &h);
289
290 dc.DrawText(text, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2);
291 }
292 }
293 dc.SetTextForeground(theTheme.Colour(clrTrackPanelText));
294 AColor::MIDIChannel(&dc, 0); // always return with gray color selected
295}
296
298( TrackPanelDrawingContext &context,
299 const wxRect &rect, const Track *pTrack )
300{
301 auto target = dynamic_cast<NoteTrackButtonHandle*>( context.target.get() );
302 bool hit = target && target->GetTrack().get() == pTrack;
303 auto channel = hit ? target->GetChannel() : -1;
304 auto &dc = context.dc;
305 wxRect midiRect = rect;
306 GetMidiControlsHorizontalBounds(rect, midiRect);
308 ( static_cast<const NoteTrack *>(pTrack), dc, midiRect, channel );
309}
310}
311
312static const struct NoteTrackTCPLines
314 *static_cast<TCPLines*>(this) =
315 NoteTrackControlsBase::StaticNoteTCPLines();
316 insert( end(), {
319#ifdef EXPERIMENTAL_MIDI_OUT
321 VelocitySliderDrawFunction },
322#endif
323 } );
325
326void NoteTrackControls::GetVelocityRect(const wxPoint &topleft, wxRect & dest)
327{
330 dest.y = topleft.y + results.first;
331 dest.height = results.second;
332}
333
334void NoteTrackControls::GetMidiControlsRect(const wxRect & rect, wxRect & dest)
335{
337 auto results = TrackInfo::CalcItemY(
339 dest.y = rect.y + results.first;
340 dest.height = results.second;
341}
342
344{
346}
347
349{
350 return noteTrackTCPLines;
351};
352
353namespace {
354
355#ifdef EXPERIMENTAL_MIDI_OUT
356 std::unique_ptr<LWSlider>
357 gVelocityCaptured
358 , gVelocity
359 ;
360#endif
361
362}
363
364#ifdef EXPERIMENTAL_MIDI_OUT
366(const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow *pParent)
367{
368 static std::once_flag flag;
369 std::call_once( flag, []{ ReCreateVelocitySlider({}); });
371
372 wxPoint pos = sliderRect.GetPosition();
373 float velocity = t ? t->GetVelocity() : 0.0;
374
375 gVelocity->Move(pos);
376 gVelocity->Set(velocity);
377 gVelocityCaptured->Move(pos);
378 gVelocityCaptured->Set(velocity);
379
380 auto slider = (captured ? gVelocityCaptured : gVelocity).get();
381 slider->SetParent( pParent );
382 return slider;
383}
384#endif
385
387{
388 if (message.appearance)
389 return;
390#ifdef EXPERIMENTAL_MIDI_OUT
391 wxPoint point{ 0, 0 };
392 wxRect sliderRect;
393 GetVelocityRect(point, sliderRect);
394
395 /* i18n-hint: Title of the Velocity slider, used to adjust the volume of note tracks */
396 gVelocity = std::make_unique<LWSlider>(nullptr, XO("Velocity"),
397 wxPoint(sliderRect.x, sliderRect.y),
398 wxSize(sliderRect.width, sliderRect.height),
399 VEL_SLIDER);
400 gVelocity->SetDefaultValue(0.0);
401 gVelocityCaptured = std::make_unique<LWSlider>(nullptr, XO("Velocity"),
402 wxPoint(sliderRect.x, sliderRect.y),
403 wxSize(sliderRect.width, sliderRect.height),
404 VEL_SLIDER);
405 gVelocityCaptured->SetDefaultValue(0.0);
406#endif
407}
408
411 return [](NoteTrack &track) {
412 return std::make_shared<NoteTrackControls>( track.SharedPointer() );
413 };
414}
415
416#include "../../../ui/ChannelView.h"
417
420 return [](NoteTrack &) {
422 };
423}
424
425#endif
#define VEL_SLIDER
Definition: ASlider.h:37
wxT("CloseDown"))
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
std::vector< TrackInfo::TCPLine > TCPLines
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetNoteTrackControls)
EndSection()
NoteTrackTCPLines noteTrackTCPLines
TrackInfo::TCPLine TCPLine
@ OnUpOctaveID
@ OnDownOctaveID
BeginSection("Basic")
@ kMidiCellWidth
@ kMidiCellHeight
AppendItem("Up", OnUpOctaveID, XXO("Up &Octave"), POPUP_MENU_FN(OnChangeOctave))
#define END_POPUP_MENU()
#define BEGIN_POPUP_MENU(HandlerClass)
#define POPUP_MENU_FN(memFn)
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
@ kTrackInfoSliderExtra
Definition: ViewInfo.h:101
@ kTrackInfoBtnSize
Definition: ViewInfo.h:96
@ kTrackInfoSliderHeight
Definition: ViewInfo.h:98
static std::once_flag flag
@ kTrackInfoWidth
Definition: ZoomInfo.h:30
@ kLeftMargin
Definition: ZoomInfo.h:27
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void LightMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:681
static void MIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:665
static void DarkMIDIChannel(wxDC *dc, int channel)
Definition: AColor.cpp:699
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
std::shared_ptr< ChannelType > GetChannel(size_t iChannel)
Retrieve a channel, cast to the given type.
Definition: Channel.h:344
virtual std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *) override=0
std::shared_ptr< Track > FindTrack()
Lightweight version of ASlider. In other words it does not have a window permanently associated with ...
Definition: ASlider.h:64
static UIHandlePtr HitTest(std::weak_ptr< MuteButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< Track > &pTrack)
std::shared_ptr< NoteTrack > GetTrack() const
static UIHandlePtr HitTest(std::weak_ptr< NoteTrackButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const std::shared_ptr< NoteTrack > &pTrack)
static void ReCreateVelocitySlider(struct ThemeChangeMessage)
static void GetMidiControlsRect(const wxRect &rect, wxRect &dest)
std::weak_ptr< NoteTrackButtonHandle > mClickHandle
std::weak_ptr< SoloButtonHandle > mSoloHandle
std::weak_ptr< VelocitySliderHandle > mVelocityHandle
static unsigned DefaultNoteTrackHeight()
const TCPLines & GetTCPLines() const override
static void GetVelocityRect(const wxPoint &topleft, wxRect &dest)
static LWSlider * VelocitySlider(const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow *pParent)
std::weak_ptr< MuteButtonHandle > mMuteHandle
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:86
bool IsVisibleChan(int c) const
Definition: NoteTrack.h:165
void OnChangeOctave(wxCommandEvent &)
Scrolls the note track up or down by an octave.
void InitUserData(void *pUserData) override
Called before the menu items are appended.
DECLARE_POPUP_MENU(NoteTrackMenuTable)
NoteTrackControlsBase::InitMenuData * mpData
static NoteTrackMenuTable & Instance()
static NoteTrackRange & Get(const NoteTrack &track)
Allow mutative access to attached data of a const track.
void ShiftNoteRange(int offset)
Shifts all notes vertically by the given pitch.
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static UIHandlePtr HitTest(std::weak_ptr< SoloButtonHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< Track > &pTrack)
wxColour & Colour(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
AUDACITY_DLL_API unsigned DefaultTrackHeight(const TCPLines &topLines)
AUDACITY_DLL_API void GetSliderHorizontalBounds(const wxPoint &topleft, wxRect &dest)
bool HitTest(const RectangleArgs &args, const wxPoint &mousePos)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
AUDACITY_DLL_API std::pair< int, int > CalcItemY(const TCPLines &lines, unsigned iItem)
Definition: TrackInfo.cpp:79
static void DrawLabelControls(const NoteTrack *pTrack, wxDC &dc, const wxRect &rect, int highlightedChannel)
void MidiControlsDrawFunction(TrackPanelDrawingContext &context, const wxRect &rect, const Track *pTrack)
void GetMidiControlsHorizontalBounds(const wxRect &rect, wxRect &dest)
void SliderDrawFunction(LWSlider *(*Selector)(const wxRect &sliderRect, const NoteTrack *t, bool captured, wxWindow *), wxDC *dc, const wxRect &rect, const Track *pTrack, wxWindow *pParent, bool captured, bool highlight)
For defining overrides of the method.
std::optional< PreferredSystemAppearance > appearance
Definition: Theme.h:112