1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NoteTrackVRulerControls.cpp
6
7 Paul Licameli split from TrackPanel.cpp
8
9 **********************************************************************/
10
11
12
13 #ifdef USE_MIDI
14 #include "NoteTrackVRulerControls.h"
15
16 #include "NoteTrackVZoomHandle.h"
17
18 #include "../../../../HitTestResult.h"
19 #include "../../../../NoteTrack.h"
20 #include "../../../../ProjectHistory.h"
21 #include "../../../../RefreshCode.h"
22 #include "../../../../TrackArtist.h"
23 #include "../../../../TrackPanelMouseEvent.h"
24
25 #include "AColor.h"
26 #include "../../../../TrackPanelDrawingContext.h"
27 #include "../../../../widgets/Ruler.h"
28
29 #include <wx/dc.h>
30
31 ///////////////////////////////////////////////////////////////////////////////
~NoteTrackVRulerControls()32 NoteTrackVRulerControls::~NoteTrackVRulerControls()
33 {
34 }
35
HitTest(const TrackPanelMouseState & st,const AudacityProject * pProject)36 std::vector<UIHandlePtr> NoteTrackVRulerControls::HitTest
37 (const TrackPanelMouseState &st,
38 const AudacityProject *pProject)
39 {
40 std::vector<UIHandlePtr> results;
41 UIHandlePtr result;
42
43 if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
44 auto track = std::static_pointer_cast<NoteTrack>(FindTrack());
45 result = NoteTrackVZoomHandle::HitTest(
46 mVZoomHandle, st.state, track, st.rect);
47 if (result)
48 results.push_back(result);
49 }
50
51 auto more = TrackVRulerControls::HitTest(st, pProject);
52 std::copy(more.begin(), more.end(), std::back_inserter(results));
53
54 return results;
55 }
56
HandleWheelRotation(const TrackPanelMouseEvent & evt,AudacityProject * pProject)57 unsigned NoteTrackVRulerControls::HandleWheelRotation
58 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
59 {
60 using namespace RefreshCode;
61 const wxMouseEvent &event = evt.event;
62
63 if (!(event.ShiftDown() || event.CmdDown()))
64 return RefreshNone;
65
66 // Always stop propagation even if the ruler didn't change. The ruler
67 // is a narrow enough target.
68 evt.event.Skip(false);
69
70 const auto pTrack = FindTrack();
71 if (!pTrack)
72 return RefreshNone;
73
74 auto steps = evt.steps;
75 const auto nt = static_cast<NoteTrack*>(pTrack.get());
76
77 if (event.CmdDown() && !event.ShiftDown()) {
78 if (steps > 0)
79 nt->ZoomIn(evt.rect, evt.event.m_y);
80 else
81 nt->ZoomOut(evt.rect, evt.event.m_y);
82 } else if (!event.CmdDown() && event.ShiftDown()) {
83 // Scroll some fixed number of notes, independent of zoom level or track height:
84 static const int movement = 6; // 6 semitones is half an octave
85 nt->ShiftNoteRange((int) (steps * movement));
86 } else {
87 return RefreshNone;
88 }
89
90 ProjectHistory::Get( *pProject ).ModifyState(false);
91
92 return RefreshCell | UpdateVRuler;
93 }
94
Draw(TrackPanelDrawingContext & context,const wxRect & rect_,unsigned iPass)95 void NoteTrackVRulerControls::Draw(
96 TrackPanelDrawingContext &context,
97 const wxRect &rect_, unsigned iPass )
98 {
99 TrackVRulerControls::Draw( context, rect_, iPass );
100
101 // Draw on a later pass like other vertical rulers,
102 // although the bevel is done a little differently
103
104 if ( iPass == TrackArtist::PassControls ) {
105 // The note track draws a vertical keyboard to label pitches
106 auto track = std::static_pointer_cast<NoteTrack>( FindTrack() );
107 if ( !track )
108 return;
109
110 auto rect = rect_;
111 --rect.width;
112 --rect.height;
113
114 bool highlight = false;
115 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
116 highlight = rect.Contains(context.lastState.GetPosition());
117 #endif
118
119 const auto artist = TrackArtist::Get( context );
120 UpdateRuler(rect);
121
122 auto dc = &context.dc;
123
124 dc->SetPen(highlight ? AColor::uglyPen : *wxTRANSPARENT_PEN);
125 dc->SetBrush(*wxWHITE_BRUSH);
126 wxRect bev = rect;
127 bev.x++;
128 bev.width--;
129 dc->DrawRectangle(bev);
130
131 rect.y += 1;
132 rect.height -= 1;
133
134 NoteTrackDisplayData data{ track.get(), rect };
135
136 wxPen hilitePen;
137 hilitePen.SetColour(120, 120, 120);
138 wxBrush blackKeyBrush;
139 blackKeyBrush.SetColour(70, 70, 70);
140
141 dc->SetBrush(blackKeyBrush);
142
143 int fontSize = 10;
144 #ifdef __WXMSW__
145 fontSize = 8;
146 #endif
147
148 wxFont labelFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
149 dc->SetFont(labelFont);
150
151 int octave = 0;
152 int obottom = data.GetOctaveBottom(octave);
153 int marg = data.GetNoteMargin();
154
155 while (obottom >= rect.y) {
156 dc->SetPen(*wxBLACK_PEN);
157 for (int white = 0; white < 7; white++) {
158 int pos = data.GetWhitePos(white);
159 if (obottom - pos > rect.y + marg + 1 &&
160 // don't draw too close to margin line -- it's annoying
161 obottom - pos < rect.y + rect.height - marg - 3)
162 AColor::Line(*dc, rect.x, obottom - pos,
163 rect.x + rect.width, obottom - pos);
164 }
165 wxRect br = rect;
166 br.height = data.GetPitchHeight(1);
167 br.x++;
168 br.width = 17;
169 for (int black = 0; black < 5; black++) {
170 br.y = obottom - data.GetBlackPos(black);
171 if (br.y > rect.y + marg - 2 && br.y + br.height < rect.y + rect.height - marg) {
172 dc->SetPen(hilitePen);
173 dc->DrawRectangle(br);
174 dc->SetPen(*wxBLACK_PEN);
175 AColor::Line(*dc,
176 br.x + 1, br.y + br.height - 1,
177 br.x + br.width - 1, br.y + br.height - 1);
178 AColor::Line(*dc,
179 br.x + br.width - 1, br.y + 1,
180 br.x + br.width - 1, br.y + br.height - 1);
181 }
182 }
183
184 if (octave >= 1 && octave <= 10) {
185 wxString s;
186 // ISO standard: A440 is in the 4th octave, denoted
187 // A4 <- the "4" should be a subscript.
188 s.Printf(wxT("C%d"), octave - 1);
189 wxCoord width, height;
190 dc->GetTextExtent(s, &width, &height);
191 if (obottom - height + 4 > rect.y &&
192 obottom + 4 < rect.y + rect.height) {
193 dc->SetTextForeground(wxColour(60, 60, 255));
194 dc->DrawText(s, rect.x + rect.width - width,
195 obottom - height + 2);
196 }
197 }
198 obottom = data.GetOctaveBottom(++octave);
199 }
200 // draw lines delineating the out-of-bounds margins
201 dc->SetPen(*wxBLACK_PEN);
202 // you would think the -1 offset here should be -2 to match the
203 // adjustment to rect.y (see above), but -1 produces correct output
204 AColor::Line(*dc, rect.x, rect.y + marg - 1, rect.x + rect.width, rect.y + marg - 1);
205 // since the margin gives us the bottom of the line,
206 // the extra -1 gets us to the top
207 AColor::Line(*dc, rect.x, rect.y + rect.height - marg - 1,
208 rect.x + rect.width, rect.y + rect.height - marg - 1);
209
210 }
211 }
212
213
UpdateRuler(const wxRect & rect)214 void NoteTrackVRulerControls::UpdateRuler( const wxRect &rect )
215 {
216 // The note track isn't drawing a ruler at all!
217 // But it needs to!
218
219 const auto nt = std::static_pointer_cast< NoteTrack >( FindTrack() );
220 if (!nt)
221 return;
222
223 static Ruler ruler;
224 const auto vruler = &ruler;
225
226 vruler->SetBounds(rect.x, rect.y, rect.x + 1, rect.y + rect.height-1);
227 vruler->SetOrientation(wxVERTICAL);
228
229 vruler->GetMaxSize( &nt->vrulerSize.x, &nt->vrulerSize.y );
230 }
231 #endif
232