1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NoteTrackView.cpp
6
7 Paul Licameli split from TrackPanel.cpp
8
9 **********************************************************************/
10
11
12 #include "NoteTrackView.h"
13
14 #ifdef USE_MIDI
15 #include "../lib-src/header-substitutes/allegro.h"
16
17 #include "NoteTrackVRulerControls.h"
18 #include "../../../../NoteTrack.h"
19
20 #include "AColor.h"
21 #include "AllThemeResources.h"
22 #include "../../../../HitTestResult.h"
23 #include "Theme.h"
24 #include "../../../../TrackArtist.h"
25 #include "../../../../TrackPanelDrawingContext.h"
26 #include "../../../../TrackPanelMouseEvent.h"
27 #include "ViewInfo.h"
28 #include "../../../ui/SelectHandle.h"
29 #include "StretchHandle.h"
30 #include "NoteTrackAffordanceControls.h"
31
32 #include <wx/dc.h>
33
NoteTrackView(const std::shared_ptr<Track> & pTrack)34 NoteTrackView::NoteTrackView( const std::shared_ptr<Track> &pTrack )
35 : CommonTrackView{ pTrack }
36 {
37 }
38
~NoteTrackView()39 NoteTrackView::~NoteTrackView()
40 {
41 }
42
DetailedHitTest(const TrackPanelMouseState & WXUNUSED (state),const AudacityProject * WXUNUSED (pProject),int,bool)43 std::vector<UIHandlePtr> NoteTrackView::DetailedHitTest
44 (const TrackPanelMouseState &WXUNUSED(state),
45 const AudacityProject *WXUNUSED(pProject), int, bool )
46 {
47 // Eligible for stretch?
48 UIHandlePtr result;
49 std::vector<UIHandlePtr> results;
50 #ifdef USE_MIDI
51 #ifdef EXPERIMENTAL_MIDI_STRETCHING
52 result = StretchHandle::HitTest(
53 mStretchHandle, state, pProject, Pointer<NoteTrack>(this) );
54 if (result)
55 results.push_back(result);
56 #endif
57 #endif
58
59 return results;
60 }
61
62 using DoGetNoteTrackView = DoGetView::Override< NoteTrack >;
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetNoteTrackView)63 DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetNoteTrackView) {
64 return [](NoteTrack &track) {
65 return std::make_shared<NoteTrackView>( track.SharedPointer() );
66 };
67 }
68
DoGetVRulerControls()69 std::shared_ptr<TrackVRulerControls> NoteTrackView::DoGetVRulerControls()
70 {
71 return
72 std::make_shared<NoteTrackVRulerControls>( shared_from_this() );
73 }
74
75 #define TIME_TO_X(t) (zoomInfo.TimeToPosition((t), rect.x))
76 #define X_TO_TIME(xx) (zoomInfo.PositionToTime((xx), rect.x))
77
GetAffordanceControls()78 std::shared_ptr<CommonTrackCell> NoteTrackView::GetAffordanceControls()
79 {
80 if (mpAffordanceCellControl == nullptr)
81 {
82 mpAffordanceCellControl = std::make_shared<NoteTrackAffordanceControls>(DoFindTrack());
83 }
84 return mpAffordanceCellControl;
85 }
86
87 namespace {
88
89 /*
90 Note: recall that Allegro attributes end in a type identifying letter.
91
92 In addition to standard notes, an Allegro_Note can denote a graphic.
93 A graphic is a note with a loud of zero (for quick testing) and an
94 attribute named "shapea" set to one of the following atoms:
95 line
96 from (time, pitch) to (time+dur, y1r), where y1r is an
97 attribute
98 rectangle
99 from (time, pitch) to (time+dur, y1r), where y1r is an
100 attribute
101 triangle
102 coordinates are (time, pitch), (x1r, y1r), (x2r, y2r)
103 dur must be the max of x1r-time, x2r-time
104 polygon
105 coordinates are (time, pitch), (x1r, y1r), (x2r, y2r),
106 (x3r, y3r), ... are coordinates (since we cannot represent
107 arrays as attribute values, we just generate as many
108 attribute names as we need)
109 dur must be the max of xNr-time for all N
110 oval
111 similar to rectangle
112 Note: this oval has horizontal and vertical axes only
113 text
114 drawn at (time, pitch)
115 duration should be zero (text is clipped based on time and duration,
116 NOT based on actual coordinates)
117
118 and optional attributes as follows:
119 linecolori is 0x00rrggbb format color for line or text foreground
120 fillcolori is 0x00rrggbb format color for fill or text background
121 linethicki is line thickness in pixels, 0 for no line
122 filll is true to fill rectangle or draw text background (default is false)
123 fonta is one of ['roman', 'swiss', 'modern'] (font, otherwise use default)
124 weighta may be 'bold' (font) (default is normal)
125 sizei is font size (default is 8)
126 justifys is a string containing two letters, a horizontal code and a
127 vertical code. The horizontal code is as follows:
128 l: the coordinate is to the left of the string (default)
129 c: the coordinate is at the center of the string
130 r: the coordinate is at the right of the string
131 The vertical code is as follows:
132 t: the coordinate is at the top of the string
133 c: the coordinate is at the center of the string
134 b: the coordinate is at the bottom of the string
135 d: the coordinate is at the baseline of the string (default)
136 Thus, -justifys:"lt" places the left top of the string at the point
137 given by (pitch, time). The default value is "ld".
138 */
139
140 // returns NULL if note is not a shape,
141 // returns atom (string) value of note if note is a shape
IsShape(Alg_note_ptr note)142 const char *IsShape(Alg_note_ptr note)
143 {
144 Alg_parameters_ptr parameters = note->parameters;
145 while (parameters) {
146 if (strcmp(parameters->parm.attr_name(), "shapea") == 0) {
147 return parameters->parm.a;
148 }
149 parameters = parameters->next;
150 }
151 return NULL;
152 }
153
154 // returns value of attr, or default if not found
LookupRealAttribute(Alg_note_ptr note,Alg_attribute attr,double def)155 double LookupRealAttribute(Alg_note_ptr note, Alg_attribute attr, double def)
156 {
157 Alg_parameters_ptr parameters = note->parameters;
158 while (parameters) {
159 if (parameters->parm.attr_name() == attr + 1 &&
160 parameters->parm.attr_type() == 'r') {
161 return parameters->parm.r;
162 }
163 parameters = parameters->next;
164 }
165 return def;
166 }
167
168 // returns value of attr, or default if not found
LookupIntAttribute(Alg_note_ptr note,Alg_attribute attr,long def)169 long LookupIntAttribute(Alg_note_ptr note, Alg_attribute attr, long def)
170 {
171 Alg_parameters_ptr parameters = note->parameters;
172 while (parameters) {
173 if (parameters->parm.attr_name() == attr + 1 &&
174 parameters->parm.attr_type() == 'i') {
175 return parameters->parm.i;
176 }
177 parameters = parameters->next;
178 }
179 return def;
180 }
181
182 // returns value of attr, or default if not found
LookupLogicalAttribute(Alg_note_ptr note,Alg_attribute attr,bool def)183 bool LookupLogicalAttribute(Alg_note_ptr note, Alg_attribute attr, bool def)
184 {
185 Alg_parameters_ptr parameters = note->parameters;
186 while (parameters) {
187 if (parameters->parm.attr_name() == attr + 1 &&
188 parameters->parm.attr_type() == 'l') {
189 return parameters->parm.l;
190 }
191 parameters = parameters->next;
192 }
193 return def;
194 }
195
196 // returns value of attr, or default if not found
LookupStringAttribute(Alg_note_ptr note,Alg_attribute attr,const char * def)197 const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, const char *def)
198 {
199 Alg_parameters_ptr parameters = note->parameters;
200 while (parameters) {
201 if (parameters->parm.attr_name() == attr + 1 &&
202 parameters->parm.attr_type() == 's') {
203 return parameters->parm.s;
204 }
205 parameters = parameters->next;
206 }
207 return def;
208 }
209
210 // returns value of attr, or default if not found
LookupAtomAttribute(Alg_note_ptr note,Alg_attribute attr,char * def)211 const char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def)
212 {
213 Alg_parameters_ptr parameters = note->parameters;
214 while (parameters) {
215 if (parameters->parm.attr_name() == attr + 1 &&
216 parameters->parm.attr_type() == 'a') {
217 return parameters->parm.s;
218 }
219 parameters = parameters->next;
220 }
221 return def;
222 }
223
224 // CLIP(x) changes x to lie between +/- CLIP_MAX due to graphics display problems
225 // with very large coordinate values (this happens when you zoom in very far)
226 // This will cause incorrect things to be displayed, but at these levels of zoom
227 // you will only see a small fraction of the overall shape. Note that rectangles
228 // and lines are clipped in a way that preserves correct graphics, so in
229 // particular, line plots will be correct at any zoom (limited by floating point
230 // precision).
231 #define CLIP_MAX 16000
232 #define CLIP(xx) { long c = (xx); if (c < -CLIP_MAX) c = -CLIP_MAX; \
233 if (c > CLIP_MAX) c = CLIP_MAX; (xx) = c; }
234
235 #define RED(i) ( unsigned char )( (((i) >> 16) & 0xff) )
236 #define GREEN(i) ( unsigned char )( (((i) >> 8) & 0xff) )
237 #define BLUE(i) ( unsigned char )( ((i) & 0xff) )
238
239 //#define PITCH_TO_Y(p) (rect.y + rect.height - (int)(pitchht * ((p) + 0.5 - pitch0) + 0.5))
240
241 /*
242 int PitchToY(double p, int bottom)
243 {
244 int octave = (((int) (p + 0.5)) / 12);
245 int n = ((int) (p + 0.5)) % 12;
246
247 return IPITCH_TO_Y((int) (p + 0.5));
248 // was: bottom - octave * octaveHeight - notePos[n] - 4;
249 }
250 */
251
252 /* DrawNoteBackground is called by DrawNoteTrack twice: once to draw
253 the unselected background, and once to draw the selected background.
254 The selected background is the same except for the horizontal range
255 and the colors. The background rectangle region is given by rect; the
256 selected region is given by sel. The first time this is called,
257 sel is equal to rect, and the entire region is drawn with unselected
258 background colors.
259 */
DrawNoteBackground(TrackPanelDrawingContext & context,const NoteTrack * track,const wxRect & rect,const wxRect & sel,const wxBrush & wb,const wxPen & wp,const wxBrush & bb,const wxPen & bp,const wxPen & mp)260 void DrawNoteBackground(TrackPanelDrawingContext &context,
261 const NoteTrack *track,
262 const wxRect &rect, const wxRect &sel,
263 const wxBrush &wb, const wxPen &wp,
264 const wxBrush &bb, const wxPen &bp,
265 const wxPen &mp)
266 {
267 auto &dc = context.dc;
268 const auto artist = TrackArtist::Get( context );
269 const auto &zoomInfo = *artist->pZoomInfo;
270
271 dc.SetBrush(wb);
272 dc.SetPen(wp);
273 #ifndef EXPERIMENTAL_NOTETRACK_OVERLAY
274 dc.DrawRectangle(sel); // fill rectangle with white keys background
275 #endif
276
277 int left = TIME_TO_X(track->GetOffset());
278 if (left < sel.x) left = sel.x; // clip on left
279
280 int right = TIME_TO_X(track->GetOffset() + track->GetSeq().get_real_dur());
281 if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right
282
283 // need overlap between MIDI data and the background region
284 if (left >= right) return;
285
286 NoteTrackDisplayData data{ track, rect };
287 dc.SetBrush(bb);
288 int octave = 0;
289 // obottom is the window coordinate of octave divider line
290 int obottom = data.GetOctaveBottom(octave);
291 // eOffset is for the line between E and F; there's another line
292 // between B and C, hence the offset of 2 for two line thicknesses
293 int eOffset = data.GetPitchHeight(5) + 2;
294 while (obottom > rect.y + data.GetNoteMargin() + 3) {
295 // draw a black line separating octaves if this octave bottom is visible
296 if (obottom < rect.y + rect.height - data.GetNoteMargin()) {
297 dc.SetPen(*wxBLACK_PEN);
298 // obottom - 1 because obottom is at the bottom of the line
299 AColor::Line(dc, left, obottom - 1, right, obottom - 1);
300 }
301 dc.SetPen(bp);
302 // draw a black-key stripe colored line separating E and F if visible
303 if (obottom - eOffset > rect.y && obottom - eOffset < rect.y + rect.height) {
304 AColor::Line(dc, left, obottom - eOffset,
305 right, obottom - eOffset);
306 }
307
308 // draw visible black key lines
309 wxRect br;
310 br.x = left;
311 br.width = right - left;
312 br.height = data.GetPitchHeight(1);
313 for (int black = 0; black < 5; black++) {
314 br.y = obottom - data.GetBlackPos(black);
315 if (br.y > rect.y && br.y + br.height < rect.y + rect.height) {
316 dc.DrawRectangle(br); // draw each black key background stripe
317 }
318 }
319 obottom = data.GetOctaveBottom(++octave);
320 }
321
322 // draw bar lines
323 Alg_seq_ptr seq = &track->GetSeq();
324 // We assume that sliding a NoteTrack around slides the barlines
325 // along with the notes. This means that when we write out a track
326 // as Allegro or MIDI without the offset, we'll need to insert an
327 // integer number of measures of silence, using tempo change to
328 // match the duration to the offset.
329 // Iterate over all time signatures to generate beat positions of
330 // bar lines, map the beats to times, map the times to position,
331 // and draw the bar lines that fall within the region of interest (sel)
332 // seq->convert_to_beats();
333 dc.SetPen(mp);
334 Alg_time_sigs &sigs = seq->time_sig;
335 int i = 0; // index into ts[]
336 double next_bar_beat = 0.0;
337 double beats_per_measure = 4.0;
338 while (true) {
339 if (i < sigs.length() && sigs[i].beat < next_bar_beat + ALG_EPS) {
340 // NEW time signature takes effect
341 Alg_time_sig &sig = sigs[i++];
342 next_bar_beat = sig.beat;
343 beats_per_measure = (sig.num * 4.0) / sig.den;
344 }
345 // map beat to time
346 double t = seq->get_time_map()->beat_to_time(next_bar_beat);
347 // map time to position
348 int xx = TIME_TO_X(t + track->GetOffset());
349 if (xx > right) break;
350 AColor::Line(dc, xx, sel.y, xx, sel.y + sel.height);
351 next_bar_beat += beats_per_measure;
352 }
353 }
354
355 /* DrawNoteTrack:
356 Draws a piano-roll style display of sequence data with added
357 graphics. Since there may be notes outside of the display region,
358 reserve a half-note-height margin at the top and bottom of the
359 window and draw out-of-bounds notes here instead.
360 */
DrawNoteTrack(TrackPanelDrawingContext & context,const NoteTrack * track,const wxRect & rect,bool muted,bool selected)361 void DrawNoteTrack(TrackPanelDrawingContext &context,
362 const NoteTrack *track,
363 const wxRect & rect,
364 bool muted,
365 bool selected)
366 {
367 auto &dc = context.dc;
368 const auto artist = TrackArtist::Get( context );
369 const auto &selectedRegion = *artist->pSelectedRegion;
370 const auto &zoomInfo = *artist->pZoomInfo;
371
372 SonifyBeginNoteBackground();
373 double sel0 = selectedRegion.t0();
374 double sel1 = selectedRegion.t1();
375
376 const double h = X_TO_TIME(rect.x);
377 const double h1 = X_TO_TIME(rect.x + rect.width);
378
379 Alg_seq_ptr seq = &track->GetSeq();
380
381 if (!track->GetSelected())
382 sel0 = sel1 = 0.0;
383
384 NoteTrackDisplayData data{ track, rect };
385
386 // reserve 1/2 note height at top and bottom of track for
387 // out-of-bounds notes
388 int numPitches = (rect.height) / data.GetPitchHeight(1);
389 if (numPitches < 0) numPitches = 0; // cannot be negative
390
391 // Background comes in 4 colors, that are now themed.
392 // 214, 214,214 -- unselected white keys
393 // 192,192,192 -- black keys
394 // 170,170,170 -- bar lines
395 // 165,165,190 -- selected white keys
396
397 wxPen blackStripePen;
398 blackStripePen.SetColour(theTheme.Colour( clrMidiZebra));
399 wxBrush blackStripeBrush;
400 blackStripeBrush.SetColour(theTheme.Colour( clrMidiZebra));
401 wxPen barLinePen;
402 barLinePen.SetColour(theTheme.Colour( clrMidiLines));
403
404 const auto &blankBrush = artist->blankBrush;
405 const auto &blankPen = artist->blankPen;
406 DrawNoteBackground(context, track, rect, rect, blankBrush, blankPen,
407 blackStripeBrush, blackStripePen, barLinePen);
408
409 dc.SetClippingRegion(rect);
410
411 // Draw the selection background
412 // First, the white keys, as a single rectangle
413 // In other words fill the selection area with selectedWhiteKeyPen
414 wxRect selBG;
415 selBG.y = rect.y;
416 selBG.height = rect.height;
417 selBG.x = TIME_TO_X(sel0);
418 selBG.width = TIME_TO_X(sel1) - TIME_TO_X(sel0);
419
420 wxPen selectedWhiteKeyPen;
421 selectedWhiteKeyPen.SetColour(165, 165, 190);
422 dc.SetPen(selectedWhiteKeyPen);
423
424 wxBrush selectedWhiteKeyBrush;
425 selectedWhiteKeyBrush.SetColour(theTheme.Colour( clrSelected ));
426 // Then, the black keys and octave stripes, as smaller rectangles
427 wxPen selectedBlackKeyPen;
428 selectedBlackKeyPen.SetColour(theTheme.Colour( clrMidiZebra));
429 wxBrush selectedBlackKeyBrush;
430 selectedBlackKeyBrush.SetColour(theTheme.Colour( clrMidiZebra));
431 wxPen selectedBarLinePen;
432 selectedBarLinePen.SetColour(theTheme.Colour( clrMidiLines));
433
434 DrawNoteBackground(context, track, rect, selBG,
435 selectedWhiteKeyBrush, selectedWhiteKeyPen,
436 selectedBlackKeyBrush, selectedBlackKeyPen,
437 selectedBarLinePen);
438 SonifyEndNoteBackground();
439 SonifyBeginNoteForeground();
440 int marg = data.GetNoteMargin();
441
442 // NOTE: it would be better to put this in some global initialization
443 // function rather than do lookups every time.
444 Alg_attribute line = symbol_table.insert_string("line");
445 Alg_attribute rectangle = symbol_table.insert_string("rectangle");
446 Alg_attribute triangle = symbol_table.insert_string("triangle");
447 Alg_attribute polygon = symbol_table.insert_string("polygon");
448 Alg_attribute oval = symbol_table.insert_string("oval");
449 Alg_attribute text = symbol_table.insert_string("text");
450 Alg_attribute texts = symbol_table.insert_string("texts");
451 Alg_attribute x1r = symbol_table.insert_string("x1r");
452 Alg_attribute x2r = symbol_table.insert_string("x2r");
453 Alg_attribute y1r = symbol_table.insert_string("y1r");
454 Alg_attribute y2r = symbol_table.insert_string("y2r");
455 Alg_attribute linecolori = symbol_table.insert_string("linecolori");
456 Alg_attribute fillcolori = symbol_table.insert_string("fillcolori");
457 Alg_attribute linethicki = symbol_table.insert_string("linethicki");
458 Alg_attribute filll = symbol_table.insert_string("filll");
459 Alg_attribute fonta = symbol_table.insert_string("fonta");
460 Alg_attribute roman = symbol_table.insert_string("roman");
461 Alg_attribute swiss = symbol_table.insert_string("swiss");
462 Alg_attribute modern = symbol_table.insert_string("modern");
463 Alg_attribute weighta = symbol_table.insert_string("weighta");
464 Alg_attribute bold = symbol_table.insert_string("bold");
465 Alg_attribute sizei = symbol_table.insert_string("sizei");
466 Alg_attribute justifys = symbol_table.insert_string("justifys");
467
468 // We want to draw in seconds, so we need to convert to seconds
469 seq->convert_to_seconds();
470
471 Alg_iterator iterator(seq, false);
472 iterator.begin();
473 //for every event
474 Alg_event_ptr evt;
475 while (0 != (evt = iterator.next())) {
476 if (evt->get_type() == 'n') { // 'n' means a note
477 Alg_note_ptr note = (Alg_note_ptr) evt;
478 // if the note's channel is visible
479 if (track->IsVisibleChan(evt->chan)) {
480 double xx = note->time + track->GetOffset();
481 double x1 = xx + note->dur;
482 if (xx < h1 && x1 > h) { // omit if outside box
483 const char *shape = NULL;
484 if (note->loud > 0.0 || 0 == (shape = IsShape(note))) {
485 wxRect nr; // "note rectangle"
486 nr.y = data.PitchToY(note->pitch);
487 nr.height = data.GetPitchHeight(1);
488
489 nr.x = TIME_TO_X(xx);
490 nr.width = TIME_TO_X(x1) - nr.x;
491
492 if (nr.x + nr.width >= rect.x && nr.x < rect.x + rect.width) {
493 if (nr.x < rect.x) {
494 nr.width -= (rect.x - nr.x);
495 nr.x = rect.x;
496 }
497 if (nr.x + nr.width > rect.x + rect.width) // clip on right
498 nr.width = rect.x + rect.width - nr.x;
499
500 if (nr.y + nr.height < rect.y + marg + 3) {
501 // too high for window
502 nr.y = rect.y;
503 nr.height = marg;
504 dc.SetBrush(*wxBLACK_BRUSH);
505 dc.SetPen(*wxBLACK_PEN);
506 dc.DrawRectangle(nr);
507 } else if (nr.y >= rect.y + rect.height - marg - 1) {
508 // too low for window
509 nr.y = rect.y + rect.height - marg;
510 nr.height = marg;
511 dc.SetBrush(*wxBLACK_BRUSH);
512 dc.SetPen(*wxBLACK_PEN);
513 dc.DrawRectangle(nr);
514 } else {
515 if (nr.y + nr.height > rect.y + rect.height - marg)
516 nr.height = rect.y + rect.height - nr.y;
517 if (nr.y < rect.y + marg) {
518 int offset = rect.y + marg - nr.y;
519 nr.height -= offset;
520 nr.y += offset;
521 }
522 // nr.y += rect.y;
523 if (muted)
524 AColor::LightMIDIChannel(&dc, note->chan + 1);
525 else
526 AColor::MIDIChannel(&dc, note->chan + 1);
527 dc.DrawRectangle(nr);
528 if (data.GetPitchHeight(1) > 2) {
529 AColor::LightMIDIChannel(&dc, note->chan + 1);
530 AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y);
531 AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2);
532 AColor::DarkMIDIChannel(&dc, note->chan + 1);
533 AColor::Line(dc, nr.x+nr.width-1, nr.y,
534 nr.x+nr.width-1, nr.y+nr.height-1);
535 AColor::Line(dc, nr.x, nr.y+nr.height-1,
536 nr.x+nr.width-1, nr.y+nr.height-1);
537 }
538 // }
539 }
540 }
541 } else if (shape) {
542 // draw a shape according to attributes
543 // add 0.5 to pitch because pitches are plotted with
544 // height = PITCH_HEIGHT; thus, the center is raised
545 // by PITCH_HEIGHT * 0.5
546 int yy = data.PitchToY(note->pitch);
547 long linecolor = LookupIntAttribute(note, linecolori, -1);
548 long linethick = LookupIntAttribute(note, linethicki, 1);
549 long fillcolor = -1;
550 long fillflag = 0;
551
552 // set default color to be that of channel
553 AColor::MIDIChannel(&dc, note->chan+1);
554 if (shape != text) {
555 if (linecolor != -1)
556 dc.SetPen(wxPen(wxColour(RED(linecolor),
557 GREEN(linecolor),
558 BLUE(linecolor)),
559 linethick, wxPENSTYLE_SOLID));
560 }
561 if (shape != line) {
562 fillcolor = LookupIntAttribute(note, fillcolori, -1);
563 fillflag = LookupLogicalAttribute(note, filll, false);
564
565 if (fillcolor != -1)
566 dc.SetBrush(wxBrush(wxColour(RED(fillcolor),
567 GREEN(fillcolor),
568 BLUE(fillcolor)),
569 wxBRUSHSTYLE_SOLID));
570 if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH);
571 }
572 int y1 = data.PitchToY(LookupRealAttribute(note, y1r, note->pitch));
573 if (shape == line) {
574 // extreme zooms caues problems under windows, so we have to do some
575 // clipping before calling display routine
576 if (xx < h) { // clip line on left
577 yy = (int)((yy + (y1 - yy) * (h - xx) / (x1 - xx)) + 0.5);
578 xx = h;
579 }
580 if (x1 > h1) { // clip line on right
581 y1 = (int)((yy + (y1 - yy) * (h1 - xx) / (x1 - xx)) + 0.5);
582 x1 = h1;
583 }
584 AColor::Line(dc, TIME_TO_X(xx), yy, TIME_TO_X(x1), y1);
585 } else if (shape == rectangle) {
586 if (xx < h) { // clip on left, leave 10 pixels to spare
587 xx = X_TO_TIME(rect.x - (linethick + 10));
588 }
589 if (x1 > h1) { // clip on right, leave 10 pixels to spare
590 xx = X_TO_TIME(rect.x + rect.width + linethick + 10);
591 }
592 dc.DrawRectangle(TIME_TO_X(xx), yy, TIME_TO_X(x1) - TIME_TO_X(xx), y1 - yy + 1);
593 } else if (shape == triangle) {
594 wxPoint points[3];
595 points[0].x = TIME_TO_X(xx);
596 CLIP(points[0].x);
597 points[0].y = yy;
598 points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch));
599 CLIP(points[1].x);
600 points[1].y = y1;
601 points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
602 CLIP(points[2].x);
603 points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
604 dc.DrawPolygon(3, points);
605 } else if (shape == polygon) {
606 wxPoint points[20]; // upper bound of 20 sides
607 points[0].x = TIME_TO_X(xx);
608 CLIP(points[0].x);
609 points[0].y = yy;
610 points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, xx));
611 CLIP(points[1].x);
612 points[1].y = y1;
613 points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, xx));
614 CLIP(points[2].x);
615 points[2].y = data.PitchToY(LookupRealAttribute(note, y2r, note->pitch));
616 int n = 3;
617 while (n < 20) {
618 char name[8];
619 sprintf(name, "x%dr", n);
620 Alg_attribute attr = symbol_table.insert_string(name);
621 double xn = LookupRealAttribute(note, attr, -1000000.0);
622 if (xn == -1000000.0) break;
623 points[n].x = TIME_TO_X(xn);
624 CLIP(points[n].x);
625 sprintf(name, "y%dr", n - 1);
626 attr = symbol_table.insert_string(name);
627 double yn = LookupRealAttribute(note, attr, -1000000.0);
628 if (yn == -1000000.0) break;
629 points[n].y = data.PitchToY(yn);
630 n++;
631 }
632 dc.DrawPolygon(n, points);
633 } else if (shape == oval) {
634 int ix = TIME_TO_X(xx);
635 CLIP(ix);
636 int ix1 = TIME_TO_X(x1) - TIME_TO_X(xx);
637 if (ix1 > CLIP_MAX * 2) ix1 = CLIP_MAX * 2; // CLIP a width
638 dc.DrawEllipse(ix, yy, ix1, y1 - yy + 1);
639 } else if (shape == text) {
640 if (linecolor != -1)
641 dc.SetTextForeground(wxColour(RED(linecolor),
642 GREEN(linecolor),
643 BLUE(linecolor)));
644 // if no color specified, copy color from brush
645 else dc.SetTextForeground(dc.GetBrush().GetColour());
646
647 // This seems to have no effect, so I commented it out. -RBD
648 //if (fillcolor != -1)
649 // dc.SetTextBackground(wxColour(RED(fillcolor),
650 // GREEN(fillcolor),
651 // BLUE(fillcolor)));
652 //// if no color specified, copy color from brush
653 //else dc.SetTextBackground(dc.GetPen().GetColour());
654
655 const char *font = LookupAtomAttribute(note, fonta, NULL);
656 const char *weight = LookupAtomAttribute(note, weighta, NULL);
657 int size = LookupIntAttribute(note, sizei, 8);
658 const char *justify = LookupStringAttribute(note, justifys, "ld");
659 wxFont wxfont;
660 wxfont.SetFamily(font == roman ? wxFONTFAMILY_ROMAN :
661 (font == swiss ? wxFONTFAMILY_SWISS :
662 (font == modern ? wxFONTFAMILY_MODERN : wxFONTFAMILY_DEFAULT)));
663 wxfont.SetStyle(wxFONTSTYLE_NORMAL);
664 wxfont.SetWeight(weight == bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL);
665 wxfont.SetPointSize(size);
666 dc.SetFont(wxfont);
667
668 // now do justification
669 const char *s = LookupStringAttribute(note, texts, "");
670 wxCoord textWidth, textHeight;
671 dc.GetTextExtent(wxString::FromUTF8(s), &textWidth, &textHeight);
672 long hoffset = 0;
673 long voffset = -textHeight; // default should be baseline of text
674
675 if (strlen(justify) != 2) justify = "ld";
676
677 if (justify[0] == 'c') hoffset = -(textWidth/2);
678 else if (justify[0] == 'r') hoffset = -textWidth;
679
680 if (justify[1] == 't') voffset = 0;
681 else if (justify[1] == 'c') voffset = -(textHeight/2);
682 else if (justify[1] == 'b') voffset = -textHeight;
683 if (fillflag) {
684 // It should be possible to do this with background color,
685 // but maybe because of the transfer mode, no background is
686 // drawn. To fix this, just draw a rectangle:
687 dc.SetPen(wxPen(wxColour(RED(fillcolor),
688 GREEN(fillcolor),
689 BLUE(fillcolor)),
690 1, wxPENSTYLE_SOLID));
691 dc.DrawRectangle(TIME_TO_X(xx) + hoffset, yy + voffset,
692 textWidth, textHeight);
693 }
694 dc.DrawText(LAT1CTOWX(s), TIME_TO_X(xx) + hoffset, yy + voffset);
695 }
696 }
697 }
698 }
699 }
700 }
701 iterator.end();
702 // draw black line between top/bottom margins and the track
703 dc.SetPen(*wxBLACK_PEN);
704 AColor::Line(dc, rect.x, rect.y + marg, rect.x + rect.width, rect.y + marg);
705 AColor::Line(dc, rect.x, rect.y + rect.height - marg - 1, // subtract 1 to get
706 rect.x + rect.width, rect.y + rect.height - marg - 1); // top of line
707
708 if (h == 0.0 && track->GetOffset() < 0.0) {
709 TrackArt::DrawNegativeOffsetTrackArrows( context, rect );
710 }
711
712 //draw clip edges
713 {
714 int left = TIME_TO_X(track->GetOffset());
715 int right = TIME_TO_X(track->GetOffset() + track->GetSeq().get_real_dur());
716
717 TrackArt::DrawClipEdges(dc, wxRect(left, rect.GetTop(), right - left + 1, rect.GetHeight()), selected);
718 }
719
720 dc.DestroyClippingRegion();
721 SonifyEndNoteForeground();
722 }
723
724 }
725
Draw(TrackPanelDrawingContext & context,const wxRect & rect,unsigned iPass)726 void NoteTrackView::Draw(
727 TrackPanelDrawingContext &context,
728 const wxRect &rect, unsigned iPass )
729 {
730 if ( iPass == TrackArtist::PassTracks ) {
731 const auto nt = std::static_pointer_cast<const NoteTrack>(
732 FindTrack()->SubstitutePendingChangedTrack());
733 bool muted = false;
734 #ifdef EXPERIMENTAL_MIDI_OUT
735 const auto artist = TrackArtist::Get( context );
736 const auto hasSolo = artist->hasSolo;
737 muted = (hasSolo || nt->GetMute()) && !nt->GetSolo();
738 #endif
739
740 #ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
741 TrackArt::DrawBackgroundWithSelection(context, rect, nt.get(), AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
742 #endif
743 bool selected{ false };
744 if (auto affordance = std::dynamic_pointer_cast<NoteTrackAffordanceControls>(GetAffordanceControls()))
745 {
746 selected = affordance->IsSelected();
747 }
748
749 DrawNoteTrack(context, nt.get(), rect, muted, selected);
750 }
751 CommonTrackView::Draw( context, rect, iPass );
752 }
753 #endif
754