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