Stop MidiRegionView claiming PrimaryModifier-scrollwheel so
[ardour.git] / gtk2_ardour / midi_region_view.cc
1 /*
2     Copyright (C) 2001-2011 Paul Davis
3     Author: David 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_patch_change.h"
52 #include "debug.h"
53 #include "editor.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
56 #include "keyboard.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
70 #include "utils.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
74
75 #include "i18n.h"
76
77 using namespace ARDOUR;
78 using namespace PBD;
79 using namespace Editing;
80 using namespace ArdourCanvas;
81 using Gtkmm2ext::Keyboard;
82
83 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
84
85 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
86
87 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
88                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
89         : RegionView (parent, tv, r, spu, basic_color)
90         , _force_channel(-1)
91         , _last_channel_selection(0xFFFF)
92         , _current_range_min(0)
93         , _current_range_max(0)
94         , _model_name(string())
95         , _custom_device_mode(string())
96         , _active_notes(0)
97         , _note_group(new ArdourCanvas::Group(*group))
98         , _note_diff_command (0)
99         , _ghost_note(0)
100         , _drag_rect (0)
101         , _step_edit_cursor (0)
102         , _step_edit_cursor_width (1.0)
103         , _step_edit_cursor_position (0.0)
104         , _channel_selection_scoped_note (0)
105         , _temporary_note_group (0)
106         , _mouse_state(None)
107         , _pressed_button(0)
108         , _sort_needed (true)
109         , _optimization_iterator (_events.end())
110         , _list_editor (0)
111         , _no_sound_notes (false)
112         , _last_event_x (0)
113         , _last_event_y (0)
114         , _pre_enter_cursor (0)
115 {
116         _note_group->raise_to_top();
117         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118
119         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
120         connect_to_diskstream ();
121
122         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
123 }
124
125 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
126                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
127                                 TimeAxisViewItem::Visibility visibility)
128         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
129         , _force_channel(-1)
130         , _last_channel_selection(0xFFFF)
131         , _model_name(string())
132         , _custom_device_mode(string())
133         , _active_notes(0)
134         , _note_group(new ArdourCanvas::Group(*parent))
135         , _note_diff_command (0)
136         , _ghost_note(0)
137         , _drag_rect (0)
138         , _step_edit_cursor (0)
139         , _step_edit_cursor_width (1.0)
140         , _step_edit_cursor_position (0.0)
141         , _channel_selection_scoped_note (0)
142         , _temporary_note_group (0)
143         , _mouse_state(None)
144         , _pressed_button(0)
145         , _sort_needed (true)
146         , _optimization_iterator (_events.end())
147         , _list_editor (0)
148         , _no_sound_notes (false)
149         , _last_event_x (0)
150         , _last_event_y (0)
151 {
152         _note_group->raise_to_top();
153         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
154
155         connect_to_diskstream ();
156
157         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
158 }
159
160 void
161 MidiRegionView::parameter_changed (std::string const & p)
162 {
163         if (p == "diplay-first-midi-bank-as-zero") {
164                 if (_enable_display) {
165                         redisplay_model();
166                 }
167         }
168 }
169
170 MidiRegionView::MidiRegionView (const MidiRegionView& other)
171         : sigc::trackable(other)
172         , RegionView (other)
173         , _force_channel(-1)
174         , _last_channel_selection(0xFFFF)
175         , _model_name(string())
176         , _custom_device_mode(string())
177         , _active_notes(0)
178         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
179         , _note_diff_command (0)
180         , _ghost_note(0)
181         , _drag_rect (0)
182         , _step_edit_cursor (0)
183         , _step_edit_cursor_width (1.0)
184         , _step_edit_cursor_position (0.0)
185         , _channel_selection_scoped_note (0)
186         , _temporary_note_group (0)
187         , _mouse_state(None)
188         , _pressed_button(0)
189         , _sort_needed (true)
190         , _optimization_iterator (_events.end())
191         , _list_editor (0)
192         , _no_sound_notes (false)
193         , _last_event_x (0)
194         , _last_event_y (0)
195 {
196         Gdk::Color c;
197         int r,g,b,a;
198
199         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
200         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
201
202         init (c, false);
203 }
204
205 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
206         : RegionView (other, boost::shared_ptr<Region> (region))
207         , _force_channel(-1)
208         , _last_channel_selection(0xFFFF)
209         , _model_name(string())
210         , _custom_device_mode(string())
211         , _active_notes(0)
212         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
213         , _note_diff_command (0)
214         , _ghost_note(0)
215         , _drag_rect (0)
216         , _step_edit_cursor (0)
217         , _step_edit_cursor_width (1.0)
218         , _step_edit_cursor_position (0.0)
219         , _channel_selection_scoped_note (0)
220         , _temporary_note_group (0)
221         , _mouse_state(None)
222         , _pressed_button(0)
223         , _sort_needed (true)
224         , _optimization_iterator (_events.end())
225         , _list_editor (0)
226         , _no_sound_notes (false)
227         , _last_event_x (0)
228         , _last_event_y (0)
229 {
230         Gdk::Color c;
231         int r,g,b,a;
232
233         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
234         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
235
236         init (c, true);
237 }
238
239 void
240 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
241 {
242         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
243
244         CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
245                                                          ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
246                                                          gui_context());
247
248         if (wfd) {
249                 midi_region()->midi_source(0)->load_model();
250         }
251
252         _model = midi_region()->midi_source(0)->model();
253         _enable_display = false;
254
255         RegionView::init (basic_color, false);
256
257         compute_colors (basic_color);
258
259         set_height (trackview.current_height());
260
261         region_muted ();
262         region_sync_changed ();
263         region_resized (ARDOUR::bounds_change);
264         region_locked ();
265
266         reset_width_dependent_items (_pixel_width);
267
268         set_colors ();
269
270         _enable_display = true;
271         if (_model) {
272                 if (wfd) {
273                         display_model (_model);
274                 }
275         }
276
277         group->raise_to_top();
278         group->signal_event().connect(
279                 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
280
281         midi_view()->signal_channel_mode_changed().connect(
282                 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
283
284         midi_view()->signal_midi_patch_settings_changed().connect(
285                 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
286
287         trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
288                                                ui_bind(&MidiRegionView::snap_changed, this),
289                                                gui_context());
290
291         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
292         connect_to_diskstream ();
293
294         SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
295 }
296
297 void
298 MidiRegionView::connect_to_diskstream ()
299 {
300         midi_view()->midi_track()->DataRecorded.connect(
301                 *this, invalidator(*this),
302                 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
303                 gui_context());
304 }
305
306 bool
307 MidiRegionView::canvas_event(GdkEvent* ev)
308 {
309         switch (ev->type) {
310         case GDK_ENTER_NOTIFY:
311         case GDK_LEAVE_NOTIFY:
312                 _last_event_x = ev->crossing.x;
313                 _last_event_y = ev->crossing.y;
314                 break;
315         case GDK_MOTION_NOTIFY:
316                 _last_event_x = ev->motion.x;
317                 _last_event_y = ev->motion.y;
318                 break;
319         default:
320                 break;
321         }
322
323         if (!trackview.editor().internal_editing()) {
324                 return false;
325         }
326
327         switch (ev->type) {
328         case GDK_SCROLL:
329                 return scroll (&ev->scroll);
330
331         case GDK_KEY_PRESS:
332                 return key_press (&ev->key);
333
334         case GDK_KEY_RELEASE:
335                 return key_release (&ev->key);
336
337         case GDK_BUTTON_PRESS:
338                 return button_press (&ev->button);
339
340         case GDK_2BUTTON_PRESS:
341                 return true;
342
343         case GDK_BUTTON_RELEASE:
344                 return button_release (&ev->button);
345
346         case GDK_ENTER_NOTIFY:
347                 return enter_notify (&ev->crossing);
348
349         case GDK_LEAVE_NOTIFY:
350                 return leave_notify (&ev->crossing);
351
352         case GDK_MOTION_NOTIFY:
353                 return motion (&ev->motion);
354
355         default:
356                 break;
357         }
358
359         return false;
360 }
361
362 void
363 MidiRegionView::remove_ghost_note ()
364 {
365         delete _ghost_note;
366         _ghost_note = 0;
367 }
368
369 bool
370 MidiRegionView::enter_notify (GdkEventCrossing* ev)
371 {
372         trackview.editor().MouseModeChanged.connect (
373                 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
374                 );
375
376         if (trackview.editor().current_mouse_mode() == MouseRange) {
377                 create_ghost_note (ev->x, ev->y);
378         }
379
380         if (!trackview.editor().internal_editing()) {
381                 Keyboard::magic_widget_drop_focus();
382         } else {
383                 Keyboard::magic_widget_grab_focus();
384                 group->grab_focus();
385         }
386
387         return false;
388 }
389
390 bool
391 MidiRegionView::leave_notify (GdkEventCrossing* ev)
392 {
393         _mouse_mode_connection.disconnect ();
394
395         trackview.editor().verbose_cursor()->hide ();
396         remove_ghost_note ();
397
398         return false;
399 }
400
401 void
402 MidiRegionView::mouse_mode_changed ()
403 {
404         if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
405                 create_ghost_note (_last_event_x, _last_event_y);
406         } else {
407                 remove_ghost_note ();
408                 trackview.editor().verbose_cursor()->hide ();
409         }
410
411         if (!trackview.editor().internal_editing()) {
412                 Keyboard::magic_widget_drop_focus();
413         } else {
414                 Keyboard::magic_widget_grab_focus();
415                 group->grab_focus();
416         }
417 }
418
419 bool
420 MidiRegionView::button_press (GdkEventButton* ev)
421 {
422         if (ev->button != 1) {
423                 return false;
424         }
425
426         _last_x = ev->x;
427         _last_y = ev->y;
428
429         group->w2i (_last_x, _last_y);
430
431         if (_mouse_state != SelectTouchDragging) {
432
433                 _pressed_button = ev->button;
434                 _mouse_state = Pressed;
435
436                 return true;
437         }
438
439         _pressed_button = ev->button;
440
441         return true;
442 }
443
444 bool
445 MidiRegionView::button_release (GdkEventButton* ev)
446 {
447         double event_x, event_y;
448
449         if (ev->button != 1) {
450                 return false;
451         }
452
453         event_x = ev->x;
454         event_y = ev->y;
455
456         group->w2i(event_x, event_y);
457         group->ungrab(ev->time);
458
459         switch (_mouse_state) {
460         case Pressed: // Clicked
461
462                 switch (trackview.editor().current_mouse_mode()) {
463                 case MouseObject:
464                 case MouseTimeFX:
465                         {
466                                 clear_selection();
467
468                                 if (Keyboard::is_insert_note_event(ev)) {
469
470                                         double event_x, event_y;
471
472                                         event_x = ev->x;
473                                         event_y = ev->y;
474                                         group->w2i(event_x, event_y);
475
476                                         bool success;
477                                         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
478
479                                         if (!success) {
480                                                 beats = 1;
481                                         }
482
483                                         create_note_at (event_x, event_y, beats, true, true);
484                                 }
485
486                                 break;
487                         }
488                 case MouseRange:
489                         {
490                                 bool success;
491                                 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
492
493                                 if (!success) {
494                                         beats = 1;
495                                 }
496
497                                 create_note_at (event_x, event_y, beats, true, true);
498
499                                 break;
500                         }
501                 default:
502                         break;
503                 }
504
505                 _mouse_state = None;
506                 break;
507
508         case SelectRectDragging: // Select drag done
509
510                 _mouse_state = None;
511                 delete _drag_rect;
512                 _drag_rect = 0;
513                 break;
514
515         case AddDragging: // Add drag done
516
517                 _mouse_state = None;
518
519                 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
520
521                         if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
522
523                                 const double x  = _drag_rect->property_x1();
524                                 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
525
526                                 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
527                         }
528                 }
529
530                 delete _drag_rect;
531                 _drag_rect = 0;
532
533                 create_ghost_note (ev->x, ev->y);
534
535         default:
536                 break;
537         }
538
539         return false;
540 }
541
542 bool
543 MidiRegionView::motion (GdkEventMotion* ev)
544 {
545         double event_x, event_y;
546         framepos_t event_frame = 0;
547
548         event_x = ev->x;
549         event_y = ev->y;
550         group->w2i(event_x, event_y);
551
552         PublicEditor& editor = trackview.editor ();
553         
554         // convert event_x to global frame
555         framecnt_t grid_frames;
556         event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
557
558         if (!_ghost_note && editor.current_mouse_mode() != MouseRange
559             && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
560             && _mouse_state != AddDragging) {
561
562                 create_ghost_note (ev->x, ev->y);
563         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
564                    && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
565
566                 update_ghost_note (ev->x, ev->y);
567         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
568
569                 delete _ghost_note;
570                 _ghost_note = 0;
571
572                 editor.verbose_cursor()->hide ();
573         } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
574                 update_ghost_note (ev->x, ev->y);
575         }
576
577         /* any motion immediately hides velocity text that may have been visible */
578
579         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
580                 (*i)->hide_velocity ();
581         }
582
583         switch (_mouse_state) {
584         case Pressed: // Maybe start a drag, if we've moved a bit
585
586                 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
587                         /* no appreciable movement since the button was pressed */
588                         return false;
589                 }
590
591                 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
592                     && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
593                         // Select drag start
594
595                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
596                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
597
598                         _last_x = event_x;
599                         _last_y = event_y;
600                         _drag_start_x = event_x;
601                         _drag_start_y = event_y;
602
603                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
604                         _drag_rect->property_x1() = event_x;
605                         _drag_rect->property_y1() = event_y;
606                         _drag_rect->property_x2() = event_x;
607                         _drag_rect->property_y2() = event_y;
608                         _drag_rect->property_outline_what() = 0xFF;
609                         _drag_rect->property_outline_color_rgba()
610                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
611                         _drag_rect->property_fill_color_rgba()
612                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
613
614                         _mouse_state = SelectRectDragging;
615                         return true;
616
617                 } else if (editor.internal_editing()) {
618                         // Add note drag start
619
620                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
621                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
622
623                         _last_x = event_x;
624                         _last_y = event_y;
625                         _drag_start_x = event_x;
626                         _drag_start_y = event_y;
627
628                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
629                         _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
630
631                         _drag_rect->property_y1() = midi_stream_view()->note_to_y(
632                                 midi_stream_view()->y_to_note(event_y));
633                         _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
634                         
635                         _drag_rect->property_y2() = _drag_rect->property_y1()
636                                 + floor(midi_stream_view()->note_height());
637                         _drag_rect->property_outline_what() = 0xFF;
638                         _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
639                         _drag_rect->property_fill_color_rgba()    = 0xFFFFFF66;
640
641                         _mouse_state = AddDragging;
642                         
643                         delete _ghost_note;
644                         _ghost_note = 0;
645
646                         editor.verbose_cursor()->hide ();
647
648                         return true;
649                 }
650
651                 return false;
652
653         case SelectRectDragging: // Select drag motion
654         case AddDragging: // Add note drag motion
655
656                 if (ev->is_hint) {
657                         int t_x;
658                         int t_y;
659                         GdkModifierType state;
660                         gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
661                         event_x = t_x;
662                         event_y = t_y;
663                 }
664
665                 if (_mouse_state == AddDragging) {
666                         event_x = editor.frame_to_pixel(event_frame);
667
668                         if (editor.snap_mode() == SnapNormal) {
669                                 /* event_frame will have been snapped to the start of the note we are under;
670                                    it's more intuitive if we use the end of that note here
671                                 */
672                                 event_x = editor.frame_to_pixel (event_frame + grid_frames);
673                         } else {
674                                 event_x = editor.frame_to_pixel (event_frame);
675                         }
676                         
677                 }
678
679                 if (_drag_rect) {
680
681                         if (event_x > _drag_start_x) {
682                                 _drag_rect->property_x2() = event_x;
683                         }
684                         else {
685                                 _drag_rect->property_x1() = event_x;
686                         }
687                 }
688
689                 if (_drag_rect && _mouse_state == SelectRectDragging) {
690
691                         if (event_y > _drag_start_y) {
692                                 _drag_rect->property_y2() = event_y;
693                         }
694                         else {
695                                 _drag_rect->property_y1() = event_y;
696                         }
697
698                         update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y, Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
699                 }
700
701                 _last_x = event_x;
702                 _last_y = event_y;
703
704         case SelectTouchDragging:
705                 return false;
706
707         default:
708                 break;
709         }
710
711         return false;
712 }
713
714
715 bool
716 MidiRegionView::scroll (GdkEventScroll* ev)
717 {
718         if (_selection.empty()) {
719                 return false;
720         }
721
722         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
723                 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
724                    it still works for zoom.
725                 */
726                 return false;
727         }
728
729         trackview.editor().verbose_cursor()->hide ();
730
731         bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
732
733         if (ev->direction == GDK_SCROLL_UP) {
734                 change_velocities (true, fine, false);
735         } else if (ev->direction == GDK_SCROLL_DOWN) {
736                 change_velocities (false, fine, false);
737         }
738         return true;
739 }
740
741 bool
742 MidiRegionView::key_press (GdkEventKey* ev)
743 {
744         /* since GTK bindings are generally activated on press, and since
745            detectable auto-repeat is the name of the game and only sends
746            repeated presses, carry out key actions at key press, not release.
747         */
748
749         bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
750         
751         if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
752                 _mouse_state = SelectTouchDragging;
753                 return true;
754
755         } else if (ev->keyval == GDK_Escape && unmodified) {
756                 clear_selection();
757                 _mouse_state = None;
758
759         } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
760
761                 bool start = (ev->keyval == GDK_comma);
762                 bool end = (ev->keyval == GDK_period);
763                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
764                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
765
766                 change_note_lengths (fine, shorter, 0.0, start, end);
767
768                 return true;
769
770         } else if (ev->keyval == GDK_Delete && unmodified) {
771
772                 delete_selection();
773                 return true;
774
775         } else if (ev->keyval == GDK_Tab) {
776
777                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
778                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
779                 } else {
780                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
781                 }
782                 return true;
783
784         } else if (ev->keyval == GDK_ISO_Left_Tab) {
785
786                 /* Shift-TAB generates ISO Left Tab, for some reason */
787
788                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
789                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
790                 } else {
791                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
792                 }
793                 return true;
794
795
796
797         } else if (ev->keyval == GDK_Up) {
798
799                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
800                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
801
802                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
803                         change_velocities (true, fine, allow_smush);
804                 } else {
805                         transpose (true, fine, allow_smush);
806                 }
807                 return true;
808
809         } else if (ev->keyval == GDK_Down) {
810
811                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
812                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
813
814                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
815                         change_velocities (false, fine, allow_smush);
816                 } else {
817                         transpose (false, fine, allow_smush);
818                 }
819                 return true;
820
821         } else if (ev->keyval == GDK_Left && unmodified) {
822
823                 nudge_notes (false);
824                 return true;
825
826         } else if (ev->keyval == GDK_Right && unmodified) {
827
828                 nudge_notes (true);
829                 return true;
830
831         } else if (ev->keyval == GDK_c && unmodified) {
832                 channel_edit ();
833                 return true;
834         }
835
836         return false;
837 }
838
839 bool
840 MidiRegionView::key_release (GdkEventKey* ev)
841 {
842         if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
843                 _mouse_state = None;
844                 return true;
845         }
846         return false;
847 }
848
849 void
850 MidiRegionView::channel_edit ()
851 {
852         bool first = true;
853         uint8_t current_channel;
854
855         if (_selection.empty()) {
856                 return;
857         }
858         
859         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
860                 if (first) {
861                         current_channel = (*i)->note()->channel ();
862                         first = false;
863                 }
864         }
865
866         MidiChannelDialog channel_dialog (current_channel);
867         int ret = channel_dialog.run ();
868
869         switch (ret) {
870         case Gtk::RESPONSE_OK:
871                 break;
872         default:
873                 return;
874         }
875
876         uint8_t new_channel = channel_dialog.active_channel ();
877
878         start_note_diff_command (_("channel edit"));
879
880         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
881                 Selection::iterator next = i;
882                 ++next;
883                 change_note_channel (*i, new_channel);
884                 i = next;
885         }
886
887         apply_diff ();
888 }
889
890 void
891 MidiRegionView::show_list_editor ()
892 {
893         if (!_list_editor) {
894                 _list_editor = new MidiListEditor (trackview.session(), midi_region());
895         }
896         _list_editor->present ();
897 }
898
899 /** Add a note to the model, and the view, at a canvas (click) coordinate.
900  * \param x horizontal position in pixels
901  * \param y vertical position in pixels
902  * \param length duration of the note in beats, which will be snapped to the grid
903  * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
904  * \param snap_x true to snap x to the grid, otherwise false.
905  */
906 void
907 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
908 {
909         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
910         MidiStreamView* const view = mtv->midi_view();
911
912         double note = view->y_to_note(y);
913
914         assert(note >= 0.0);
915         assert(note <= 127.0);
916
917         // Start of note in frames relative to region start
918         framepos_t start_frames = trackview.editor().pixel_to_frame (x);
919         if (snap_x) {
920                 framecnt_t grid_frames;
921                 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
922         }
923         assert(start_frames >= 0);
924
925         // Snap length
926         length = region_frames_to_region_beats(
927                 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
928
929         assert (length != 0);
930
931         if (sh) {
932                 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
933         }
934
935         const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
936                                                                   region_frames_to_region_beats(start_frames + _region->start()), length,
937                                                                   (uint8_t)note, 0x40));
938
939         if (_model->contains (new_note)) {
940                 return;
941         }
942
943         view->update_note_range(new_note->note());
944
945         MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
946         cmd->add (new_note);
947         _model->apply_command(*trackview.session(), cmd);
948
949         play_midi_note (new_note);
950 }
951
952 void
953 MidiRegionView::clear_events()
954 {
955         clear_selection();
956
957         MidiGhostRegion* gr;
958         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
959                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
960                         gr->clear_events();
961                 }
962         }
963
964         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
965                 delete *i;
966         }
967
968         _events.clear();
969         _patch_changes.clear();
970         _sys_exes.clear();
971         _optimization_iterator = _events.end();
972 }
973
974 void
975 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
976 {
977         _model = model;
978
979         content_connection.disconnect ();
980         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
981
982         clear_events ();
983
984         if (_enable_display) {
985                 redisplay_model();
986         }
987 }
988
989 void
990 MidiRegionView::start_note_diff_command (string name)
991 {
992         if (!_note_diff_command) {
993                 _note_diff_command = _model->new_note_diff_command (name);
994         }
995 }
996
997 void
998 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
999 {
1000         if (_note_diff_command) {
1001                 _note_diff_command->add (note);
1002         }
1003         if (selected) {
1004                 _marked_for_selection.insert(note);
1005         }
1006         if (show_velocity) {
1007                 _marked_for_velocity.insert(note);
1008         }
1009 }
1010
1011 void
1012 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1013 {
1014         if (_note_diff_command && ev->note()) {
1015                 _note_diff_command->remove(ev->note());
1016         }
1017 }
1018
1019 void
1020 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1021                                       MidiModel::NoteDiffCommand::Property property,
1022                                       uint8_t val)
1023 {
1024         if (_note_diff_command) {
1025                 _note_diff_command->change (ev->note(), property, val);
1026         }
1027 }
1028
1029 void
1030 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1031                                       MidiModel::NoteDiffCommand::Property property,
1032                                       Evoral::MusicalTime val)
1033 {
1034         if (_note_diff_command) {
1035                 _note_diff_command->change (ev->note(), property, val);
1036         }
1037 }
1038
1039 void
1040 MidiRegionView::apply_diff (bool as_subcommand)
1041 {
1042         bool add_or_remove;
1043
1044         if (!_note_diff_command) {
1045                 return;
1046         }
1047
1048         if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1049                 // Mark all selected notes for selection when model reloads
1050                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1051                         _marked_for_selection.insert((*i)->note());
1052                 }
1053         }
1054
1055         if (as_subcommand) {
1056                 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1057         } else {
1058                 _model->apply_command (*trackview.session(), _note_diff_command);
1059         }
1060
1061         _note_diff_command = 0;
1062         midi_view()->midi_track()->playlist_modified();
1063
1064         if (add_or_remove) {
1065                 _marked_for_selection.clear();
1066         }
1067
1068         _marked_for_velocity.clear();
1069 }
1070
1071 void
1072 MidiRegionView::abort_command()
1073 {
1074         delete _note_diff_command;
1075         _note_diff_command = 0;
1076         clear_selection();
1077 }
1078
1079 CanvasNoteEvent*
1080 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1081 {
1082         if (_optimization_iterator != _events.end()) {
1083                 ++_optimization_iterator;
1084         }
1085
1086         if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1087                 return *_optimization_iterator;
1088         }
1089
1090         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1091                 if ((*_optimization_iterator)->note() == note) {
1092                         return *_optimization_iterator;
1093                 }
1094         }
1095
1096         return 0;
1097 }
1098
1099 void
1100 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1101 {
1102         MidiModel::Notes notes;
1103         _model->get_notes (notes, op, val, chan_mask);
1104
1105         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1106                 CanvasNoteEvent* cne = find_canvas_note (*n);
1107                 if (cne) {
1108                         e.push_back (cne);
1109                 }
1110         }
1111 }
1112
1113 void
1114 MidiRegionView::redisplay_model()
1115 {
1116         // Don't redisplay the model if we're currently recording and displaying that
1117         if (_active_notes) {
1118                 return;
1119         }
1120
1121         if (!_model) {
1122                 return;
1123         }
1124
1125         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1126                 (*i)->invalidate ();
1127         }
1128
1129         MidiModel::ReadLock lock(_model->read_lock());
1130
1131         MidiModel::Notes& notes (_model->notes());
1132         _optimization_iterator = _events.begin();
1133
1134         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1135
1136                 boost::shared_ptr<NoteType> note (*n);
1137                 CanvasNoteEvent* cne;
1138                 bool visible;
1139
1140                 if (note_in_region_range (note, visible)) {
1141
1142                         if ((cne = find_canvas_note (note)) != 0) {
1143
1144                                 cne->validate ();
1145
1146                                 CanvasNote* cn;
1147                                 CanvasHit* ch;
1148
1149                                 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1150                                         update_note (cn);
1151                                 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1152                                         update_hit (ch);
1153                                 }
1154
1155                                 if (visible) {
1156                                         cne->show ();
1157                                 } else {
1158                                         cne->hide ();
1159                                 }
1160
1161                         } else {
1162
1163                                 add_note (note, visible);
1164                         }
1165
1166                 } else {
1167
1168                         if ((cne = find_canvas_note (note)) != 0) {
1169                                 cne->validate ();
1170                                 cne->hide ();
1171                         }
1172                 }
1173         }
1174
1175
1176         /* remove note items that are no longer valid */
1177
1178         for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1179                 if (!(*i)->valid ()) {
1180                         delete *i;
1181                         i = _events.erase (i);
1182                 } else {
1183                         ++i;
1184                 }
1185         }
1186
1187         _patch_changes.clear();
1188         _sys_exes.clear();
1189
1190         display_sysexes();
1191         display_patch_changes ();
1192
1193         _marked_for_selection.clear ();
1194         _marked_for_velocity.clear ();
1195
1196         /* we may have caused _events to contain things out of order (e.g. if a note
1197            moved earlier or later). we don't generally need them in time order, but
1198            make a note that a sort is required for those cases that require it.
1199         */
1200
1201         _sort_needed = true;
1202 }
1203
1204 void
1205 MidiRegionView::display_patch_changes ()
1206 {
1207         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1208         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1209
1210         for (uint8_t i = 0; i < 16; ++i) {
1211                 if (chn_mask & (1<<i)) {
1212                         display_patch_changes_on_channel (i);
1213                 }
1214                 /* TODO gray-out patch instad of not displaying it */
1215         }
1216 }
1217
1218 void
1219 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1220 {
1221         for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1222
1223                 if ((*i)->channel() != channel) {
1224                         continue;
1225                 }
1226
1227                 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1228
1229                 boost::shared_ptr<MIDI::Name::Patch> patch =
1230                         MIDI::Name::MidiPatchManager::instance().find_patch(
1231                                 _model_name, _custom_device_mode, channel, patch_key);
1232
1233                 if (patch != 0) {
1234                         add_canvas_patch_change (*i, patch->name());
1235                 } else {
1236                         char buf[16];
1237                         /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1238                         snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1239                         add_canvas_patch_change (*i, buf);
1240                 }
1241         }
1242 }
1243
1244 void
1245 MidiRegionView::display_sysexes()
1246 {
1247         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1248                 Evoral::MusicalTime time = (*i)->time();
1249                 assert(time >= 0);
1250
1251                 ostringstream str;
1252                 str << hex;
1253                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1254                         str << int((*i)->buffer()[b]);
1255                         if (b != (*i)->size() -1) {
1256                                 str << " ";
1257                         }
1258                 }
1259                 string text = str.str();
1260
1261                 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1262
1263                 double height = midi_stream_view()->contents_height();
1264
1265                 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1266                         new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1267
1268                 // Show unless patch change is beyond the region bounds
1269                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1270                         sysex->hide();
1271                 } else {
1272                         sysex->show();
1273                 }
1274
1275                 _sys_exes.push_back(sysex);
1276         }
1277 }
1278
1279
1280 MidiRegionView::~MidiRegionView ()
1281 {
1282         in_destructor = true;
1283
1284         trackview.editor().verbose_cursor()->hide ();
1285
1286         note_delete_connection.disconnect ();
1287
1288         delete _list_editor;
1289
1290         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1291
1292         if (_active_notes) {
1293                 end_write();
1294         }
1295
1296         _selection.clear();
1297         clear_events();
1298
1299         delete _note_group;
1300         delete _note_diff_command;
1301         delete _step_edit_cursor;
1302         delete _temporary_note_group;
1303 }
1304
1305 void
1306 MidiRegionView::region_resized (const PropertyChange& what_changed)
1307 {
1308         RegionView::region_resized(what_changed);
1309
1310         if (what_changed.contains (ARDOUR::Properties::position)) {
1311                 set_duration(_region->length(), 0);
1312                 if (_enable_display) {
1313                         redisplay_model();
1314                 }
1315         }
1316 }
1317
1318 void
1319 MidiRegionView::reset_width_dependent_items (double pixel_width)
1320 {
1321         RegionView::reset_width_dependent_items(pixel_width);
1322         assert(_pixel_width == pixel_width);
1323
1324         if (_enable_display) {
1325                 redisplay_model();
1326         }
1327
1328         move_step_edit_cursor (_step_edit_cursor_position);
1329         set_step_edit_cursor_width (_step_edit_cursor_width);
1330 }
1331
1332 void
1333 MidiRegionView::set_height (double height)
1334 {
1335         static const double FUDGE = 2.0;
1336         const double old_height = _height;
1337         RegionView::set_height(height);
1338         _height = height - FUDGE;
1339
1340         apply_note_range(midi_stream_view()->lowest_note(),
1341                          midi_stream_view()->highest_note(),
1342                          height != old_height + FUDGE);
1343
1344         if (name_pixbuf) {
1345                 name_pixbuf->raise_to_top();
1346         }
1347
1348         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1349                 (*x)->set_height (midi_stream_view()->contents_height());
1350         }
1351
1352         if (_step_edit_cursor) {
1353                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1354         }
1355 }
1356
1357
1358 /** Apply the current note range from the stream view
1359  * by repositioning/hiding notes as necessary
1360  */
1361 void
1362 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1363 {
1364         if (!_enable_display) {
1365                 return;
1366         }
1367
1368         if (!force && _current_range_min == min && _current_range_max == max) {
1369                 return;
1370         }
1371
1372         _current_range_min = min;
1373         _current_range_max = max;
1374
1375         for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1376                 CanvasNoteEvent* event = *i;
1377                 boost::shared_ptr<NoteType> note (event->note());
1378
1379                 if (note->note() < _current_range_min ||
1380                     note->note() > _current_range_max) {
1381                         event->hide();
1382                 } else {
1383                         event->show();
1384                 }
1385
1386                 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1387
1388                         const double y1 = midi_stream_view()->note_to_y(note->note());
1389                         const double y2 = y1 + floor(midi_stream_view()->note_height());
1390
1391                         cnote->property_y1() = y1;
1392                         cnote->property_y2() = y2;
1393
1394                 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1395
1396                         const double diamond_size = update_hit (chit);
1397
1398                         chit->set_height (diamond_size);
1399                 }
1400         }
1401 }
1402
1403 GhostRegion*
1404 MidiRegionView::add_ghost (TimeAxisView& tv)
1405 {
1406         CanvasNote* note;
1407
1408         double unit_position = _region->position () / samples_per_unit;
1409         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1410         MidiGhostRegion* ghost;
1411
1412         if (mtv && mtv->midi_view()) {
1413                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1414                    to allow having midi notes on top of note lines and waveforms.
1415                 */
1416                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1417         } else {
1418                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1419         }
1420
1421         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1422                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1423                         ghost->add_note(note);
1424                 }
1425         }
1426
1427         ghost->set_height ();
1428         ghost->set_duration (_region->length() / samples_per_unit);
1429         ghosts.push_back (ghost);
1430
1431         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1432
1433         return ghost;
1434 }
1435
1436
1437 /** Begin tracking note state for successive calls to add_event
1438  */
1439 void
1440 MidiRegionView::begin_write()
1441 {
1442         assert(!_active_notes);
1443         _active_notes = new CanvasNote*[128];
1444         for (unsigned i=0; i < 128; ++i) {
1445                 _active_notes[i] = 0;
1446         }
1447 }
1448
1449
1450 /** Destroy note state for add_event
1451  */
1452 void
1453 MidiRegionView::end_write()
1454 {
1455         delete[] _active_notes;
1456         _active_notes = 0;
1457         _marked_for_selection.clear();
1458         _marked_for_velocity.clear();
1459 }
1460
1461
1462 /** Resolve an active MIDI note (while recording).
1463  */
1464 void
1465 MidiRegionView::resolve_note(uint8_t note, double end_time)
1466 {
1467         if (midi_view()->note_mode() != Sustained) {
1468                 return;
1469         }
1470
1471         if (_active_notes && _active_notes[note]) {
1472
1473                 /* XXX is end_time really region-centric? I think so, because
1474                    this is a new region that we're recording, so source zero is
1475                    the same as region zero
1476                 */
1477                 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1478
1479                 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1480                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1481                 _active_notes[note] = 0;
1482         }
1483 }
1484
1485
1486 /** Extend active notes to rightmost edge of region (if length is changed)
1487  */
1488 void
1489 MidiRegionView::extend_active_notes()
1490 {
1491         if (!_active_notes) {
1492                 return;
1493         }
1494
1495         for (unsigned i=0; i < 128; ++i) {
1496                 if (_active_notes[i]) {
1497                         _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1498                 }
1499         }
1500 }
1501
1502
1503 void
1504 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1505 {
1506         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1507                 return;
1508         }
1509
1510         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1511
1512         if (!route_ui || !route_ui->midi_track()) {
1513                 return;
1514         }
1515
1516         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1517         np->add (note);
1518         np->play ();
1519 }
1520
1521 void
1522 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1523 {
1524         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1525                 return;
1526         }
1527
1528         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1529
1530         if (!route_ui || !route_ui->midi_track()) {
1531                 return;
1532         }
1533
1534         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1535
1536         for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1537                 np->add (*n);
1538         }
1539
1540         np->play ();
1541 }
1542
1543
1544 bool
1545 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1546 {
1547         const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1548         bool outside = (note_start_frames  < 0) || (note_start_frames > _region->last_frame());
1549
1550         visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1551                 (note->note() <= midi_stream_view()->highest_note());
1552
1553         return !outside;
1554 }
1555
1556 /** Update a canvas note's size from its model note.
1557  *  @param ev Canvas note to update.
1558  *  @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1559  */
1560 void
1561 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1562 {
1563         boost::shared_ptr<NoteType> note = ev->note();
1564
1565         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1566         const double y1 = midi_stream_view()->note_to_y(note->note());
1567
1568         ev->property_x1() = x;
1569         ev->property_y1() = y1;
1570
1571         /* trim note display to not overlap the end of its region */
1572
1573         if (note->length() > 0) {
1574                 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1575                 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1576         } else {
1577                 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1578         }
1579
1580         ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1581
1582         if (note->length() == 0) {
1583                 if (_active_notes) {
1584                         assert(note->note() < 128);
1585                         // If this note is already active there's a stuck note,
1586                         // finish the old note rectangle
1587                         if (_active_notes[note->note()]) {
1588                                 CanvasNote* const old_rect = _active_notes[note->note()];
1589                                 boost::shared_ptr<NoteType> old_note = old_rect->note();
1590                                 old_rect->property_x2() = x;
1591                                 old_rect->property_outline_what() = (guint32) 0xF;
1592                         }
1593                         _active_notes[note->note()] = ev;
1594                 }
1595                 /* outline all but right edge */
1596                 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1597         } else {
1598                 /* outline all edges */
1599                 ev->property_outline_what() = (guint32) 0xF;
1600         }
1601
1602         if (update_ghost_regions) {
1603                 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1604                         MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1605                         if (gr) {
1606                                 gr->update_note (ev);
1607                         }
1608                 }
1609         }
1610 }
1611
1612 double
1613 MidiRegionView::update_hit (CanvasHit* ev)
1614 {
1615         boost::shared_ptr<NoteType> note = ev->note();
1616
1617         const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1618         const double x = trackview.editor().frame_to_pixel(note_start_frames);
1619         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1620         const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1621
1622         ev->move_to (x, y);
1623
1624         return diamond_size;
1625 }
1626
1627 /** Add a MIDI note to the view (with length).
1628  *
1629  * If in sustained mode, notes with length 0 will be considered active
1630  * notes, and resolve_note should be called when the corresponding note off
1631  * event arrives, to properly display the note.
1632  */
1633 void
1634 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1635 {
1636         CanvasNoteEvent* event = 0;
1637
1638         assert(note->time() >= 0);
1639         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1640
1641         //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1642
1643         if (midi_view()->note_mode() == Sustained) {
1644
1645                 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1646
1647                 update_note (ev_rect);
1648
1649                 event = ev_rect;
1650
1651                 MidiGhostRegion* gr;
1652
1653                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1654                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1655                                 gr->add_note(ev_rect);
1656                         }
1657                 }
1658
1659         } else if (midi_view()->note_mode() == Percussive) {
1660
1661                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1662
1663                 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1664
1665                 update_hit (ev_diamond);
1666
1667                 event = ev_diamond;
1668
1669         } else {
1670                 event = 0;
1671         }
1672
1673         if (event) {
1674                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1675                         note_selected(event, true);
1676                 }
1677
1678                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1679                         event->show_velocity();
1680                 }
1681
1682                 event->on_channel_selection_change(_last_channel_selection);
1683                 _events.push_back(event);
1684
1685                 if (visible) {
1686                         event->show();
1687                 } else {
1688                         event->hide ();
1689                 }
1690         }
1691
1692         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1693         MidiStreamView* const view = mtv->midi_view();
1694
1695         view->update_note_range (note->note());
1696 }
1697
1698 void
1699 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1700                                Evoral::MusicalTime pos, Evoral::MusicalTime len)
1701 {
1702         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1703
1704         /* potentially extend region to hold new note */
1705
1706         framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1707         framepos_t region_end = _region->last_frame();
1708
1709         if (end_frame > region_end) {
1710                 _region->set_length (end_frame - _region->position());
1711         }
1712
1713         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1714         MidiStreamView* const view = mtv->midi_view();
1715
1716         view->update_note_range(new_note->note());
1717
1718         _marked_for_selection.clear ();
1719         clear_selection ();
1720
1721         start_note_diff_command (_("step add"));
1722         note_diff_add_note (new_note, true, false);
1723         apply_diff();
1724
1725         // last_step_edit_note = new_note;
1726 }
1727
1728 void
1729 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1730 {
1731         change_note_lengths (false, false, beats, false, true);
1732 }
1733
1734 void
1735 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1736 {
1737         assert (patch->time() >= 0);
1738
1739         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1740
1741         double const height = midi_stream_view()->contents_height();
1742
1743         boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1744                 new CanvasPatchChange(*this, *_note_group,
1745                                       displaytext,
1746                                       height,
1747                                       x, 1.0,
1748                                       _model_name,
1749                                       _custom_device_mode,
1750                                       patch)
1751                           );
1752
1753         // Show unless patch change is beyond the region bounds
1754         if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1755                 patch_change->hide();
1756         } else {
1757                 patch_change->show();
1758         }
1759
1760         _patch_changes.push_back (patch_change);
1761 }
1762
1763 void
1764 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1765 {
1766         MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1767         while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1768                 ++i;
1769         }
1770
1771         if (i != _model->patch_changes().end()) {
1772                 key.msb = (*i)->bank_msb ();
1773                 key.lsb = (*i)->bank_lsb ();
1774                 key.program_number = (*i)->program ();
1775         } else {
1776                 key.msb = key.lsb = key.program_number = 0;
1777         }
1778
1779         assert (key.is_sane());
1780 }
1781
1782
1783 void
1784 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1785 {
1786         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1787
1788         if (pc.patch()->program() != new_patch.program_number) {
1789                 c->change_program (pc.patch (), new_patch.program_number);
1790         }
1791
1792         int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1793         if (pc.patch()->bank() != new_bank) {
1794                 c->change_bank (pc.patch (), new_bank);
1795         }
1796
1797         _model->apply_command (*trackview.session(), c);
1798
1799         _patch_changes.clear ();
1800         display_patch_changes ();
1801 }
1802
1803 void
1804 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1805 {
1806         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1807
1808         if (old_change->time() != new_change.time()) {
1809                 c->change_time (old_change, new_change.time());
1810         }
1811
1812         if (old_change->channel() != new_change.channel()) {
1813                 c->change_channel (old_change, new_change.channel());
1814         }
1815
1816         if (old_change->program() != new_change.program()) {
1817                 c->change_program (old_change, new_change.program());
1818         }
1819
1820         if (old_change->bank() != new_change.bank()) {
1821                 c->change_bank (old_change, new_change.bank());
1822         }
1823
1824         _model->apply_command (*trackview.session(), c);
1825
1826         _patch_changes.clear ();
1827         display_patch_changes ();
1828 }
1829
1830 /** Add a patch change to the region.
1831  *  @param t Time in frames relative to region position
1832  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1833  *  MidiTimeAxisView::get_channel_for_add())
1834  */
1835 void
1836 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1837 {
1838         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1839
1840         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1841         c->add (MidiModel::PatchChangePtr (
1842                         new Evoral::PatchChange<Evoral::MusicalTime> (
1843                                 absolute_frames_to_source_beats (_region->position() + t),
1844                                 mtv->get_channel_for_add(), patch.program(), patch.bank()
1845                                 )
1846                         )
1847                 );
1848
1849         _model->apply_command (*trackview.session(), c);
1850
1851         _patch_changes.clear ();
1852         display_patch_changes ();
1853 }
1854
1855 void
1856 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1857 {
1858         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1859         c->change_time (pc.patch (), t);
1860         _model->apply_command (*trackview.session(), c);
1861
1862         _patch_changes.clear ();
1863         display_patch_changes ();
1864 }
1865
1866 void
1867 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1868 {
1869         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1870         c->remove (pc->patch ());
1871         _model->apply_command (*trackview.session(), c);
1872
1873         _patch_changes.clear ();
1874         display_patch_changes ();
1875 }
1876
1877 void
1878 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1879 {
1880         if (patch.patch()->program() < 127) {
1881                 MIDI::Name::PatchPrimaryKey key;
1882                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1883                 key.program_number++;
1884                 change_patch_change (patch, key);
1885         }
1886 }
1887
1888 void
1889 MidiRegionView::next_patch (CanvasPatchChange& patch)
1890 {
1891         if (patch.patch()->program() > 0) {
1892                 MIDI::Name::PatchPrimaryKey key;
1893                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1894                 key.program_number--;
1895                 change_patch_change (patch, key);
1896         }
1897 }
1898
1899 void
1900 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1901 {
1902         if (patch.patch()->program() < 127) {
1903                 MIDI::Name::PatchPrimaryKey key;
1904                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1905                 if (key.lsb > 0) {
1906                         key.lsb--;
1907                         change_patch_change (patch, key);
1908                 } else {
1909                         if (key.msb > 0) {
1910                                 key.lsb = 127;
1911                                 key.msb--;
1912                                 change_patch_change (patch, key);
1913                         }
1914                 }
1915         }
1916 }
1917
1918 void
1919 MidiRegionView::next_bank (CanvasPatchChange& patch)
1920 {
1921         if (patch.patch()->program() > 0) {
1922                 MIDI::Name::PatchPrimaryKey key;
1923                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1924                 if (key.lsb < 127) {
1925                         key.lsb++;
1926                         change_patch_change (patch, key);
1927                 } else {
1928                         if (key.msb < 127) {
1929                                 key.lsb = 0;
1930                                 key.msb++;
1931                                 change_patch_change (patch, key);
1932                         }
1933                 }
1934         }
1935 }
1936
1937 void
1938 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1939 {
1940         if (_selection.empty()) {
1941                 return;
1942         }
1943
1944         _selection.erase (cne);
1945 }
1946
1947 void
1948 MidiRegionView::delete_selection()
1949 {
1950         if (_selection.empty()) {
1951                 return;
1952         }
1953
1954         start_note_diff_command (_("delete selection"));
1955
1956         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1957                 if ((*i)->selected()) {
1958                         _note_diff_command->remove((*i)->note());
1959                 }
1960         }
1961
1962         _selection.clear();
1963
1964         apply_diff ();
1965 }
1966
1967 void
1968 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1969 {
1970         start_note_diff_command (_("delete note"));
1971         _note_diff_command->remove (n);
1972         apply_diff ();
1973
1974         trackview.editor().verbose_cursor()->hide ();
1975 }
1976
1977 void
1978 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1979 {
1980         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1981                 if ((*i) != ev) {
1982                         Selection::iterator tmp = i;
1983                         ++tmp;
1984
1985                         (*i)->set_selected (false);
1986                         (*i)->hide_velocity ();
1987                         _selection.erase (i);
1988                         
1989                         i = tmp;
1990                 } else {
1991                         ++i;
1992                 }
1993         }
1994
1995         /* this does not change the status of this regionview w.r.t the editor
1996            selection.
1997         */
1998
1999         if (signal) {
2000                 SelectionCleared (this); /* EMIT SIGNAL */
2001         }
2002 }
2003
2004 void
2005 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2006 {
2007         clear_selection_except (ev);
2008
2009         /* don't bother with checking to see if we should remove this
2010            regionview from the editor selection, since we're about to add
2011            another note, and thus put/keep this regionview in the editor
2012            selection anyway.
2013         */
2014
2015         if (!ev->selected()) {
2016                 add_to_selection (ev);
2017         }
2018 }
2019
2020 void
2021 MidiRegionView::select_all_notes ()
2022 {
2023         clear_selection ();
2024
2025         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2026                 add_to_selection (*i);
2027         }
2028 }
2029
2030 void
2031 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2032 {
2033         uint8_t low_note = 127;
2034         uint8_t high_note = 0;
2035         MidiModel::Notes& notes (_model->notes());
2036         _optimization_iterator = _events.begin();
2037
2038         if (!add) {
2039                 clear_selection ();
2040         }
2041
2042         if (extend && _selection.empty()) {
2043                 extend = false;
2044         }
2045
2046         if (extend) {
2047
2048                 /* scan existing selection to get note range */
2049
2050                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2051                         if ((*i)->note()->note() < low_note) {
2052                                 low_note = (*i)->note()->note();
2053                         }
2054                         if ((*i)->note()->note() > high_note) {
2055                                 high_note = (*i)->note()->note();
2056                         }
2057                 }
2058
2059                 low_note = min (low_note, notenum);
2060                 high_note = max (high_note, notenum);
2061         }
2062
2063         _no_sound_notes = true;
2064
2065         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2066
2067                 boost::shared_ptr<NoteType> note (*n);
2068                 CanvasNoteEvent* cne;
2069                 bool select = false;
2070
2071                 if (((1 << note->channel()) & channel_mask) != 0) {
2072                         if (extend) {
2073                                 if ((note->note() >= low_note && note->note() <= high_note)) {
2074                                         select = true;
2075                                 }
2076                         } else if (note->note() == notenum) {
2077                                 select = true;
2078                         }
2079                 }
2080
2081                 if (select) {
2082                         if ((cne = find_canvas_note (note)) != 0) {
2083                                 // extend is false because we've taken care of it,
2084                                 // since it extends by time range, not pitch.
2085                                 note_selected (cne, add, false);
2086                         }
2087                 }
2088
2089                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2090
2091         }
2092
2093         _no_sound_notes = false;
2094 }
2095
2096 void
2097 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2098 {
2099         MidiModel::Notes& notes (_model->notes());
2100         _optimization_iterator = _events.begin();
2101
2102         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2103
2104                 boost::shared_ptr<NoteType> note (*n);
2105                 CanvasNoteEvent* cne;
2106
2107                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2108                         if ((cne = find_canvas_note (note)) != 0) {
2109                                 if (cne->selected()) {
2110                                         note_deselected (cne);
2111                                 } else {
2112                                         note_selected (cne, true, false);
2113                                 }
2114                         }
2115                 }
2116         }
2117 }
2118
2119 void
2120 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2121 {
2122         if (!add) {
2123                 clear_selection_except (ev);
2124                 if (!_selection.empty()) {
2125                         PublicEditor& editor (trackview.editor());
2126                         editor.get_selection().add (this);
2127                 }
2128         }
2129
2130         if (!extend) {
2131
2132                 if (!ev->selected()) {
2133                         add_to_selection (ev);
2134                 }
2135
2136         } else {
2137                 /* find end of latest note selected, select all between that and the start of "ev" */
2138
2139                 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2140                 Evoral::MusicalTime latest = 0;
2141
2142                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2143                         if ((*i)->note()->end_time() > latest) {
2144                                 latest = (*i)->note()->end_time();
2145                         }
2146                         if ((*i)->note()->time() < earliest) {
2147                                 earliest = (*i)->note()->time();
2148                         }
2149                 }
2150
2151                 if (ev->note()->end_time() > latest) {
2152                         latest = ev->note()->end_time();
2153                 }
2154
2155                 if (ev->note()->time() < earliest) {
2156                         earliest = ev->note()->time();
2157                 }
2158
2159                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2160
2161                         /* find notes entirely within OR spanning the earliest..latest range */
2162
2163                         if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2164                             ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2165                                 add_to_selection (*i);
2166                         }
2167
2168                 }
2169         }
2170 }
2171
2172 void
2173 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2174 {
2175         remove_from_selection (ev);
2176 }
2177
2178 void
2179 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2180 {
2181         if (x1 > x2) {
2182                 swap (x1, x2);
2183         }
2184
2185         if (y1 > y2) {
2186                 swap (y1, y2);
2187         }
2188
2189         // TODO: Make this faster by storing the last updated selection rect, and only
2190         // adjusting things that are in the area that appears/disappeared.
2191         // We probably need a tree to be able to find events in O(log(n)) time.
2192
2193         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2194
2195                 /* check if any corner of the note is inside the rect
2196
2197                    Notes:
2198                    1) this is computing "touched by", not "contained by" the rect.
2199                    2) this does not require that events be sorted in time.
2200                 */
2201
2202                 const double ix1 = (*i)->x1();
2203                 const double ix2 = (*i)->x2();
2204                 const double iy1 = (*i)->y1();
2205                 const double iy2 = (*i)->y2();
2206
2207                 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2208                     (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2209                     (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2210                     (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2211
2212                         // Inside rectangle
2213                         if (!(*i)->selected()) {
2214                                 add_to_selection (*i);
2215                         }
2216                 } else if ((*i)->selected() && !extend) {
2217                         // Not inside rectangle
2218                         remove_from_selection (*i);
2219                 }
2220         }
2221 }
2222
2223 void
2224 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2225 {
2226         Selection::iterator i = _selection.find (ev);
2227
2228         if (i != _selection.end()) {
2229                 _selection.erase (i);
2230         }
2231
2232         ev->set_selected (false);
2233         ev->hide_velocity ();
2234
2235         if (_selection.empty()) {
2236                 PublicEditor& editor (trackview.editor());
2237                 editor.get_selection().remove (this);
2238         }
2239 }
2240
2241 void
2242 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2243 {
2244         bool add_mrv_selection = false;
2245
2246         if (_selection.empty()) {
2247                 add_mrv_selection = true;
2248         }
2249
2250         if (_selection.insert (ev).second) {
2251                 ev->set_selected (true);
2252                 play_midi_note ((ev)->note());
2253         }
2254
2255         if (add_mrv_selection) {
2256                 PublicEditor& editor (trackview.editor());
2257                 editor.get_selection().add (this);
2258         }
2259 }
2260
2261 void
2262 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2263 {
2264         typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2265         PossibleChord to_play;
2266         Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2267
2268         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2269                 if ((*i)->note()->time() < earliest) {
2270                         earliest = (*i)->note()->time();
2271                 }
2272         }
2273
2274         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2275                 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2276                         to_play.push_back ((*i)->note());
2277                 }
2278                 (*i)->move_event(dx, dy);
2279         }
2280
2281         if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2282
2283                 if (to_play.size() > 1) {
2284
2285                         PossibleChord shifted;
2286
2287                         for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2288                                 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2289                                 moved_note->set_note (moved_note->note() + cumulative_dy);
2290                                 shifted.push_back (moved_note);
2291                         }
2292
2293                         play_midi_chord (shifted);
2294
2295                 } else if (!to_play.empty()) {
2296
2297                         boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2298                         moved_note->set_note (moved_note->note() + cumulative_dy);
2299                         play_midi_note (moved_note);
2300                 }
2301         }
2302 }
2303
2304 void
2305 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2306 {
2307         assert (!_selection.empty());
2308
2309         uint8_t lowest_note_in_selection  = 127;
2310         uint8_t highest_note_in_selection = 0;
2311         uint8_t highest_note_difference = 0;
2312
2313         // find highest and lowest notes first
2314
2315         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2316                 uint8_t pitch = (*i)->note()->note();
2317                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2318                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2319         }
2320
2321         /*
2322           cerr << "dnote: " << (int) dnote << endl;
2323           cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2324           << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2325           cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2326           << int(highest_note_in_selection) << endl;
2327           cerr << "selection size: " << _selection.size() << endl;
2328           cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2329         */
2330
2331         // Make sure the note pitch does not exceed the MIDI standard range
2332         if (highest_note_in_selection + dnote > 127) {
2333                 highest_note_difference = highest_note_in_selection - 127;
2334         }
2335
2336         start_note_diff_command (_("move notes"));
2337
2338         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2339
2340                 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2341
2342                 if (new_time < 0) {
2343                         continue;
2344                 }
2345
2346                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2347
2348                 uint8_t original_pitch = (*i)->note()->note();
2349                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2350
2351                 // keep notes in standard midi range
2352                 clamp_to_0_127(new_pitch);
2353
2354                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2355                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2356
2357                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2358         }
2359
2360         apply_diff();
2361
2362         // care about notes being moved beyond the upper/lower bounds on the canvas
2363         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2364             highest_note_in_selection > midi_stream_view()->highest_note()) {
2365                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2366         }
2367 }
2368
2369 framepos_t
2370 MidiRegionView::snap_pixel_to_frame(double x)
2371 {
2372         PublicEditor& editor (trackview.editor());
2373         return snap_frame_to_frame (editor.pixel_to_frame (x));
2374 }
2375
2376 double
2377 MidiRegionView::snap_to_pixel(double x)
2378 {
2379         return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2380 }
2381
2382 double
2383 MidiRegionView::get_position_pixels()
2384 {
2385         framepos_t region_frame = get_position();
2386         return trackview.editor().frame_to_pixel(region_frame);
2387 }
2388
2389 double
2390 MidiRegionView::get_end_position_pixels()
2391 {
2392         framepos_t frame = get_position() + get_duration ();
2393         return trackview.editor().frame_to_pixel(frame);
2394 }
2395
2396 framepos_t
2397 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2398 {
2399         /* the time converter will return the frame corresponding to `beats'
2400            relative to the start of the source. The start of the source
2401            is an implied position given by region->position - region->start
2402         */
2403         const framepos_t source_start = _region->position() - _region->start();
2404         return  source_start +  _source_relative_time_converter.to (beats);
2405 }
2406
2407 double
2408 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2409 {
2410         /* the `frames' argument needs to be converted into a frame count
2411            relative to the start of the source before being passed in to the
2412            converter.
2413         */
2414         const framepos_t source_start = _region->position() - _region->start();
2415         return  _source_relative_time_converter.from (frames - source_start);
2416 }
2417
2418 framepos_t
2419 MidiRegionView::region_beats_to_region_frames(double beats) const
2420 {
2421         return _region_relative_time_converter.to(beats);
2422 }
2423
2424 double
2425 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2426 {
2427         return _region_relative_time_converter.from(frames);
2428 }
2429
2430 void
2431 MidiRegionView::begin_resizing (bool /*at_front*/)
2432 {
2433         _resize_data.clear();
2434
2435         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2436                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2437
2438                 // only insert CanvasNotes into the map
2439                 if (note) {
2440                         NoteResizeData *resize_data = new NoteResizeData();
2441                         resize_data->canvas_note = note;
2442
2443                         // create a new SimpleRect from the note which will be the resize preview
2444                         SimpleRect *resize_rect = new SimpleRect(
2445                                 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2446
2447                         // calculate the colors: get the color settings
2448                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2449                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2450                                 128);
2451
2452                         // make the resize preview notes more transparent and bright
2453                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2454
2455                         // calculate color based on note velocity
2456                         resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2457                                 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2458                                 fill_color,
2459                                 0.85);
2460
2461                         resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2462                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2463
2464                         resize_data->resize_rect = resize_rect;
2465                         _resize_data.push_back(resize_data);
2466                 }
2467         }
2468 }
2469
2470 /** Update resizing notes while user drags.
2471  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2472  * @param at_front which end of the note (true == note on, false == note off)
2473  * @param delta_x change in mouse position since the start of the drag
2474  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2475  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2476  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2477  * as the \a primary note.
2478  */
2479 void
2480 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2481 {
2482         bool cursor_set = false;
2483
2484         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2485                 SimpleRect* resize_rect = (*i)->resize_rect;
2486                 CanvasNote* canvas_note = (*i)->canvas_note;
2487                 double current_x;
2488
2489                 if (at_front) {
2490                         if (relative) {
2491                                 current_x = canvas_note->x1() + delta_x;
2492                         } else {
2493                                 current_x = primary->x1() + delta_x;
2494                         }
2495                 } else {
2496                         if (relative) {
2497                                 current_x = canvas_note->x2() + delta_x;
2498                         } else {
2499                                 current_x = primary->x2() + delta_x;
2500                         }
2501                 }
2502
2503                 if (at_front) {
2504                         resize_rect->property_x1() = snap_to_pixel(current_x);
2505                         resize_rect->property_x2() = canvas_note->x2();
2506                 } else {
2507                         resize_rect->property_x2() = snap_to_pixel(current_x);
2508                         resize_rect->property_x1() = canvas_note->x1();
2509                 }
2510
2511                 if (!cursor_set) {
2512                         double beats;
2513
2514                         beats = snap_pixel_to_frame (current_x);
2515                         /* XXX not sure this is correct - snap_pixel_to_frame()
2516                            returns an absolute frame.
2517                         */
2518                         beats = region_frames_to_region_beats (beats);
2519
2520                         double len;
2521
2522                         if (at_front) {
2523                                 if (beats < canvas_note->note()->end_time()) {
2524                                         len = canvas_note->note()->time() - beats;
2525                                         len += canvas_note->note()->length();
2526                                 } else {
2527                                         len = 0;
2528                                 }
2529                         } else {
2530                                 if (beats >= canvas_note->note()->time()) {
2531                                         len = beats - canvas_note->note()->time();
2532                                 } else {
2533                                         len = 0;
2534                                 }
2535                         }
2536
2537                         char buf[16];
2538                         snprintf (buf, sizeof (buf), "%.3g beats", len);
2539                         show_verbose_cursor (buf, 0, 0);
2540
2541                         cursor_set = true;
2542                 }
2543
2544         }
2545 }
2546
2547
2548 /** Finish resizing notes when the user releases the mouse button.
2549  *  Parameters the same as for \a update_resizing().
2550  */
2551 void
2552 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2553 {
2554         start_note_diff_command (_("resize notes"));
2555
2556         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2557                 CanvasNote*  canvas_note = (*i)->canvas_note;
2558                 SimpleRect*  resize_rect = (*i)->resize_rect;
2559
2560                 /* Get the new x position for this resize, which is in pixels relative
2561                  * to the region position.
2562                  */
2563                 
2564                 double current_x;
2565
2566                 if (at_front) {
2567                         if (relative) {
2568                                 current_x = canvas_note->x1() + delta_x;
2569                         } else {
2570                                 current_x = primary->x1() + delta_x;
2571                         }
2572                 } else {
2573                         if (relative) {
2574                                 current_x = canvas_note->x2() + delta_x;
2575                         } else {
2576                                 current_x = primary->x2() + delta_x;
2577                         }
2578                 }
2579
2580                 /* Convert that to a frame within the region */
2581                 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2582
2583                 /* and then to beats */
2584                 /* XXX not sure this is correct - snap_pixel_to_frame()
2585                    returns an absolute frame.
2586                 */
2587                 current_x = region_frames_to_region_beats (current_x);
2588
2589                 if (at_front && current_x < canvas_note->note()->end_time()) {
2590                         note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2591
2592                         double len = canvas_note->note()->time() - current_x;
2593                         len += canvas_note->note()->length();
2594
2595                         if (len > 0) {
2596                                 /* XXX convert to beats */
2597                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2598                         }
2599                 }
2600
2601                 if (!at_front) {
2602                         double len = current_x - canvas_note->note()->time();
2603
2604                         if (len > 0) {
2605                                 /* XXX convert to beats */
2606                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2607                         }
2608                 }
2609
2610                 delete resize_rect;
2611                 delete (*i);
2612         }
2613
2614         _resize_data.clear();
2615         apply_diff();
2616 }
2617
2618 void
2619 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2620 {
2621         uint8_t new_velocity;
2622
2623         if (relative) {
2624                 new_velocity = event->note()->velocity() + velocity;
2625                 clamp_to_0_127(new_velocity);
2626         } else {
2627                 new_velocity = velocity;
2628         }
2629
2630         event->set_selected (event->selected()); // change color
2631
2632         note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2633 }
2634
2635 void
2636 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2637 {
2638         uint8_t new_note;
2639
2640         if (relative) {
2641                 new_note = event->note()->note() + note;
2642         } else {
2643                 new_note = note;
2644         }
2645
2646         clamp_to_0_127 (new_note);
2647         note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2648 }
2649
2650 void
2651 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2652 {
2653         bool change_start = false;
2654         bool change_length = false;
2655         Evoral::MusicalTime new_start = 0;
2656         Evoral::MusicalTime new_length = 0;
2657
2658         /* NOTE: the semantics of the two delta arguments are slightly subtle:
2659
2660            front_delta: if positive - move the start of the note later in time (shortening it)
2661            if negative - move the start of the note earlier in time (lengthening it)
2662
2663            end_delta:   if positive - move the end of the note later in time (lengthening it)
2664            if negative - move the end of the note earlier in time (shortening it)
2665         */
2666
2667         if (front_delta) {
2668                 if (front_delta < 0) {
2669
2670                         if (event->note()->time() < -front_delta) {
2671                                 new_start = 0;
2672                         } else {
2673                                 new_start = event->note()->time() + front_delta; // moves earlier
2674                         }
2675
2676                         /* start moved toward zero, so move the end point out to where it used to be.
2677                            Note that front_delta is negative, so this increases the length.
2678                         */
2679
2680                         new_length = event->note()->length() - front_delta;
2681                         change_start = true;
2682                         change_length = true;
2683
2684                 } else {
2685
2686                         Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2687
2688                         if (new_pos < event->note()->end_time()) {
2689                                 new_start = event->note()->time() + front_delta;
2690                                 /* start moved toward the end, so move the end point back to where it used to be */
2691                                 new_length = event->note()->length() - front_delta;
2692                                 change_start = true;
2693                                 change_length = true;
2694                         }
2695                 }
2696
2697         }
2698
2699         if (end_delta) {
2700                 bool can_change = true;
2701                 if (end_delta < 0) {
2702                         if (event->note()->length() < -end_delta) {
2703                                 can_change = false;
2704                         }
2705                 }
2706
2707                 if (can_change) {
2708                         new_length = event->note()->length() + end_delta;
2709                         change_length = true;
2710                 }
2711         }
2712
2713         if (change_start) {
2714                 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2715         }
2716
2717         if (change_length) {
2718                 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2719         }
2720 }
2721
2722 void
2723 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2724 {
2725         uint8_t new_channel;
2726
2727         if (relative) {
2728                 if (chn < 0.0) {
2729                         if (event->note()->channel() < -chn) {
2730                                 new_channel = 0;
2731                         } else {
2732                                 new_channel = event->note()->channel() + chn;
2733                         }
2734                 } else {
2735                         new_channel = event->note()->channel() + chn;
2736                 }
2737         } else {
2738                 new_channel = (uint8_t) chn;
2739         }
2740
2741         note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2742 }
2743
2744 void
2745 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2746 {
2747         Evoral::MusicalTime new_time;
2748
2749         if (relative) {
2750                 if (delta < 0.0) {
2751                         if (event->note()->time() < -delta) {
2752                                 new_time = 0;
2753                         } else {
2754                                 new_time = event->note()->time() + delta;
2755                         }
2756                 } else {
2757                         new_time = event->note()->time() + delta;
2758                 }
2759         } else {
2760                 new_time = delta;
2761         }
2762
2763         note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2764 }
2765
2766 void
2767 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2768 {
2769         note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2770 }
2771
2772 void
2773 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2774 {
2775         int8_t delta;
2776
2777         if (_selection.empty()) {
2778                 return;
2779         }
2780
2781         if (fine) {
2782                 delta = 1;
2783         } else {
2784                 delta = 10;
2785         }
2786
2787         if (!up) {
2788                 delta = -delta;
2789         }
2790
2791         if (!allow_smush) {
2792                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2793                         if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2794                                 return;
2795                         }
2796                 }
2797         }
2798
2799         start_note_diff_command (_("change velocities"));
2800
2801         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2802                 Selection::iterator next = i;
2803                 ++next;
2804                 change_note_velocity (*i, delta, true);
2805                 i = next;
2806         }
2807
2808         apply_diff();
2809
2810         if (!_selection.empty()) {
2811                 char buf[24];
2812                 snprintf (buf, sizeof (buf), "Vel %d",
2813                           (int) (*_selection.begin())->note()->velocity());
2814                 show_verbose_cursor (buf, 10, 10);
2815         }
2816 }
2817
2818
2819 void
2820 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2821 {
2822         if (_selection.empty()) {
2823                 return;
2824         }
2825
2826         int8_t delta;
2827
2828         if (fine) {
2829                 delta = 1;
2830         } else {
2831                 delta = 12;
2832         }
2833
2834         if (!up) {
2835                 delta = -delta;
2836         }
2837
2838         if (!allow_smush) {
2839                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2840                         if (!up) {
2841                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2842                                         return;
2843                                 }
2844                         } else {
2845                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
2846                                         return;
2847                                 }
2848                         }
2849                 }
2850         }
2851
2852         start_note_diff_command (_("transpose"));
2853
2854         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2855                 Selection::iterator next = i;
2856                 ++next;
2857                 change_note_note (*i, delta, true);
2858                 i = next;
2859         }
2860
2861         apply_diff ();
2862 }
2863
2864 void
2865 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2866 {
2867         if (delta == 0.0) {
2868                 if (fine) {
2869                         delta = 1.0/128.0;
2870                 } else {
2871                         /* grab the current grid distance */
2872                         bool success;
2873                         delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2874                         if (!success) {
2875                                 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2876                                 cerr << "Grid type not available as beats - TO BE FIXED\n";
2877                                 return;
2878                         }
2879                 }
2880         }
2881
2882         if (shorter) {
2883                 delta = -delta;
2884         }
2885
2886         start_note_diff_command (_("change note lengths"));
2887
2888         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2889                 Selection::iterator next = i;
2890                 ++next;
2891
2892                 /* note the negation of the delta for start */
2893
2894                 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2895                 i = next;
2896         }
2897
2898         apply_diff ();
2899
2900 }
2901
2902 void
2903 MidiRegionView::nudge_notes (bool forward)
2904 {
2905         if (_selection.empty()) {
2906                 return;
2907         }
2908
2909         /* pick a note as the point along the timeline to get the nudge distance.
2910            its not necessarily the earliest note, so we may want to pull the notes out
2911            into a vector and sort before using the first one.
2912         */
2913
2914         framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2915         framepos_t unused;
2916         framepos_t distance;
2917
2918         if (trackview.editor().snap_mode() == Editing::SnapOff) {
2919
2920                 /* grid is off - use nudge distance */
2921
2922                 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2923
2924         } else {
2925
2926                 /* use grid */
2927
2928                 framepos_t next_pos = ref_point;
2929
2930                 if (forward) {
2931                         if (max_framepos - 1 < next_pos) {
2932                                 next_pos += 1;
2933                         }
2934                 } else {
2935                         if (next_pos == 0) {
2936                                 return;
2937                         }
2938                         next_pos -= 1;
2939                 }
2940
2941                 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2942                 distance = ref_point - next_pos;
2943         }
2944
2945         if (distance == 0) {
2946                 return;
2947         }
2948
2949         Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2950
2951         if (!forward) {
2952                 delta = -delta;
2953         }
2954
2955         start_note_diff_command (_("nudge"));
2956
2957         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2958                 Selection::iterator next = i;
2959                 ++next;
2960                 change_note_time (*i, delta, true);
2961                 i = next;
2962         }
2963
2964         apply_diff ();
2965 }
2966
2967 void
2968 MidiRegionView::change_channel(uint8_t channel)
2969 {
2970         start_note_diff_command(_("change channel"));
2971         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2972                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2973         }
2974
2975         apply_diff();
2976 }
2977
2978
2979 void
2980 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2981 {
2982         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2983
2984         _pre_enter_cursor = editor->get_canvas_cursor ();
2985
2986         if (_mouse_state == SelectTouchDragging) {
2987                 note_selected (ev, true);
2988         }
2989
2990         show_verbose_cursor (ev->note ());
2991 }
2992
2993 void
2994 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2995 {
2996         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2997
2998         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2999                 (*i)->hide_velocity ();
3000         }
3001
3002         editor->verbose_cursor()->hide ();
3003
3004         if (_pre_enter_cursor) {
3005                 editor->set_canvas_cursor (_pre_enter_cursor);
3006                 _pre_enter_cursor = 0;
3007         }
3008 }
3009
3010 void
3011 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3012 {
3013         ostringstream s;
3014         /* XXX should get patch name if we can */
3015         s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3016         show_verbose_cursor (s.str(), 10, 20);
3017 }
3018
3019 void
3020 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3021 {
3022         trackview.editor().verbose_cursor()->hide ();
3023 }
3024
3025 void
3026 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3027 {
3028         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3029
3030         if (x_fraction > 0.0 && x_fraction < 0.25) {
3031                 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3032         } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3033                 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3034         } else {
3035                 if (_pre_enter_cursor && can_set_cursor) {
3036                         editor->set_canvas_cursor (_pre_enter_cursor);
3037                 }
3038         }
3039 }
3040
3041 void
3042 MidiRegionView::set_frame_color()
3043 {
3044         uint32_t f;
3045
3046         TimeAxisViewItem::set_frame_color ();
3047
3048         if (!frame) {
3049                 return;
3050         }
3051
3052         if (_selected) {
3053                 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3054         } else if (high_enough_for_name) {
3055                 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3056         } else {
3057                 f = fill_color;
3058         }
3059
3060         if (!rect_visible) {
3061                 f = UINT_RGBA_CHANGE_A (f, 0);
3062         }
3063
3064         frame->property_fill_color_rgba() = f;
3065 }
3066
3067 void
3068 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3069 {
3070         switch (mode) {
3071         case AllChannels:
3072         case FilterChannels:
3073                 _force_channel = -1;
3074                 break;
3075         case ForceChannel:
3076                 _force_channel = mask;
3077                 mask = 0xFFFF; // Show all notes as active (below)
3078         };
3079
3080         // Update notes for selection
3081         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3082                 (*i)->on_channel_selection_change(mask);
3083         }
3084
3085         _last_channel_selection = mask;
3086
3087         _patch_changes.clear ();
3088         display_patch_changes ();
3089 }
3090
3091 void
3092 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3093 {
3094         _model_name         = model;
3095         _custom_device_mode = custom_device_mode;
3096         redisplay_model();
3097 }
3098
3099 void
3100 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3101 {
3102         if (_selection.empty()) {
3103                 return;
3104         }
3105
3106         PublicEditor& editor (trackview.editor());
3107
3108         switch (op) {
3109         case Delete:
3110                 /* XXX what to do ? */
3111                 break;
3112         case Cut:
3113         case Copy:
3114                 editor.get_cut_buffer().add (selection_as_cut_buffer());
3115                 break;
3116         default:
3117                 break;
3118         }
3119
3120         if (op != Copy) {
3121
3122                 start_note_diff_command();
3123
3124                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3125                         switch (op) {
3126                         case Copy:
3127                                 break;
3128                         case Delete:
3129                         case Cut:
3130                         case Clear:
3131                                 note_diff_remove_note (*i);
3132                                 break;
3133                         }
3134                 }
3135
3136                 apply_diff();
3137         }
3138 }
3139
3140 MidiCutBuffer*
3141 MidiRegionView::selection_as_cut_buffer () const
3142 {
3143         Notes notes;
3144
3145         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3146                 NoteType* n = (*i)->note().get();
3147                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3148         }
3149
3150         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3151         cb->set (notes);
3152
3153         return cb;
3154 }
3155
3156 /** This method handles undo */
3157 void
3158 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3159 {
3160         if (mcb.empty()) {
3161                 return;
3162         }
3163
3164         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3165
3166         trackview.session()->begin_reversible_command (_("paste"));
3167
3168         start_note_diff_command (_("paste"));
3169
3170         Evoral::MusicalTime beat_delta;
3171         Evoral::MusicalTime paste_pos_beats;
3172         Evoral::MusicalTime duration;
3173         Evoral::MusicalTime end_point = 0;
3174
3175         duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3176         paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3177         beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3178         paste_pos_beats = 0;
3179
3180         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3181                                                        (*mcb.notes().begin())->time(),
3182                                                        (*mcb.notes().rbegin())->end_time(),
3183                                                        duration, pos, _region->position(),
3184                                                        paste_pos_beats, beat_delta));
3185
3186         clear_selection ();
3187
3188         for (int n = 0; n < (int) times; ++n) {
3189
3190                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3191
3192                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3193                         copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3194
3195                         /* make all newly added notes selected */
3196
3197                         note_diff_add_note (copied_note, true);
3198                         end_point = copied_note->end_time();
3199                 }
3200
3201                 paste_pos_beats += duration;
3202         }
3203
3204         /* if we pasted past the current end of the region, extend the region */
3205
3206         framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3207         framepos_t region_end = _region->position() + _region->length() - 1;
3208
3209         if (end_frame > region_end) {
3210
3211                 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3212
3213                 _region->clear_changes ();
3214                 _region->set_length (end_frame);
3215                 trackview.session()->add_command (new StatefulDiffCommand (_region));
3216         }
3217
3218         apply_diff (true);
3219
3220         trackview.session()->commit_reversible_command ();
3221 }
3222
3223 struct EventNoteTimeEarlyFirstComparator {
3224         bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3225                 return a->note()->time() < b->note()->time();
3226         }
3227 };
3228
3229 void
3230 MidiRegionView::time_sort_events ()
3231 {
3232         if (!_sort_needed) {
3233                 return;
3234         }
3235
3236         EventNoteTimeEarlyFirstComparator cmp;
3237         _events.sort (cmp);
3238
3239         _sort_needed = false;
3240 }
3241
3242 void
3243 MidiRegionView::goto_next_note (bool add_to_selection)
3244 {
3245         bool use_next = false;
3246
3247         if (_events.back()->selected()) {
3248                 return;
3249         }
3250
3251         time_sort_events ();
3252
3253         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3254                 if ((*i)->selected()) {
3255                         use_next = true;
3256                         continue;
3257                 } else if (use_next) {
3258                         if (!add_to_selection) {
3259                                 unique_select (*i);
3260                         } else {
3261                                 note_selected (*i, true, false);
3262                         }
3263                         return;
3264                 }
3265         }
3266
3267         /* use the first one */
3268
3269         unique_select (_events.front());
3270
3271 }
3272
3273 void
3274 MidiRegionView::goto_previous_note (bool add_to_selection)
3275 {
3276         bool use_next = false;
3277
3278         if (_events.front()->selected()) {
3279                 return;
3280         }
3281
3282         time_sort_events ();
3283
3284         for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3285                 if ((*i)->selected()) {
3286                         use_next = true;
3287                         continue;
3288                 } else if (use_next) {
3289                         if (!add_to_selection) {
3290                                 unique_select (*i);
3291                         } else {
3292                                 note_selected (*i, true, false);
3293                         }
3294                         return;
3295                 }
3296         }
3297
3298         /* use the last one */
3299
3300         unique_select (*(_events.rbegin()));
3301 }
3302
3303 void
3304 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3305 {
3306         bool had_selected = false;
3307
3308         time_sort_events ();
3309
3310         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3311                 if ((*i)->selected()) {
3312                         selected.insert ((*i)->note());
3313                         had_selected = true;
3314                 }
3315         }
3316
3317         if (allow_all_if_none_selected && !had_selected) {
3318                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3319                         selected.insert ((*i)->note());
3320                 }
3321         }
3322 }
3323
3324 void
3325 MidiRegionView::update_ghost_note (double x, double y)
3326 {
3327         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3328
3329         _last_ghost_x = x;
3330         _last_ghost_y = y;
3331
3332         _note_group->w2i (x, y);
3333
3334         PublicEditor& editor = trackview.editor ();
3335         
3336         framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3337         framecnt_t grid_frames;
3338         framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3339         
3340         /* use region_frames... because we are converting a delta within the region
3341         */
3342          
3343         double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3344
3345         /* note that this sets the time of the ghost note in beats relative to
3346            the start of the source; that is how all note times are stored.
3347         */
3348         _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3349         _ghost_note->note()->set_length (length);
3350         _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3351         _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3352
3353         /* the ghost note does not appear in ghost regions, so pass false in here */
3354         update_note (_ghost_note, false);
3355
3356         show_verbose_cursor (_ghost_note->note ());
3357 }
3358
3359 void
3360 MidiRegionView::create_ghost_note (double x, double y)
3361 {
3362         delete _ghost_note;
3363         _ghost_note = 0;
3364
3365         boost::shared_ptr<NoteType> g (new NoteType);
3366         _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3367         _ghost_note->property_outline_color_rgba() = 0x000000aa;
3368         update_ghost_note (x, y);
3369         _ghost_note->show ();
3370
3371         _last_ghost_x = x;
3372         _last_ghost_y = y;
3373
3374         show_verbose_cursor (_ghost_note->note ());
3375 }
3376
3377 void
3378 MidiRegionView::snap_changed ()
3379 {
3380         if (!_ghost_note) {
3381                 return;
3382         }
3383
3384         create_ghost_note (_last_ghost_x, _last_ghost_y);
3385 }
3386
3387 void
3388 MidiRegionView::drop_down_keys ()
3389 {
3390         _mouse_state = None;
3391 }
3392
3393 void
3394 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3395 {
3396         double note = midi_stream_view()->y_to_note(y);
3397         Events e;
3398         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3399
3400         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3401
3402         if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3403                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3404         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3405                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3406         } else {
3407                 return;
3408         }
3409
3410         bool add_mrv_selection = false;
3411
3412         if (_selection.empty()) {
3413                 add_mrv_selection = true;
3414         }
3415
3416         for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3417                 if (_selection.insert (*i).second) {
3418                         (*i)->set_selected (true);
3419                 }
3420         }
3421
3422         if (add_mrv_selection) {
3423                 PublicEditor& editor (trackview.editor());
3424                 editor.get_selection().add (this);
3425         }
3426 }
3427
3428 void
3429 MidiRegionView::color_handler ()
3430 {
3431         RegionView::color_handler ();
3432
3433         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3434                 (*i)->set_selected ((*i)->selected()); // will change color
3435         }
3436
3437         /* XXX probably more to do here */
3438 }
3439
3440 void
3441 MidiRegionView::enable_display (bool yn)
3442 {
3443         RegionView::enable_display (yn);
3444         if (yn) {
3445                 redisplay_model ();
3446         }
3447 }
3448
3449 void
3450 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3451 {
3452         if (_step_edit_cursor == 0) {
3453                 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3454
3455                 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3456                 _step_edit_cursor->property_y1() = 0;
3457                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3458                 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3459                 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3460         }
3461
3462         move_step_edit_cursor (pos);
3463         _step_edit_cursor->show ();
3464 }
3465
3466 void
3467 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3468 {
3469         _step_edit_cursor_position = pos;
3470
3471         if (_step_edit_cursor) {
3472                 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3473                 _step_edit_cursor->property_x1() = pixel;
3474                 set_step_edit_cursor_width (_step_edit_cursor_width);
3475         }
3476 }
3477
3478 void
3479 MidiRegionView::hide_step_edit_cursor ()
3480 {
3481         if (_step_edit_cursor) {
3482                 _step_edit_cursor->hide ();
3483         }
3484 }
3485
3486 void
3487 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3488 {
3489         _step_edit_cursor_width = beats;
3490
3491         if (_step_edit_cursor) {
3492                 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3493         }
3494 }
3495
3496 /** Called when a diskstream on our track has received some data.  Update the view, if applicable.
3497  *  @param buf Data that has been recorded.
3498  *  @param w Source that this data will end up in.
3499  */
3500 void
3501 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3502 {
3503         if (!_active_notes) {
3504                 /* we aren't actively being recorded to */
3505                 return;
3506         }
3507
3508         boost::shared_ptr<MidiSource> src = w.lock ();
3509         if (!src || src != midi_region()->midi_source()) {
3510                 /* recorded data was not destined for our source */
3511                 return;
3512         }
3513
3514         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3515         BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3516
3517         framepos_t back = max_framepos;
3518
3519         for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3520                 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3521                 assert (ev.buffer ());
3522
3523                 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3524
3525                 if (ev.type() == MIDI_CMD_NOTE_ON) {
3526
3527                         boost::shared_ptr<NoteType> note (
3528                                 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3529                                                           );
3530
3531                         add_note (note, true);
3532
3533                         /* fix up our note range */
3534                         if (ev.note() < _current_range_min) {
3535                                 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3536                         } else if (ev.note() > _current_range_max) {
3537                                 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3538                         }
3539
3540                 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3541                         resolve_note (ev.note (), time_beats);
3542                 }
3543
3544                 back = ev.time ();
3545         }
3546
3547         midi_stream_view()->check_record_layers (region(), back);
3548 }
3549
3550 void
3551 MidiRegionView::trim_front_starting ()
3552 {
3553         /* Reparent the note group to the region view's parent, so that it doesn't change
3554            when the region view is trimmed.
3555         */
3556         _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3557         _temporary_note_group->move (group->property_x(), group->property_y());
3558         _note_group->reparent (*_temporary_note_group);
3559 }
3560
3561 void
3562 MidiRegionView::trim_front_ending ()
3563 {
3564         _note_group->reparent (*group);
3565         delete _temporary_note_group;
3566         _temporary_note_group = 0;
3567
3568         if (_region->start() < 0) {
3569                 /* Trim drag made start time -ve; fix this */
3570                 midi_region()->fix_negative_start ();
3571         }
3572 }
3573
3574 void
3575 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3576 {
3577         PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3578         if (d.run () != Gtk::RESPONSE_ACCEPT) {
3579                 return;
3580         }
3581
3582         change_patch_change (pc->patch(), d.patch ());
3583 }
3584
3585
3586 void
3587 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3588 {
3589         char buf[24];
3590         snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3591                   Evoral::midi_note_name (n->note()).c_str(),
3592                   (int) n->note (),
3593                   (int) n->channel() + 1,
3594                   (int) n->velocity());
3595
3596         show_verbose_cursor (buf, 10, 20);
3597 }
3598
3599 void
3600 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3601 {
3602         double wx, wy;
3603
3604         trackview.editor().get_pointer_position (wx, wy);
3605
3606         wx += xoffset;
3607         wy += yoffset;
3608
3609         /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3610
3611         double x1, y1, x2, y2;
3612         trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3613
3614         if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3615                 wy -= (y2 - y1) + 2 * yoffset;
3616         }
3617
3618         trackview.editor().verbose_cursor()->set (text, wx, wy);
3619         trackview.editor().verbose_cursor()->show ();
3620 }
3621
3622 /** @param p A session framepos.
3623  *  @param grid_frames Filled in with the number of frames that a grid interval is at p.
3624  *  @return p snapped to the grid subdivision underneath it.
3625  */
3626 framepos_t
3627 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3628 {
3629         PublicEditor& editor = trackview.editor ();
3630         
3631         bool success;
3632         Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3633
3634         if (!success) {
3635                 grid_beats = 1;
3636         }
3637         
3638         grid_frames = region_beats_to_region_frames (grid_beats);
3639
3640         /* Hack so that we always snap to the note that we are over, instead of snapping
3641            to the next one if we're more than halfway through the one we're over.
3642         */
3643         if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3644                 p -= grid_frames / 2;
3645         }
3646
3647         return snap_frame_to_frame (p);
3648 }
3649
3650 /** Called when the selection has been cleared in any MidiRegionView.
3651  *  @param rv MidiRegionView that the selection was cleared in.
3652  */
3653 void
3654 MidiRegionView::selection_cleared (MidiRegionView* rv)
3655 {
3656         if (rv == this) {
3657                 return;
3658         }
3659
3660         /* Clear our selection in sympathy; but don't signal the fact */
3661         clear_selection (false);
3662 }