endless further tweaks to step editor; stop stealing the "r" key inside MidiRegionVie...
[ardour.git] / gtk2_ardour / midi_region_view.cc
1 /*
2     Copyright (C) 2001-2007 Paul Davis
3     Author: Dave Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <cmath>
21 #include <cassert>
22 #include <algorithm>
23 #include <ostream>
24
25 #include <gtkmm.h>
26
27 #include <gtkmm2ext/gtk_ui.h>
28
29 #include <sigc++/signal.h>
30
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
33
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
41
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
46
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas-program-change.h"
52 #include "ghostregion.h"
53 #include "gui_thread.h"
54 #include "keyboard.h"
55 #include "midi_cut_buffer.h"
56 #include "midi_list_editor.h"
57 #include "midi_region_view.h"
58 #include "midi_streamview.h"
59 #include "midi_time_axis.h"
60 #include "midi_time_axis.h"
61 #include "midi_util.h"
62 #include "public_editor.h"
63 #include "selection.h"
64 #include "simpleline.h"
65 #include "streamview.h"
66 #include "utils.h"
67
68 #include "i18n.h"
69
70 using namespace ARDOUR;
71 using namespace PBD;
72 using namespace Editing;
73 using namespace ArdourCanvas;
74 using Gtkmm2ext::Keyboard;
75
76 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
77                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
78         : RegionView (parent, tv, r, spu, basic_color)
79         , _force_channel(-1)
80         , _last_channel_selection(0xFFFF)
81         , _current_range_min(0)
82         , _current_range_max(0)
83         , _model_name(string())
84         , _custom_device_mode(string())
85         , _active_notes(0)
86         , _note_group(new ArdourCanvas::Group(*parent))
87         , _diff_command(0)
88         , _ghost_note(0)
89         , _drag_rect (0)
90         , _mouse_state(None)
91         , _pressed_button(0)
92         , _sort_needed (true)
93         , _optimization_iterator (_events.end())
94         , _list_editor (0)
95         , no_sound_notes (false)
96 {
97         _note_group->raise_to_top();
98         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
99 }
100
101 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
102                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
103                 TimeAxisViewItem::Visibility visibility)
104         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
105         , _force_channel(-1)
106         , _last_channel_selection(0xFFFF)
107         , _model_name(string())
108         , _custom_device_mode(string())
109         , _active_notes(0)
110         , _note_group(new ArdourCanvas::Group(*parent))
111         , _diff_command(0)
112         , _ghost_note(0)
113         , _drag_rect (0)
114         , _mouse_state(None)
115         , _pressed_button(0)
116         , _sort_needed (true)
117         , _optimization_iterator (_events.end())
118         , _list_editor (0)
119         , no_sound_notes (false)
120 {
121         _note_group->raise_to_top();
122         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
123 }
124
125 MidiRegionView::MidiRegionView (const MidiRegionView& other)
126         : sigc::trackable(other)
127         , RegionView (other)
128         , _force_channel(-1)
129         , _last_channel_selection(0xFFFF)
130         , _model_name(string())
131         , _custom_device_mode(string())
132         , _active_notes(0)
133         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
134         , _diff_command(0)
135         , _ghost_note(0)
136         , _drag_rect (0)
137         , _mouse_state(None)
138         , _pressed_button(0)
139         , _sort_needed (true)
140         , _optimization_iterator (_events.end())
141         , _list_editor (0)
142         , no_sound_notes (false)
143 {
144         Gdk::Color c;
145         int r,g,b,a;
146
147         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
148         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
149
150         init (c, false);
151 }
152
153 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
154         : RegionView (other, boost::shared_ptr<Region> (region))
155         , _force_channel(-1)
156         , _last_channel_selection(0xFFFF)
157         , _model_name(string())
158         , _custom_device_mode(string())
159         , _active_notes(0)
160         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
161         , _diff_command(0)
162         , _ghost_note(0)
163         , _drag_rect (0)
164         , _mouse_state(None)
165         , _pressed_button(0)
166         , _sort_needed (true)
167         , _optimization_iterator (_events.end())
168         , _list_editor (0)
169         , no_sound_notes (false)
170 {
171         Gdk::Color c;
172         int r,g,b,a;
173
174         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
175         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
176
177         init (c, true);
178 }
179
180 void
181 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
182 {
183         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184
185         CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR, 
186                                                          ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
187                                                          gui_context());
188
189         if (wfd) {
190                 midi_region()->midi_source(0)->load_model();
191         }
192
193         _model = midi_region()->midi_source(0)->model();
194         _enable_display = false;
195
196         RegionView::init (basic_color, false);
197
198         compute_colors (basic_color);
199
200         set_height (trackview.current_height());
201
202         region_muted ();
203         region_sync_changed ();
204         region_resized (ARDOUR::bounds_change);
205         region_locked ();
206
207         reset_width_dependent_items (_pixel_width);
208
209         set_colors ();
210
211         _enable_display = true;
212         if (_model) {
213                 if (wfd) {
214                         display_model (_model);
215                 }
216         }
217
218         group->raise_to_top();
219         group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
220
221         midi_view()->signal_channel_mode_changed().connect(
222                         sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
223
224         midi_view()->signal_midi_patch_settings_changed().connect(
225                         sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
226
227         trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
228 }
229
230 bool
231 MidiRegionView::canvas_event(GdkEvent* ev)
232 {
233         if (!trackview.editor().internal_editing()) {
234                 return false;
235         }
236
237         /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
238            to its items, which means that ev->type == GDK_SCROLL will never be seen
239         */
240
241         switch (ev->type) {
242         case GDK_SCROLL:
243                 return scroll (&ev->scroll);
244
245         case GDK_KEY_PRESS:
246                 return key_press (&ev->key);
247
248         case GDK_KEY_RELEASE:
249                 return key_release (&ev->key);
250
251         case GDK_BUTTON_PRESS:
252                 return button_press (&ev->button);
253
254         case GDK_2BUTTON_PRESS:
255                 return true;
256
257         case GDK_BUTTON_RELEASE:
258                 return button_release (&ev->button);
259                 
260         case GDK_ENTER_NOTIFY:
261                 return enter_notify (&ev->crossing);
262
263         case GDK_LEAVE_NOTIFY:
264                 return leave_notify (&ev->crossing);
265
266         case GDK_MOTION_NOTIFY:
267                 return motion (&ev->motion);
268
269         default: 
270                 break;
271         }
272
273         return false;
274 }
275
276 bool
277 MidiRegionView::enter_notify (GdkEventCrossing* ev)
278 {
279         /* FIXME: do this on switch to note tool, too, if the pointer is already in */
280
281         Keyboard::magic_widget_grab_focus();
282         group->grab_focus();
283         
284         if (trackview.editor().current_mouse_mode() == MouseRange) {
285                 create_ghost_note (ev->x, ev->y);
286         }
287
288         return false;
289 }
290
291 bool
292 MidiRegionView::leave_notify (GdkEventCrossing* ev)
293 {
294         trackview.editor().hide_verbose_canvas_cursor ();
295         delete _ghost_note;
296         _ghost_note = 0;
297         return false;
298 }
299
300 bool
301 MidiRegionView::button_press (GdkEventButton* ev)
302 {
303         _last_x = ev->x;
304         _last_y = ev->y;
305         group->w2i (_last_x, _last_y);
306         
307         if (_mouse_state != SelectTouchDragging && ev->button == 1) {
308                 _pressed_button = ev->button;
309                 _mouse_state = Pressed;
310                 return true;
311         }
312         
313         _pressed_button = ev->button;
314
315         return true;
316 }
317
318 bool
319 MidiRegionView::button_release (GdkEventButton* ev)
320 {
321         double event_x, event_y;
322         nframes64_t event_frame = 0;
323
324         event_x = ev->x;
325         event_y = ev->y;
326         group->w2i(event_x, event_y);
327         group->ungrab(ev->time);
328         event_frame = trackview.editor().pixel_to_frame(event_x);
329
330         if (ev->button == 3) {
331                 return false;
332         } else if (_pressed_button != 1) {
333                 return false;
334         }
335
336         switch (_mouse_state) {
337         case Pressed: // Clicked
338                 switch (trackview.editor().current_mouse_mode()) {
339                 case MouseObject:
340                 case MouseTimeFX:
341                         clear_selection();
342                         maybe_select_by_position (ev, event_x, event_y);
343                         break;
344
345                 case MouseRange:
346                 {
347                         bool success;
348                         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
349                         if (!success) {
350                                 beats = 1;
351                         }
352                         create_note_at (event_x, event_y, beats, true);
353                         break;
354                 }
355                 default:
356                         break;
357                 }
358                 _mouse_state = None;
359                 break;
360         case SelectRectDragging: // Select drag done
361                 _mouse_state = None;
362                 delete _drag_rect;
363                 _drag_rect = 0;
364                 break;
365
366         case AddDragging: // Add drag done
367                 _mouse_state = None;
368                 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
369                         const double x      = _drag_rect->property_x1();
370                         const double length = trackview.editor().pixel_to_frame 
371                                 (_drag_rect->property_x2() - _drag_rect->property_x1());
372
373                         create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), false);
374                 }
375
376                 delete _drag_rect;
377                 _drag_rect = 0;
378
379                 create_ghost_note (ev->x, ev->y);
380
381         default:
382                 break;
383         }
384
385         return false;
386 }
387
388 bool
389 MidiRegionView::motion (GdkEventMotion* ev)
390 {
391         double event_x, event_y;
392         nframes64_t event_frame = 0;
393
394         event_x = ev->x;
395         event_y = ev->y;
396         group->w2i(event_x, event_y);
397
398         // convert event_x to global frame
399         event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
400         trackview.editor().snap_to(event_frame);
401         // convert event_frame back to local coordinates relative to position
402         event_frame -= _region->position();
403
404         if (_ghost_note) {
405                 update_ghost_note (ev->x, ev->y);
406         }
407
408         /* any motion immediately hides velocity text that may have been visible */
409                 
410         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
411                 (*i)->hide_velocity ();
412         }
413
414         switch (_mouse_state) {
415         case Pressed: // Maybe start a drag, if we've moved a bit
416
417                 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
418                         /* no appreciable movement since the button was pressed */
419                         return false;
420                 }
421
422                 // Select drag start
423                 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
424                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
425                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
426                         _last_x = event_x;
427                         _last_y = event_y;
428                         _drag_start_x = event_x;
429                         _drag_start_y = event_y;
430
431                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
432                         _drag_rect->property_x1() = event_x;
433                         _drag_rect->property_y1() = event_y;
434                         _drag_rect->property_x2() = event_x;
435                         _drag_rect->property_y2() = event_y;
436                         _drag_rect->property_outline_what() = 0xFF;
437                         _drag_rect->property_outline_color_rgba()
438                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
439                         _drag_rect->property_fill_color_rgba()
440                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
441
442                         _mouse_state = SelectRectDragging;
443                         return true;
444
445                         // Add note drag start
446                 } else if (trackview.editor().internal_editing()) {
447
448                         delete _ghost_note;
449                         _ghost_note = 0;
450                                 
451                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
452                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
453                         _last_x = event_x;
454                         _last_y = event_y;
455                         _drag_start_x = event_x;
456                         _drag_start_y = event_y;
457
458                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
459                         _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
460
461                         _drag_rect->property_y1() = midi_stream_view()->note_to_y(
462                                 midi_stream_view()->y_to_note(event_y));
463                         _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
464                         _drag_rect->property_y2() = _drag_rect->property_y1()
465                                 + floor(midi_stream_view()->note_height());
466                         _drag_rect->property_outline_what() = 0xFF;
467                         _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
468                         _drag_rect->property_fill_color_rgba()    = 0xFFFFFF66;
469
470                         _mouse_state = AddDragging;
471                         return true;
472                 }
473
474                 return false;
475
476         case SelectRectDragging: // Select drag motion
477         case AddDragging: // Add note drag motion
478                 if (ev->is_hint) {
479                         int t_x;
480                         int t_y;
481                         GdkModifierType state;
482                         gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
483                         event_x = t_x;
484                         event_y = t_y;
485                 }
486
487                 if (_mouse_state == AddDragging)
488                         event_x = trackview.editor().frame_to_pixel(event_frame);
489
490                 if (_drag_rect) {
491                         if (event_x > _drag_start_x)
492                                 _drag_rect->property_x2() = event_x;
493                         else
494                                 _drag_rect->property_x1() = event_x;
495                 }
496
497                 if (_drag_rect && _mouse_state == SelectRectDragging) {
498                         if (event_y > _drag_start_y)
499                                 _drag_rect->property_y2() = event_y;
500                         else
501                                 _drag_rect->property_y1() = event_y;
502
503                         update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
504                 }
505
506                 _last_x = event_x;
507                 _last_y = event_y;
508
509         case SelectTouchDragging:
510                 return false;
511
512         default:
513                 break;
514         }
515
516         return false;
517 }
518
519
520 bool
521 MidiRegionView::scroll (GdkEventScroll* ev)
522 {
523         if (_selection.empty()) {
524                 return false;
525         }
526
527         trackview.editor().hide_verbose_canvas_cursor ();
528
529         bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
530         
531         if (ev->direction == GDK_SCROLL_UP) {
532                 change_velocities (true, fine, false);
533         } else if (ev->direction == GDK_SCROLL_DOWN) {
534                 change_velocities (false, fine, false);
535         } 
536         return true;
537 }
538
539 bool
540 MidiRegionView::key_press (GdkEventKey* ev)
541
542         /* since GTK bindings are generally activated on press, and since
543            detectable auto-repeat is the name of the game and only sends
544            repeated presses, carry out key actions at key press, not release.
545         */
546         
547         if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
548                 _mouse_state = SelectTouchDragging;
549                 return true;
550                 
551         } else if (ev->keyval == GDK_Escape) {
552                 clear_selection();
553                 _mouse_state = None;
554                 
555         } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
556                 
557                 bool start = (ev->keyval == GDK_comma);
558                 bool end = (ev->keyval == GDK_period);
559                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
560                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
561                 
562                 change_note_lengths (fine, shorter, start, end);
563                 
564                 return true;
565                 
566         } else if (ev->keyval == GDK_Delete) {
567                 
568                 delete_selection();
569                 return true;
570                 
571         } else if (ev->keyval == GDK_Tab) {
572                 
573                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
574                         goto_previous_note ();
575                 } else {
576                         goto_next_note ();
577                 }
578                 return true;
579                 
580         } else if (ev->keyval == GDK_Up) {
581                 
582                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
583                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
584                 
585                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
586                         change_velocities (true, fine, allow_smush);
587                 } else {
588                         transpose (true, fine, allow_smush);
589                 }
590                 return true;
591                 
592         } else if (ev->keyval == GDK_Down) {
593                 
594                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
595                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
596                 
597                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
598                         change_velocities (false, fine, allow_smush);
599                 } else {
600                         transpose (false, fine, allow_smush);
601                 }
602                 return true;
603                 
604         } else if (ev->keyval == GDK_Left) {
605                 
606                 nudge_notes (false);
607                 return true;
608                 
609         } else if (ev->keyval == GDK_Right) {
610                 
611                 nudge_notes (true);
612                 return true;
613                 
614         } else if (ev->keyval == GDK_Control_L) {
615                 return true;
616
617         }
618         
619         return false;
620 }
621
622 bool
623 MidiRegionView::key_release (GdkEventKey* ev)
624 {
625         if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
626                 _mouse_state = None;
627                 return true;
628         }
629         return false;
630 }
631
632 void
633 MidiRegionView::show_list_editor ()
634 {
635         if (!_list_editor) {
636                 _list_editor = new MidiListEditor (trackview.session(), midi_region());
637         }
638         _list_editor->present ();
639 }
640
641 /** Add a note to the model, and the view, at a canvas (click) coordinate.
642  * \param x horizontal position in pixels
643  * \param y vertical position in pixels
644  * \param length duration of the note in beats, which will be snapped to the grid
645  * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
646  */
647 void
648 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
649 {
650         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
651         MidiStreamView* const view = mtv->midi_view();
652
653         double note = midi_stream_view()->y_to_note(y);
654
655         assert(note >= 0.0);
656         assert(note <= 127.0);
657
658         // Start of note in frames relative to region start
659         nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
660         assert(start_frames >= 0);
661
662         // Snap length
663         length = frames_to_beats(
664                         snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
665
666         assert (length != 0);
667
668         if (sh) {
669                 length = frames_to_beats (beats_to_frames (length) - 1);
670         }
671
672         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
673         int chn_cnt = 0;
674         uint8_t channel = 0;
675
676         /* pick the highest selected channel, unless all channels are selected,
677            which is interpreted to mean channel 1 (zero)
678         */
679
680         for (uint16_t i = 0; i < 16; ++i) {
681                 if (chn_mask & (1<<i)) {
682                         channel = i;
683                         chn_cnt++;
684                 }
685         }
686
687         if (chn_cnt == 16) {
688                 channel = 0;
689         }
690
691         const boost::shared_ptr<NoteType> new_note (new NoteType (channel,
692                                                                   frames_to_beats(start_frames + _region->start()), length,
693                                                                   (uint8_t)note, 0x40));
694
695         if (_model->contains (new_note)) {
696                 return;
697         }
698
699         view->update_note_range(new_note->note());
700
701         MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
702         cmd->add(new_note);
703         _model->apply_command(*trackview.session(), cmd);
704
705         play_midi_note (new_note);
706 }
707
708 void
709 MidiRegionView::clear_events()
710 {
711         clear_selection();
712
713         MidiGhostRegion* gr;
714         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
715                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
716                         gr->clear_events();
717                 }
718         }
719
720         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
721                 delete *i;
722         }
723
724         _events.clear();
725         _pgm_changes.clear();
726         _sys_exes.clear();
727         _optimization_iterator = _events.end();
728 }
729
730
731 void
732 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
733 {
734         _model = model;
735         content_connection.disconnect ();
736         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
737
738         clear_events ();
739
740         if (_enable_display) {
741                 redisplay_model();
742         }
743 }
744
745 void
746 MidiRegionView::start_diff_command(string name)
747 {
748         if (!_diff_command) {
749                 _diff_command = _model->new_diff_command(name);
750         }
751 }
752
753 void
754 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
755 {
756         if (_diff_command) {
757                 _diff_command->add(note);
758         }
759         if (selected) {
760                 _marked_for_selection.insert(note);
761         }
762         if (show_velocity) {
763                 _marked_for_velocity.insert(note);
764         }
765 }
766
767 void
768 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
769 {
770         if (_diff_command && ev->note()) {
771                 _diff_command->remove(ev->note());
772         }
773 }
774
775 void
776 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
777                                  MidiModel::DiffCommand::Property property,
778                                  uint8_t val)
779 {
780         if (_diff_command) {
781                 _diff_command->change (ev->note(), property, val);
782         }
783 }
784
785 void
786 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
787                                  MidiModel::DiffCommand::Property property,
788                                  Evoral::MusicalTime val)
789 {
790         if (_diff_command) {
791                 _diff_command->change (ev->note(), property, val);
792         }
793 }
794
795 void
796 MidiRegionView::apply_diff ()
797 {
798         bool add_or_remove;
799
800         if (!_diff_command) {
801                 return;
802         }
803
804         if ((add_or_remove = _diff_command->adds_or_removes())) {
805                 // Mark all selected notes for selection when model reloads
806                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
807                         _marked_for_selection.insert((*i)->note());
808                 }
809         }
810
811         _model->apply_command(*trackview.session(), _diff_command);
812         _diff_command = 0;
813         midi_view()->midi_track()->playlist_modified();
814         
815         if (add_or_remove) {
816                 _marked_for_selection.clear();
817         }
818
819         _marked_for_velocity.clear();
820 }
821
822 void
823 MidiRegionView::apply_diff_as_subcommand()
824 {
825         bool add_or_remove;
826
827         if (!_diff_command) {
828                 return;
829         }
830
831         if ((add_or_remove = _diff_command->adds_or_removes())) {
832                 // Mark all selected notes for selection when model reloads
833                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
834                         _marked_for_selection.insert((*i)->note());
835                 }
836         }
837
838         _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
839         _diff_command = 0;
840         midi_view()->midi_track()->playlist_modified();
841
842         if (add_or_remove) {
843                 _marked_for_selection.clear();
844         }
845         _marked_for_velocity.clear();
846 }
847
848
849 void
850 MidiRegionView::abort_command()
851 {
852         delete _diff_command;
853         _diff_command = 0;
854         clear_selection();
855 }
856
857 CanvasNoteEvent*
858 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
859 {
860         if (_optimization_iterator != _events.end()) {
861                 ++_optimization_iterator;
862         }
863
864         if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
865                 return *_optimization_iterator;
866         }
867
868         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
869                 if ((*_optimization_iterator)->note() == note) {
870                         return *_optimization_iterator;
871                 }
872         }
873
874         return 0;
875 }
876
877 void
878 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
879 {
880         MidiModel::Notes notes;
881         _model->get_notes (notes, op, val, chan_mask);
882
883         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
884                 CanvasNoteEvent* cne = find_canvas_note (*n);
885                 if (cne) {
886                         e.push_back (cne);
887                 }
888         }
889 }
890
891 void
892 MidiRegionView::redisplay_model()
893 {
894         // Don't redisplay the model if we're currently recording and displaying that
895         if (_active_notes) {
896                 return;
897         }
898
899         if (!_model) {
900                 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
901                 return;
902         }
903
904         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
905                 (*i)->invalidate ();
906         }
907
908         MidiModel::ReadLock lock(_model->read_lock());
909
910         MidiModel::Notes& notes (_model->notes());
911         _optimization_iterator = _events.begin();
912
913         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
914
915                 boost::shared_ptr<NoteType> note (*n);
916                 CanvasNoteEvent* cne;
917                 bool visible;
918
919                 if (note_in_region_range (note, visible)) {
920
921                         if ((cne = find_canvas_note (note)) != 0) {
922
923                                 cne->validate ();
924
925                                 CanvasNote* cn;
926                                 CanvasHit* ch;
927
928                                 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
929                                         update_note (cn);
930                                 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
931                                         update_hit (ch);
932                                 }
933
934                                 if (visible) {
935                                         cne->show ();
936                                 } else {
937                                         cne->hide ();
938                                 }
939
940                         } else {
941
942                                 add_note (note, visible);
943                         }
944
945                 } else {
946
947                         if ((cne = find_canvas_note (note)) != 0) {
948                                 cne->validate ();
949                                 cne->hide ();
950                         }
951                 }
952         }
953
954
955         /* remove note items that are no longer valid */
956
957         for (Events::iterator i = _events.begin(); i != _events.end(); ) {
958                 if (!(*i)->valid ()) {
959                         delete *i;
960                         i = _events.erase (i);
961                 } else {
962                         ++i;
963                 }
964         }
965
966         _pgm_changes.clear();
967         _sys_exes.clear();
968         
969         display_sysexes();
970         display_program_changes();
971
972         _marked_for_selection.clear ();
973         _marked_for_velocity.clear ();
974
975         /* we may have caused _events to contain things out of order (e.g. if a note
976            moved earlier or later). we don't generally need them in time order, but
977            make a note that a sort is required for those cases that require it.
978         */
979
980         _sort_needed = true;
981 }
982
983 void
984 MidiRegionView::display_program_changes()
985 {
986         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
987         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
988
989         for (uint8_t i = 0; i < 16; ++i) {
990                 if (chn_mask & (1<<i)) {
991                         display_program_changes_on_channel (i);
992                 }
993         }
994 }
995
996 void
997 MidiRegionView::display_program_changes_on_channel(uint8_t channel)
998 {
999         boost::shared_ptr<Evoral::Control> control = 
1000                 _model->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation, channel));
1001
1002         if (!control) {
1003                 return;
1004         }
1005
1006         Glib::Mutex::Lock lock (control->list()->lock());
1007
1008         for (AutomationList::const_iterator event = control->list()->begin();
1009                         event != control->list()->end(); ++event) {
1010                 double event_time     = (*event)->when;
1011                 double program_number = floor((*event)->value + 0.5);
1012
1013                 // Get current value of bank select MSB at time of the program change
1014                 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1015                 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1016                 uint8_t msb = 0;
1017                 if (msb_control != 0) {
1018                         msb = uint8_t(floor(msb_control->get_double(true, event_time) + 0.5));
1019                 }
1020
1021                 // Get current value of bank select LSB at time of the program change
1022                 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1023                 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1024                 uint8_t lsb = 0;
1025                 if (lsb_control != 0) {
1026                         lsb = uint8_t(floor(lsb_control->get_double(true, event_time) + 0.5));
1027                 }
1028
1029                 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1030
1031                 boost::shared_ptr<MIDI::Name::Patch> patch =
1032                         MIDI::Name::MidiPatchManager::instance().find_patch(
1033                                         _model_name, _custom_device_mode, channel, patch_key);
1034
1035                 PCEvent program_change(event_time, uint8_t(program_number), channel);
1036
1037                 if (patch != 0) {
1038                         add_pgm_change(program_change, patch->name());
1039                 } else {
1040                         char buf[4];
1041                         // program_number is zero-based: convert to one-based
1042                         snprintf(buf, 4, "%d", int(program_number+1));
1043                         add_pgm_change(program_change, buf);
1044                 }
1045         }
1046 }
1047
1048 void
1049 MidiRegionView::display_sysexes()
1050 {
1051         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1052                 Evoral::MusicalTime time = (*i)->time();
1053                 assert(time >= 0);
1054
1055                 ostringstream str;
1056                 str << hex;
1057                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1058                         str << int((*i)->buffer()[b]);
1059                         if (b != (*i)->size() -1) {
1060                                 str << " ";
1061                         }
1062                 }
1063                 string text = str.str();
1064
1065                 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1066
1067                 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1068
1069                 double height = midi_stream_view()->contents_height();
1070
1071                 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1072                                 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1073
1074                 // Show unless program change is beyond the region bounds
1075                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1076                         sysex->hide();
1077                 } else {
1078                         sysex->show();
1079                 }
1080
1081                 _sys_exes.push_back(sysex);
1082         }
1083 }
1084
1085
1086 MidiRegionView::~MidiRegionView ()
1087 {
1088         in_destructor = true;
1089
1090         trackview.editor().hide_verbose_canvas_cursor ();
1091
1092         note_delete_connection.disconnect ();
1093
1094         delete _list_editor;
1095
1096         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1097
1098         if (_active_notes) {
1099                 end_write();
1100         }
1101
1102         _selection.clear();
1103         clear_events();
1104         delete _note_group;
1105         delete _diff_command;
1106 }
1107
1108 void
1109 MidiRegionView::region_resized (const PropertyChange& what_changed)
1110 {
1111         RegionView::region_resized(what_changed);
1112
1113         if (what_changed.contains (ARDOUR::Properties::position)) {
1114                 set_duration(_region->length(), 0);
1115                 if (_enable_display) {
1116                         redisplay_model();
1117                 }
1118         }
1119 }
1120
1121 void
1122 MidiRegionView::reset_width_dependent_items (double pixel_width)
1123 {
1124         RegionView::reset_width_dependent_items(pixel_width);
1125         assert(_pixel_width == pixel_width);
1126
1127         if (_enable_display) {
1128                 redisplay_model();
1129         }
1130 }
1131
1132 void
1133 MidiRegionView::set_height (double height)
1134 {
1135         static const double FUDGE = 2.0;
1136         const double old_height = _height;
1137         RegionView::set_height(height);
1138         _height = height - FUDGE;
1139
1140         apply_note_range(midi_stream_view()->lowest_note(),
1141                          midi_stream_view()->highest_note(),
1142                          height != old_height + FUDGE);
1143
1144         if (name_pixbuf) {
1145                 name_pixbuf->raise_to_top();
1146         }
1147 }
1148
1149
1150 /** Apply the current note range from the stream view
1151  * by repositioning/hiding notes as necessary
1152  */
1153 void
1154 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1155 {
1156         if (!_enable_display) {
1157                 return;
1158         }
1159
1160         if (!force && _current_range_min == min && _current_range_max == max) {
1161                 return;
1162         }
1163
1164         _current_range_min = min;
1165         _current_range_max = max;
1166
1167         for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1168                 CanvasNoteEvent* event = *i;
1169                 boost::shared_ptr<NoteType> note (event->note());
1170
1171                 if (note->note() < _current_range_min ||
1172                     note->note() > _current_range_max) {
1173                         event->hide();
1174                 } else {
1175                         event->show();
1176                 }
1177
1178                 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1179
1180                         const double y1 = midi_stream_view()->note_to_y(note->note());
1181                         const double y2 = y1 + floor(midi_stream_view()->note_height());
1182
1183                         cnote->property_y1() = y1;
1184                         cnote->property_y2() = y2;
1185
1186                 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1187
1188                         double x = trackview.editor().frame_to_pixel(
1189                                 beats_to_frames(note->time()) - _region->start());
1190                         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1191                         double y = midi_stream_view()->note_to_y(event->note()->note())
1192                                 + ((diamond_size-2.0) / 4.0);
1193
1194                         chit->set_height (diamond_size);
1195                         chit->move (x - chit->x1(), y - chit->y1());
1196                         chit->show ();
1197                 }
1198         }
1199 }
1200
1201 GhostRegion*
1202 MidiRegionView::add_ghost (TimeAxisView& tv)
1203 {
1204         CanvasNote* note;
1205
1206         double unit_position = _region->position () / samples_per_unit;
1207         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1208         MidiGhostRegion* ghost;
1209
1210         if (mtv && mtv->midi_view()) {
1211                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1212                    to allow having midi notes on top of note lines and waveforms.
1213                  */
1214                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1215         } else {
1216                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1217         }
1218
1219         ghost->set_height ();
1220         ghost->set_duration (_region->length() / samples_per_unit);
1221         ghosts.push_back (ghost);
1222
1223         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1224                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1225                         ghost->add_note(note);
1226                 }
1227         }
1228
1229         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1230
1231         return ghost;
1232 }
1233
1234
1235 /** Begin tracking note state for successive calls to add_event
1236  */
1237 void
1238 MidiRegionView::begin_write()
1239 {
1240         assert(!_active_notes);
1241         _active_notes = new CanvasNote*[128];
1242         for (unsigned i=0; i < 128; ++i) {
1243                 _active_notes[i] = 0;
1244         }
1245 }
1246
1247
1248 /** Destroy note state for add_event
1249  */
1250 void
1251 MidiRegionView::end_write()
1252 {
1253         delete[] _active_notes;
1254         _active_notes = 0;
1255         _marked_for_selection.clear();
1256         _marked_for_velocity.clear();
1257 }
1258
1259
1260 /** Resolve an active MIDI note (while recording).
1261  */
1262 void
1263 MidiRegionView::resolve_note(uint8_t note, double end_time)
1264 {
1265         if (midi_view()->note_mode() != Sustained) {
1266                 return;
1267         }
1268
1269         if (_active_notes && _active_notes[note]) {
1270                 const nframes64_t end_time_frames = beats_to_frames(end_time);
1271                 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1272                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1273                 _active_notes[note] = 0;
1274         }
1275 }
1276
1277
1278 /** Extend active notes to rightmost edge of region (if length is changed)
1279  */
1280 void
1281 MidiRegionView::extend_active_notes()
1282 {
1283         if (!_active_notes) {
1284                 return;
1285         }
1286
1287         for (unsigned i=0; i < 128; ++i) {
1288                 if (_active_notes[i]) {
1289                         _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1290                 }
1291         }
1292 }
1293
1294 void
1295 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1296 {
1297         if (no_sound_notes || !trackview.editor().sound_notes()) {
1298                 return;
1299         }
1300
1301         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1302         assert(route_ui);
1303
1304         route_ui->midi_track()->write_immediate_event(
1305                         note->on_event().size(), note->on_event().buffer());
1306
1307         const double note_length_beats = (note->off_event().time() - note->on_event().time());
1308         nframes_t note_length_ms = beats_to_frames(note_length_beats)
1309                         * (1000 / (double)route_ui->session()->nominal_frame_rate());
1310         Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1311                         note_length_ms, G_PRIORITY_DEFAULT);
1312 }
1313
1314 bool
1315 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1316 {
1317         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1318         assert(route_ui);
1319
1320         route_ui->midi_track()->write_immediate_event(
1321                         note->off_event().size(), note->off_event().buffer());
1322
1323         return false;
1324 }
1325
1326 bool
1327 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1328 {
1329         const nframes64_t note_start_frames = beats_to_frames(note->time());
1330
1331         bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1332                 (note_start_frames < _region->start());
1333
1334         visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1335                 (note->note() <= midi_stream_view()->highest_note());
1336
1337         return !outside;
1338 }
1339
1340 void
1341 MidiRegionView::update_note (CanvasNote* ev)
1342 {
1343         boost::shared_ptr<NoteType> note = ev->note();
1344
1345         const nframes64_t note_start_frames = beats_to_frames(note->time());
1346
1347         /* trim note display to not overlap the end of its region */
1348         const nframes64_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1349
1350         const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1351         const double y1 = midi_stream_view()->note_to_y(note->note());
1352         const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1353
1354         ev->property_x1() = x;
1355         ev->property_y1() = y1;
1356         if (note->length() > 0) {
1357                 ev->property_x2() = note_endpixel;
1358         } else {
1359                 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1360         }
1361         ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1362
1363         if (note->length() == 0) {
1364                 if (_active_notes) {
1365                         assert(note->note() < 128);
1366                         // If this note is already active there's a stuck note,
1367                         // finish the old note rectangle
1368                         if (_active_notes[note->note()]) {
1369                                 CanvasNote* const old_rect = _active_notes[note->note()];
1370                                 boost::shared_ptr<NoteType> old_note = old_rect->note();
1371                                 old_rect->property_x2() = x;
1372                                 old_rect->property_outline_what() = (guint32) 0xF;
1373                         }
1374                         _active_notes[note->note()] = ev;
1375                 }
1376                 /* outline all but right edge */
1377                 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1378         } else {
1379                 /* outline all edges */
1380                 ev->property_outline_what() = (guint32) 0xF;
1381         }
1382 }
1383
1384 void
1385 MidiRegionView::update_hit (CanvasHit* ev)
1386 {
1387         boost::shared_ptr<NoteType> note = ev->note();
1388
1389         const nframes64_t note_start_frames = beats_to_frames(note->time());
1390         const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1391         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1392         const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1393
1394         ev->move_to (x, y);
1395 }
1396
1397 /** Add a MIDI note to the view (with length).
1398  *
1399  * If in sustained mode, notes with length 0 will be considered active
1400  * notes, and resolve_note should be called when the corresponding note off
1401  * event arrives, to properly display the note.
1402  */
1403 void
1404 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1405 {
1406         CanvasNoteEvent* event = 0;
1407
1408         assert(note->time() >= 0);
1409         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1410
1411         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1412
1413         if (midi_view()->note_mode() == Sustained) {
1414
1415                 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1416
1417                 update_note (ev_rect);
1418
1419                 event = ev_rect;
1420
1421                 MidiGhostRegion* gr;
1422
1423                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1424                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1425                                 gr->add_note(ev_rect);
1426                         }
1427                 }
1428
1429         } else if (midi_view()->note_mode() == Percussive) {
1430
1431                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1432
1433                 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1434
1435                 update_hit (ev_diamond);
1436
1437                 event = ev_diamond;
1438
1439         } else {
1440                 event = 0;
1441         }
1442
1443         if (event) {
1444                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1445                         note_selected(event, true);
1446                 }
1447
1448                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1449                         event->show_velocity();
1450                 }
1451                 event->on_channel_selection_change(_last_channel_selection);
1452                 _events.push_back(event);
1453
1454                 if (visible) {
1455                         event->show();
1456                 } else {
1457                         event->hide ();
1458                 }
1459         }
1460 }
1461
1462 void
1463 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1464                           Evoral::MusicalTime pos, Evoral::MusicalTime len)
1465 {
1466         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1467
1468         /* potentially extend region to hold new note */
1469
1470         nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1471         nframes64_t region_end = _region->position() + _region->length() - 1;
1472
1473         if (end_frame > region_end) {
1474                 _region->set_length (end_frame, this);
1475         }
1476
1477         start_diff_command (_("step add"));
1478         diff_add_note (new_note, true, false);
1479         apply_diff();
1480
1481         // last_step_edit_note = new_note;
1482 }
1483
1484 void
1485 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1486 {
1487         assert(program.time >= 0);
1488
1489         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1490         const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1491
1492         double height = midi_stream_view()->contents_height();
1493
1494         boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1495                         new CanvasProgramChange(*this, *group,
1496                                         displaytext,
1497                                         height,
1498                                         x, 1.0,
1499                                         _model_name,
1500                                         _custom_device_mode,
1501                                         program.time, program.channel, program.value));
1502
1503         // Show unless program change is beyond the region bounds
1504         if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1505                 pgm_change->hide();
1506         } else {
1507                 pgm_change->show();
1508         }
1509
1510         _pgm_changes.push_back(pgm_change);
1511 }
1512
1513 void
1514 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1515 {
1516         Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1517         boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1518         double msb = 0.0;
1519         if (msb_control != 0) {
1520                 msb = int(msb_control->get_double(true, time));
1521         }
1522
1523         Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1524         boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1525         double lsb = 0.0;
1526         if (lsb_control != 0) {
1527                 lsb = lsb_control->get_double(true, time);
1528         }
1529
1530         Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1531         boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1532         double program_number = -1.0;
1533         if (program_control != 0) {
1534                 program_number = program_control->get_double(true, time);
1535         }
1536
1537         key.msb = (int) floor(msb + 0.5);
1538         key.lsb = (int) floor(lsb + 0.5);
1539         key.program_number = (int) floor(program_number + 0.5);
1540         assert(key.is_sane());
1541 }
1542
1543
1544 void
1545 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1546 {
1547         // TODO: Get the real event here and alter them at the original times
1548         Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1549         boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1550         if (msb_control != 0) {
1551                 msb_control->set_double(double(new_patch.msb), true, old_program.time);
1552         }
1553
1554         // TODO: Get the real event here and alter them at the original times
1555         Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1556         boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1557         if (lsb_control != 0) {
1558                 lsb_control->set_double(double(new_patch.lsb), true, old_program.time);
1559         }
1560
1561         Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1562         boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1563
1564         assert(program_control != 0);
1565         program_control->set_double(float(new_patch.program_number), true, old_program.time);
1566
1567         _pgm_changes.clear ();
1568         display_program_changes (); // XXX would be nice to limit to just old_program.channel
1569 }
1570
1571 void
1572 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1573 {
1574         PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1575         alter_program_change(program_change_event, new_patch);
1576 }
1577
1578 void
1579 MidiRegionView::previous_program(CanvasProgramChange& program)
1580 {
1581         if (program.program() < 127) {
1582                 MIDI::Name::PatchPrimaryKey key;
1583                 get_patch_key_at(program.event_time(), program.channel(), key);
1584                 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1585
1586                 key.program_number++;
1587                 alter_program_change(program_change_event, key);
1588         }
1589 }
1590
1591 void
1592 MidiRegionView::next_program(CanvasProgramChange& program)
1593 {
1594         if (program.program() > 0) {
1595                 MIDI::Name::PatchPrimaryKey key;
1596                 get_patch_key_at(program.event_time(), program.channel(), key);
1597                 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1598
1599                 key.program_number--;
1600                 alter_program_change(program_change_event, key);
1601         }
1602 }
1603
1604 void
1605 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1606 {
1607         if (_selection.empty()) {
1608                 return;
1609         }
1610  
1611         if (_selection.erase (cne) > 0) {
1612                 cerr << "Erased a CNE from selection\n";
1613         }
1614 }
1615
1616 void
1617 MidiRegionView::delete_selection()
1618 {
1619         if (_selection.empty()) {
1620                 return;
1621         }
1622
1623         start_diff_command (_("delete selection"));
1624
1625         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1626                 if ((*i)->selected()) {
1627                         _diff_command->remove((*i)->note());
1628                 }
1629         }
1630
1631         _selection.clear();
1632
1633         apply_diff ();
1634 }
1635
1636 void
1637 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1638 {
1639         start_diff_command (_("delete note"));
1640         _diff_command->remove (n);
1641         apply_diff ();
1642
1643         trackview.editor().hide_verbose_canvas_cursor ();
1644 }
1645
1646 void
1647 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1648 {
1649         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1650                 if ((*i)->selected() && (*i) != ev) {
1651                         (*i)->set_selected(false);
1652                         (*i)->hide_velocity();
1653                 }
1654         }
1655
1656         _selection.clear();
1657 }
1658
1659 void
1660 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1661 {
1662         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1663                 if ((*i) != ev) {
1664
1665                         Selection::iterator tmp = i;
1666                         ++tmp;
1667
1668                         (*i)->set_selected (false);
1669                         _selection.erase (i);
1670
1671                         i = tmp;
1672
1673                 } else {
1674                         ++i;
1675                 }
1676         }
1677
1678         /* don't bother with removing this regionview from the editor selection,
1679            since we're about to add another note, and thus put/keep this
1680            regionview in the editor selection.
1681         */
1682
1683         if (!ev->selected()) {
1684                 add_to_selection (ev);
1685         }
1686 }
1687
1688 void
1689 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1690 {
1691         uint8_t low_note = 127;
1692         uint8_t high_note = 0;
1693         MidiModel::Notes& notes (_model->notes());
1694         _optimization_iterator = _events.begin();
1695
1696         if (!add) {
1697                 clear_selection ();
1698         }
1699
1700         if (extend && _selection.empty()) {
1701                 extend = false;
1702         }
1703
1704         if (extend) {
1705
1706                 /* scan existing selection to get note range */
1707
1708                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1709                         if ((*i)->note()->note() < low_note) {
1710                                 low_note = (*i)->note()->note();
1711                         }
1712                         if ((*i)->note()->note() > high_note) {
1713                                 high_note = (*i)->note()->note();
1714                         }
1715                 }
1716
1717                 low_note = min (low_note, notenum);
1718                 high_note = max (high_note, notenum);
1719         }
1720
1721         no_sound_notes = true;
1722
1723         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1724
1725                 boost::shared_ptr<NoteType> note (*n);
1726                 CanvasNoteEvent* cne;
1727                 bool select = false;
1728
1729                 if (((1 << note->channel()) & channel_mask) != 0) {
1730                         if (extend) {
1731                                 if ((note->note() >= low_note && note->note() <= high_note)) {
1732                                         select = true;
1733                                 }
1734                         } else if (note->note() == notenum) {
1735                                 select = true;
1736                         }
1737                 }
1738
1739                 if (select) {
1740                         if ((cne = find_canvas_note (note)) != 0) {
1741                                 // extend is false because we've taken care of it, 
1742                                 // since it extends by time range, not pitch.
1743                                 note_selected (cne, add, false);
1744                         }
1745                 }
1746                 
1747                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1748
1749         }
1750
1751         no_sound_notes = false;
1752 }
1753
1754 void
1755 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1756 {
1757         MidiModel::Notes& notes (_model->notes());
1758         _optimization_iterator = _events.begin();
1759
1760         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1761
1762                 boost::shared_ptr<NoteType> note (*n);
1763                 CanvasNoteEvent* cne;
1764
1765                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1766                         if ((cne = find_canvas_note (note)) != 0) {
1767                                 if (cne->selected()) {
1768                                         note_deselected (cne);
1769                                 } else {
1770                                         note_selected (cne, true, false);
1771                                 }
1772                         }
1773                 }
1774         }
1775 }
1776
1777 void
1778 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1779 {
1780         if (!add) {
1781                 clear_selection_except(ev);
1782         }
1783
1784         if (!extend) {
1785
1786                 if (!ev->selected()) {
1787                         add_to_selection (ev);
1788                 }
1789
1790         } else {
1791                 /* find end of latest note selected, select all between that and the start of "ev" */
1792
1793                 Evoral::MusicalTime earliest = DBL_MAX;
1794                 Evoral::MusicalTime latest = 0;
1795
1796                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1797                         if ((*i)->note()->end_time() > latest) {
1798                                 latest = (*i)->note()->end_time();
1799                         }
1800                         if ((*i)->note()->time() < earliest) {
1801                                 earliest = (*i)->note()->time();
1802                         }
1803                 }
1804
1805                 if (ev->note()->end_time() > latest) {
1806                         latest = ev->note()->end_time();
1807                 }
1808
1809                 if (ev->note()->time() < earliest) {
1810                         earliest = ev->note()->time();
1811                 }
1812
1813                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1814
1815                         /* find notes entirely within OR spanning the earliest..latest range */
1816
1817                         if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1818                             ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1819                                 add_to_selection (*i);
1820                         }
1821
1822 #if 0
1823                         /* if events were guaranteed to be time sorted, we could do this.
1824                            but as of sept 10th 2009, they no longer are.
1825                         */
1826
1827                         if ((*i)->note()->time() > latest) {
1828                                 break;
1829                         }
1830 #endif
1831                 }
1832         }
1833 }
1834
1835 void
1836 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1837 {
1838         remove_from_selection (ev);
1839 }
1840
1841 void
1842 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1843 {
1844         if (x1 > x2) {
1845                 swap (x1, x2);
1846         }
1847
1848         if (y1 > y2) {
1849                 swap (y1, y2);
1850         }
1851
1852         // TODO: Make this faster by storing the last updated selection rect, and only
1853         // adjusting things that are in the area that appears/disappeared.
1854         // We probably need a tree to be able to find events in O(log(n)) time.
1855
1856         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1857
1858                 /* check if any corner of the note is inside the rect
1859
1860                    Notes:
1861                      1) this is computing "touched by", not "contained by" the rect.
1862                      2) this does not require that events be sorted in time.
1863                  */
1864
1865                 const double ix1 = (*i)->x1();
1866                 const double ix2 = (*i)->x2();
1867                 const double iy1 = (*i)->y1();
1868                 const double iy2 = (*i)->y2();
1869
1870                 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1871                     (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1872                     (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1873                     (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1874
1875                         // Inside rectangle
1876                         if (!(*i)->selected()) {
1877                                 add_to_selection (*i);
1878                         }
1879                 } else if ((*i)->selected()) {
1880                         // Not inside rectangle
1881                         remove_from_selection (*i);
1882                 }
1883         }
1884 }
1885
1886 void
1887 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1888 {
1889         Selection::iterator i = _selection.find (ev);
1890
1891         if (i != _selection.end()) {
1892                 _selection.erase (i);
1893         }
1894
1895         ev->set_selected (false);
1896         ev->hide_velocity ();
1897
1898         if (_selection.empty()) {
1899                 PublicEditor& editor (trackview.editor());
1900                 editor.get_selection().remove (this);
1901         }
1902 }
1903
1904 void
1905 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1906 {
1907         bool add_mrv_selection = false;
1908
1909         if (_selection.empty()) {
1910                 add_mrv_selection = true;
1911         }
1912
1913         if (_selection.insert (ev).second) {
1914                 ev->set_selected (true);
1915                 play_midi_note ((ev)->note());
1916         }
1917
1918         if (add_mrv_selection) {
1919                 PublicEditor& editor (trackview.editor());
1920                 editor.get_selection().add (this);
1921         }
1922 }
1923
1924 void
1925 MidiRegionView::move_selection(double dx, double dy)
1926 {
1927         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1928                 (*i)->move_event(dx, dy);
1929         }
1930 }
1931
1932 void
1933 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1934 {
1935         assert (!_selection.empty());
1936
1937         uint8_t lowest_note_in_selection  = 127;
1938         uint8_t highest_note_in_selection = 0;
1939         uint8_t highest_note_difference = 0;
1940
1941         // find highest and lowest notes first
1942
1943         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1944                 uint8_t pitch = (*i)->note()->note();
1945                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
1946                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1947         }
1948
1949         /*
1950         cerr << "dnote: " << (int) dnote << endl;
1951         cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1952              << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1953         cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1954              << int(highest_note_in_selection) << endl;
1955         cerr << "selection size: " << _selection.size() << endl;
1956         cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1957         */
1958
1959         // Make sure the note pitch does not exceed the MIDI standard range
1960         if (highest_note_in_selection + dnote > 127) {
1961                 highest_note_difference = highest_note_in_selection - 127;
1962         }
1963
1964         start_diff_command(_("move notes"));
1965
1966         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1967
1968                 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1969
1970                 if (dt >= 0) {
1971                         start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1972                 } else {
1973                         start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1974                 }
1975
1976                 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1977
1978                 if (new_time < 0) {
1979                         continue;
1980                 }
1981
1982                 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1983
1984                 uint8_t original_pitch = (*i)->note()->note();
1985                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
1986
1987                 // keep notes in standard midi range
1988                 clamp_to_0_127(new_pitch);
1989
1990                 // keep original pitch if note is dragged outside valid midi range
1991                 if ((original_pitch != 0 && new_pitch == 0)
1992                                 || (original_pitch != 127 && new_pitch == 127)) {
1993                         new_pitch = original_pitch;
1994                 }
1995
1996                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
1997                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1998
1999                 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2000         }
2001
2002         apply_diff();
2003
2004         // care about notes being moved beyond the upper/lower bounds on the canvas
2005         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2006             highest_note_in_selection > midi_stream_view()->highest_note()) {
2007                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2008         }
2009 }
2010
2011 nframes64_t
2012 MidiRegionView::snap_pixel_to_frame(double x)
2013 {
2014         PublicEditor& editor = trackview.editor();
2015         // x is region relative, convert it to global absolute frames
2016         nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
2017         editor.snap_to(frame);
2018         return frame - _region->position(); // convert back to region relative
2019 }
2020
2021 nframes64_t
2022 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2023 {
2024         PublicEditor& editor = trackview.editor();
2025         // x is region relative, convert it to global absolute frames
2026         nframes64_t frame = x + _region->position();
2027         editor.snap_to(frame);
2028         return frame - _region->position(); // convert back to region relative
2029 }
2030
2031 double
2032 MidiRegionView::snap_to_pixel(double x)
2033 {
2034         return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2035 }
2036
2037 double
2038 MidiRegionView::get_position_pixels()
2039 {
2040         nframes64_t region_frame = get_position();
2041         return trackview.editor().frame_to_pixel(region_frame);
2042 }
2043
2044 double
2045 MidiRegionView::get_end_position_pixels()
2046 {
2047         nframes64_t frame = get_position() + get_duration ();
2048         return trackview.editor().frame_to_pixel(frame);
2049 }
2050
2051 nframes64_t
2052 MidiRegionView::beats_to_frames(double beats) const
2053 {
2054         return _time_converter.to(beats);
2055 }
2056
2057 double
2058 MidiRegionView::frames_to_beats(nframes64_t frames) const
2059 {
2060         return _time_converter.from(frames);
2061 }
2062
2063 void
2064 MidiRegionView::begin_resizing (bool /*at_front*/)
2065 {
2066         _resize_data.clear();
2067
2068         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2069                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2070
2071                 // only insert CanvasNotes into the map
2072                 if (note) {
2073                         NoteResizeData *resize_data = new NoteResizeData();
2074                         resize_data->canvas_note = note;
2075
2076                         // create a new SimpleRect from the note which will be the resize preview
2077                         SimpleRect *resize_rect = new SimpleRect(
2078                                         *group, note->x1(), note->y1(), note->x2(), note->y2());
2079
2080                         // calculate the colors: get the color settings
2081                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2082                                         ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2083                                         128);
2084
2085                         // make the resize preview notes more transparent and bright
2086                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2087
2088                         // calculate color based on note velocity
2089                         resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2090                                 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2091                                         fill_color,
2092                                         0.85);
2093
2094                         resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2095                                         ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2096
2097                         resize_data->resize_rect = resize_rect;
2098                         _resize_data.push_back(resize_data);
2099                 }
2100         }
2101 }
2102
2103 /** Update resizing notes while user drags.
2104  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2105  * @param at_front which end of the note (true == note on, false == note off)
2106  * @param delta_x change in mouse position since the start of the drag 
2107  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2108  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2109  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2110  * as the \a primary note.
2111  */
2112 void
2113 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2114 {
2115         bool cursor_set = false;
2116
2117         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2118                 SimpleRect* resize_rect = (*i)->resize_rect;
2119                 CanvasNote* canvas_note = (*i)->canvas_note;
2120                 double current_x;
2121
2122                 if (at_front) {
2123                         if (relative) {
2124                                 current_x = canvas_note->x1() + delta_x;
2125                         } else {
2126                                 current_x = primary->x1() + delta_x;
2127                         }
2128                 } else {
2129                         if (relative) {
2130                                 current_x = canvas_note->x2() + delta_x;
2131                         } else {
2132                                 current_x = primary->x2() + delta_x;
2133                         }
2134                 }
2135
2136                 if (at_front) {
2137                         resize_rect->property_x1() = snap_to_pixel(current_x);
2138                         resize_rect->property_x2() = canvas_note->x2();
2139                 } else {
2140                         resize_rect->property_x2() = snap_to_pixel(current_x);
2141                         resize_rect->property_x1() = canvas_note->x1();
2142                 }
2143
2144                 if (!cursor_set) {
2145                         double beats;
2146
2147                         beats = snap_pixel_to_frame (current_x);
2148                         beats = frames_to_beats (beats);
2149                         
2150                         double len;
2151
2152                         if (at_front) {
2153                                 if (beats < canvas_note->note()->end_time()) {
2154                                         len = canvas_note->note()->time() - beats;
2155                                         len += canvas_note->note()->length();
2156                                 } else {
2157                                         len = 0;
2158                                 }
2159                         } else {
2160                                 if (beats >= canvas_note->note()->end_time()) { 
2161                                         len = beats - canvas_note->note()->time();
2162                                 } else {
2163                                         len = 0;
2164                                 }
2165                         }
2166
2167                         char buf[16];
2168                         snprintf (buf, sizeof (buf), "%.3g beats", len);
2169                         trackview.editor().show_verbose_canvas_cursor_with (buf);
2170
2171                         cursor_set = true;
2172                 }
2173
2174         }
2175 }
2176
2177
2178 /** Finish resizing notes when the user releases the mouse button.
2179  *  Parameters the same as for \a update_resizing().
2180  */
2181 void
2182 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2183 {
2184         start_diff_command(_("resize notes"));
2185
2186         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2187                 CanvasNote*  canvas_note = (*i)->canvas_note;
2188                 SimpleRect*  resize_rect = (*i)->resize_rect;
2189                 double current_x;
2190
2191                 if (at_front) {
2192                         if (relative) {
2193                                 current_x = canvas_note->x1() + delta_x;
2194                         } else {
2195                                 current_x = primary->x1() + delta_x;
2196                         }
2197                 } else {
2198                         if (relative) {
2199                                 current_x = canvas_note->x2() + delta_x;
2200                         } else {
2201                                 current_x = primary->x2() + delta_x;
2202                         }
2203                 }
2204
2205                 current_x = snap_pixel_to_frame (current_x);
2206                 current_x = frames_to_beats (current_x);
2207
2208                 if (at_front && current_x < canvas_note->note()->end_time()) {
2209                         diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2210
2211                         double len = canvas_note->note()->time() - current_x;
2212                         len += canvas_note->note()->length();
2213
2214                         if (len > 0) {
2215                                 /* XXX convert to beats */
2216                                 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2217                         }
2218                 }
2219
2220                 if (!at_front) {
2221                         double len = current_x - canvas_note->note()->time();
2222
2223                         if (len > 0) {
2224                                 /* XXX convert to beats */
2225                                 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2226                         }
2227                 }
2228
2229                 delete resize_rect;
2230                 delete (*i);
2231         }
2232
2233         _resize_data.clear();
2234         apply_diff();
2235 }
2236
2237 void
2238 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2239 {
2240         uint8_t new_velocity;
2241
2242         if (relative) {
2243                 new_velocity = event->note()->velocity() + velocity;
2244                 clamp_to_0_127(new_velocity);
2245         } else {
2246                 new_velocity = velocity;
2247         }
2248
2249         event->set_selected (event->selected()); // change color 
2250         
2251         diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2252 }
2253
2254 void
2255 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2256 {
2257         uint8_t new_note;
2258
2259         if (relative) {
2260                 new_note = event->note()->note() + note;
2261         } else {
2262                 new_note = note;
2263         }
2264
2265         clamp_to_0_127 (new_note);
2266         diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2267 }
2268
2269 void
2270 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2271 {
2272         bool change_start = false;
2273         bool change_length = false;
2274         Evoral::MusicalTime new_start = 0;
2275         Evoral::MusicalTime new_length = 0;
2276
2277         /* NOTE: the semantics of the two delta arguments are slightly subtle:
2278
2279            front_delta: if positive - move the start of the note later in time (shortening it)
2280                         if negative - move the start of the note earlier in time (lengthening it)
2281
2282            end_delta:   if positive - move the end of the note later in time (lengthening it)
2283                         if negative - move the end of the note earlier in time (shortening it)
2284          */
2285
2286         if (front_delta) {
2287                 if (front_delta < 0) {
2288
2289                         if (event->note()->time() < -front_delta) {
2290                                 new_start = 0;
2291                         } else {
2292                                 new_start = event->note()->time() + front_delta; // moves earlier
2293                         }
2294
2295                         /* start moved toward zero, so move the end point out to where it used to be.
2296                            Note that front_delta is negative, so this increases the length.
2297                         */
2298
2299                         new_length = event->note()->length() - front_delta;
2300                         change_start = true;
2301                         change_length = true;
2302
2303                 } else {
2304
2305                         Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2306
2307                         if (new_pos < event->note()->end_time()) {
2308                                 new_start = event->note()->time() + front_delta;
2309                                 /* start moved toward the end, so move the end point back to where it used to be */
2310                                 new_length = event->note()->length() - front_delta;
2311                                 change_start = true;
2312                                 change_length = true;
2313                         }
2314                 }
2315
2316         }
2317
2318         if (end_delta) {
2319                 bool can_change = true;
2320                 if (end_delta < 0) {
2321                         if (event->note()->length() < -end_delta) {
2322                                 can_change = false;
2323                         }
2324                 }
2325
2326                 if (can_change) {
2327                         new_length = event->note()->length() + end_delta;
2328                         change_length = true;
2329                 }
2330         }
2331
2332         if (change_start) {
2333                 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2334         }
2335
2336         if (change_length) {
2337                 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2338         }
2339 }
2340
2341 void
2342 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2343 {
2344         Evoral::MusicalTime new_time;
2345
2346         if (relative) {
2347                 if (delta < 0.0) {
2348                         if (event->note()->time() < -delta) {
2349                                 new_time = 0;
2350                         } else {
2351                                 new_time = event->note()->time() + delta;
2352                         }
2353                 } else {
2354                         new_time = event->note()->time() + delta;
2355                 }
2356         } else {
2357                 new_time = delta;
2358         }
2359
2360         diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2361 }
2362
2363 void
2364 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2365 {
2366         int8_t delta;
2367
2368         if (_selection.empty()) {
2369                 return;
2370         }
2371
2372         if (fine) {
2373                 delta = 1;
2374         } else {
2375                 delta = 10;
2376         }
2377
2378         if (!up) {
2379                 delta = -delta;
2380         }
2381
2382         if (!allow_smush) {
2383                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2384                         if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2385                                 return;
2386                         }
2387                 }
2388         }
2389
2390         start_diff_command(_("change velocities"));
2391
2392         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2393                 Selection::iterator next = i;
2394                 ++next;
2395                 change_note_velocity (*i, delta, true);
2396                 i = next;
2397         }
2398
2399         apply_diff();
2400         
2401         if (!_selection.empty()) {
2402                 char buf[24];
2403                 snprintf (buf, sizeof (buf), "Vel %d", 
2404                           (int) (*_selection.begin())->note()->velocity());
2405                 trackview.editor().show_verbose_canvas_cursor_with (buf);
2406         }
2407 }
2408
2409
2410 void
2411 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2412 {
2413         if (_selection.empty()) {
2414                 return;
2415         }
2416
2417         int8_t delta;
2418
2419         if (fine) {
2420                 delta = 1;
2421         } else {
2422                 delta = 12;
2423         }
2424
2425         if (!up) {
2426                 delta = -delta;
2427         }
2428
2429         if (!allow_smush) {
2430                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2431                         if (!up) {
2432                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2433                                         return;
2434                                 }
2435                         } else {
2436                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
2437                                         return;
2438                                 }
2439                         }
2440                 }
2441         }
2442
2443         start_diff_command (_("transpose"));
2444
2445         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2446                 Selection::iterator next = i;
2447                 ++next;
2448                 change_note_note (*i, delta, true);
2449                 i = next;
2450         }
2451
2452         apply_diff ();
2453 }
2454
2455 void
2456 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2457 {
2458         Evoral::MusicalTime delta;
2459
2460         if (fine) {
2461                 delta = 1.0/128.0;
2462         } else {
2463                 /* grab the current grid distance */
2464                 bool success;
2465                 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2466                 if (!success) {
2467                         /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2468                         cerr << "Grid type not available as beats - TO BE FIXED\n";
2469                         return;
2470                 }
2471         }
2472
2473         if (shorter) {
2474                 delta = -delta;
2475         }
2476
2477         start_diff_command (_("change note lengths"));
2478
2479         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2480                 Selection::iterator next = i;
2481                 ++next;
2482
2483                 /* note the negation of the delta for start */
2484
2485                 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2486                 i = next;
2487         }
2488
2489         apply_diff ();
2490
2491 }
2492
2493 void
2494 MidiRegionView::nudge_notes (bool forward)
2495 {
2496         if (_selection.empty()) {
2497                 return;
2498         }
2499
2500         /* pick a note as the point along the timeline to get the nudge distance.
2501            its not necessarily the earliest note, so we may want to pull the notes out
2502            into a vector and sort before using the first one.
2503         */
2504
2505         nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2506         nframes64_t unused;
2507         nframes64_t distance;
2508
2509         if (trackview.editor().snap_mode() == Editing::SnapOff) {
2510                 
2511                 /* grid is off - use nudge distance */
2512
2513                 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2514
2515         } else {
2516
2517                 /* use grid */
2518
2519                 nframes64_t next_pos = ref_point;
2520
2521                 if (forward) {
2522                         /* XXX need check on max_frames, but that needs max_frames64 or something */
2523                         next_pos += 1;
2524                 } else {
2525                         if (next_pos == 0) {
2526                                 return;
2527                         }
2528                         next_pos -= 1;
2529                 }
2530
2531                 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2532                 distance = ref_point - next_pos;
2533         }
2534
2535         if (distance == 0) {
2536                 return;
2537         }
2538
2539         Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2540
2541         if (!forward) {
2542                 delta = -delta;
2543         }
2544
2545         start_diff_command (_("nudge"));
2546
2547         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2548                 Selection::iterator next = i;
2549                 ++next;
2550                 change_note_time (*i, delta, true);
2551                 i = next;
2552         }
2553
2554         apply_diff ();
2555 }
2556
2557 void
2558 MidiRegionView::change_channel(uint8_t channel)
2559 {
2560         start_diff_command(_("change channel"));
2561         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2562                 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2563         }
2564
2565         apply_diff();
2566 }
2567
2568
2569 void
2570 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2571 {
2572         if (_mouse_state == SelectTouchDragging) {
2573                 note_selected (ev, true);
2574         }
2575
2576         show_verbose_canvas_cursor (ev->note ());
2577 }
2578
2579 void
2580 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2581 {
2582         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2583                 (*i)->hide_velocity ();
2584         }
2585
2586         trackview.editor().hide_verbose_canvas_cursor ();
2587 }
2588
2589 void
2590 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2591 {
2592         boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2593         if (msrc)
2594                 display_model(msrc->model());
2595 }
2596
2597 void
2598 MidiRegionView::set_frame_color()
2599 {
2600         if (frame) {
2601                 if (_selected && should_show_selection) {
2602                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2603                 } else {
2604                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2605                 }
2606         }
2607 }
2608
2609 void
2610 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2611 {
2612         switch (mode) {
2613         case AllChannels:
2614         case FilterChannels:
2615                 _force_channel = -1;
2616                 break;
2617         case ForceChannel:
2618                 _force_channel = mask;
2619                 mask = 0xFFFF; // Show all notes as active (below)
2620         };
2621
2622         // Update notes for selection
2623         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2624                 (*i)->on_channel_selection_change(mask);
2625         }
2626
2627         _last_channel_selection = mask;
2628 }
2629
2630 void
2631 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2632 {
2633         _model_name         = model;
2634         _custom_device_mode = custom_device_mode;
2635         redisplay_model();
2636 }
2637
2638 void
2639 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2640 {
2641         if (_selection.empty()) {
2642                 return;
2643         }
2644
2645         PublicEditor& editor (trackview.editor());
2646
2647         switch (op) {
2648         case Cut:
2649         case Copy:
2650                 editor.get_cut_buffer().add (selection_as_cut_buffer());
2651                 break;
2652         default:
2653                 break;
2654         }
2655
2656         if (op != Copy) {
2657
2658                 start_diff_command();
2659                 
2660                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2661                         switch (op) {
2662                         case Copy:
2663                                 break;
2664                         case Cut:
2665                         case Clear:
2666                                 diff_remove_note (*i);
2667                                 break;
2668                         }
2669                 }
2670                 
2671                 apply_diff();
2672         }
2673 }
2674
2675 MidiCutBuffer*
2676 MidiRegionView::selection_as_cut_buffer () const
2677 {
2678         Notes notes;
2679
2680         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2681                 NoteType* n = (*i)->note().get();
2682                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2683         }
2684
2685         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2686         cb->set (notes);
2687
2688         return cb;
2689 }
2690
2691 void
2692 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2693 {
2694         if (mcb.empty()) {
2695                 return;
2696         }
2697
2698         start_diff_command (_("paste"));
2699
2700         Evoral::MusicalTime beat_delta;
2701         Evoral::MusicalTime paste_pos_beats;
2702         Evoral::MusicalTime duration;
2703         Evoral::MusicalTime end_point = 0;
2704
2705         duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2706         paste_pos_beats = frames_to_beats (pos - _region->position());
2707         beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2708         paste_pos_beats = 0;
2709
2710         _selection.clear ();
2711
2712         for (int n = 0; n < (int) times; ++n) {
2713
2714                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2715
2716                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2717                         copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2718
2719                         /* make all newly added notes selected */
2720
2721                         diff_add_note (copied_note, true);
2722                         end_point = copied_note->end_time();
2723                 }
2724
2725                 paste_pos_beats += duration;
2726         }
2727
2728         /* if we pasted past the current end of the region, extend the region */
2729
2730         nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2731         nframes64_t region_end = _region->position() + _region->length() - 1;
2732
2733         if (end_frame > region_end) {
2734
2735                 trackview.session()->begin_reversible_command (_("paste"));
2736
2737                 _region->clear_history ();
2738                 _region->set_length (end_frame, this);
2739                 trackview.session()->add_command (new StatefulDiffCommand (_region));
2740         }
2741
2742         apply_diff ();
2743 }
2744
2745 struct EventNoteTimeEarlyFirstComparator {
2746     bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2747             return a->note()->time() < b->note()->time();
2748     }
2749 };
2750
2751 void
2752 MidiRegionView::time_sort_events ()
2753 {
2754         if (!_sort_needed) {
2755                 return;
2756         }
2757
2758         EventNoteTimeEarlyFirstComparator cmp;
2759         _events.sort (cmp);
2760
2761         _sort_needed = false;
2762 }
2763
2764 void
2765 MidiRegionView::goto_next_note ()
2766 {
2767         // nframes64_t pos = -1;
2768         bool use_next = false;
2769
2770         if (_events.back()->selected()) {
2771                 return;
2772         }
2773
2774         time_sort_events ();
2775
2776         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2777                 if ((*i)->selected()) {
2778                         use_next = true;
2779                         continue;
2780                 } else if (use_next) {
2781                         unique_select (*i);
2782                         // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2783                         return;
2784                 }
2785         }
2786
2787         /* use the first one */
2788
2789         unique_select (_events.front());
2790
2791 }
2792
2793 void
2794 MidiRegionView::goto_previous_note ()
2795 {
2796         // nframes64_t pos = -1;
2797         bool use_next = false;
2798
2799         if (_events.front()->selected()) {
2800                 return;
2801         }
2802
2803         time_sort_events ();
2804
2805         for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2806                 if ((*i)->selected()) {
2807                         use_next = true;
2808                         continue;
2809                 } else if (use_next) {
2810                         unique_select (*i);
2811                         // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2812                         return;
2813                 }
2814         }
2815
2816         /* use the last one */
2817
2818         unique_select (*(_events.rbegin()));
2819 }
2820
2821 void
2822 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2823 {
2824         bool had_selected = false;
2825
2826         time_sort_events ();
2827
2828         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2829                 if ((*i)->selected()) {
2830                         selected.insert ((*i)->note());
2831                         had_selected = true;
2832                 }
2833         }
2834         
2835         if (allow_all_if_none_selected && !had_selected) {
2836                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2837                         selected.insert ((*i)->note());
2838                 }
2839         }
2840 }
2841
2842 void
2843 MidiRegionView::update_ghost_note (double x, double y)
2844 {
2845         _last_ghost_x = x;
2846         _last_ghost_y = y;
2847         
2848         group->w2i (x, y);
2849         nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2850         trackview.editor().snap_to (f);
2851         f -= _region->position ();
2852
2853         bool success;
2854         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2855         if (!success) {
2856                 beats = 1;
2857         }
2858         
2859         double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2860         
2861         _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2862         _ghost_note->note()->set_length (length);
2863         _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2864
2865         update_note (_ghost_note);
2866
2867         show_verbose_canvas_cursor (_ghost_note->note ());
2868 }
2869
2870 void
2871 MidiRegionView::create_ghost_note (double x, double y)
2872 {
2873         delete _ghost_note;
2874         _ghost_note = 0;
2875
2876         boost::shared_ptr<NoteType> g (new NoteType);
2877         _ghost_note = new NoEventCanvasNote (*this, *group, g);
2878         update_ghost_note (x, y);
2879         _ghost_note->show ();
2880
2881         _last_ghost_x = x;
2882         _last_ghost_y = y;
2883
2884         show_verbose_canvas_cursor (_ghost_note->note ());
2885 }
2886
2887 void
2888 MidiRegionView::snap_changed ()
2889 {
2890         if (!_ghost_note) {
2891                 return;
2892         }
2893         
2894         create_ghost_note (_last_ghost_x, _last_ghost_y);
2895 }
2896
2897 void
2898 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2899 {
2900         char buf[24];
2901         snprintf (buf, sizeof (buf), "%s (%d)\nVel %d", 
2902                   Evoral::midi_note_name (n->note()).c_str(), 
2903                   (int) n->note (),
2904                   (int) n->velocity());
2905         trackview.editor().show_verbose_canvas_cursor_with (buf);
2906 }
2907
2908 void
2909 MidiRegionView::drop_down_keys ()
2910 {
2911         _mouse_state = None;
2912 }
2913
2914 void
2915 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2916 {
2917         double note = midi_stream_view()->y_to_note(y);
2918         Events e;
2919         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2920         
2921         cerr << "Selecting by position\n";
2922
2923         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2924
2925         if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2926                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2927         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2928                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2929         } else {
2930                 return;
2931         }
2932
2933         bool add_mrv_selection = false;
2934
2935         if (_selection.empty()) {
2936                 add_mrv_selection = true;
2937         }
2938
2939         for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2940                 if (_selection.insert (*i).second) {
2941                         (*i)->set_selected (true);
2942                 }
2943         }
2944
2945         if (add_mrv_selection) {
2946                 PublicEditor& editor (trackview.editor());
2947                 editor.get_selection().add (this);
2948         }
2949 }                
2950
2951 void
2952 MidiRegionView::color_handler ()
2953 {
2954         RegionView::color_handler ();
2955
2956         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2957                 (*i)->set_selected ((*i)->selected()); // will change color
2958         }
2959
2960         /* XXX probably more to do here */
2961 }
2962
2963 void
2964 MidiRegionView::enable_display (bool yn)
2965 {
2966         RegionView::enable_display (yn);
2967         if (yn) {
2968                 redisplay_model ();
2969         }
2970 }